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    pub smccc_trng: bool,
745    #[cfg(all(windows, feature = "audio"))]
746    pub snd_split_configs: Vec<SndSplitConfig>,
747    pub socket_path: Option<PathBuf>,
748    #[cfg(feature = "audio")]
749    pub sound: Option<PathBuf>,
750    pub stub_pci_devices: Vec<StubPciParameters>,
751    pub suspended: bool,
752    #[cfg(target_arch = "aarch64")]
753    pub sve: Option<SveConfig>,
754    pub swap_dir: Option<PathBuf>,
755    pub swiotlb: Option<u64>,
756    #[cfg(target_os = "android")]
757    pub task_profiles: Vec<String>,
758    #[cfg(any(target_os = "android", target_os = "linux"))]
759    pub unmap_guest_memory_on_fork: bool,
760    pub usb: bool,
761    #[cfg(any(target_os = "android", target_os = "linux"))]
762    #[cfg(feature = "media")]
763    pub v4l2_proxy: Vec<PathBuf>,
764    pub vcpu_affinity: Option<VcpuAffinity>,
765    pub vcpu_cgroup_path: Option<PathBuf>,
766    pub vcpu_count: Option<usize>,
767    #[cfg(target_arch = "x86_64")]
768    pub vcpu_hybrid_type: BTreeMap<usize, CpuHybridType>, // CPU index -> hybrid type
769    #[cfg(any(target_os = "android", target_os = "linux"))]
770    pub vfio: Vec<super::sys::config::VfioOption>,
771    #[cfg(any(target_os = "android", target_os = "linux"))]
772    pub vfio_isolate_hotplug: bool,
773    #[cfg(any(target_os = "android", target_os = "linux"))]
774    #[cfg(target_arch = "aarch64")]
775    pub vhost_scmi: bool,
776    #[cfg(any(target_os = "android", target_os = "linux"))]
777    #[cfg(target_arch = "aarch64")]
778    pub vhost_scmi_device: PathBuf,
779    pub vhost_user: Vec<VhostUserFrontendOption>,
780    pub vhost_user_connect_timeout_ms: Option<u64>,
781    #[cfg(feature = "video-decoder")]
782    pub video_dec: Vec<VideoDeviceConfig>,
783    #[cfg(feature = "video-encoder")]
784    pub video_enc: Vec<VideoDeviceConfig>,
785    #[cfg(all(
786        target_arch = "aarch64",
787        any(target_os = "android", target_os = "linux")
788    ))]
789    pub virt_cpufreq: bool,
790    pub virt_cpufreq_v2: bool,
791    pub virtio_input: Vec<InputDeviceOption>,
792    #[cfg(feature = "audio")]
793    #[serde(skip)]
794    pub virtio_snds: Vec<SndParameters>,
795    pub vsock: Option<VsockConfig>,
796    #[cfg(feature = "vtpm")]
797    pub vtpm_proxy: bool,
798    pub wayland_socket_paths: BTreeMap<String, PathBuf>,
799    #[cfg(all(windows, feature = "gpu"))]
800    pub window_procedure_thread_split_config: Option<WindowProcedureThreadSplitConfig>,
801    pub x_display: Option<String>,
802}
803
804impl Default for Config {
805    fn default() -> Config {
806        Config {
807            #[cfg(all(target_arch = "x86_64", unix))]
808            ac_adapter: false,
809            acpi_tables: Vec::new(),
810            #[cfg(feature = "android_display")]
811            android_display_service: None,
812            android_fstab: None,
813            async_executor: None,
814            #[cfg(feature = "balloon")]
815            balloon: true,
816            #[cfg(feature = "balloon")]
817            balloon_bias: 0,
818            #[cfg(feature = "balloon")]
819            balloon_control: None,
820            #[cfg(feature = "balloon")]
821            balloon_page_reporting: false,
822            #[cfg(feature = "balloon")]
823            balloon_ws_num_bins: VIRTIO_BALLOON_WS_DEFAULT_NUM_BINS,
824            #[cfg(feature = "balloon")]
825            balloon_ws_reporting: false,
826            battery_config: None,
827            boot_cpu: 0,
828            #[cfg(windows)]
829            block_control_tube: Vec::new(),
830            #[cfg(windows)]
831            block_vhost_user_tube: Vec::new(),
832            #[cfg(target_arch = "x86_64")]
833            break_linux_pci_config_io: false,
834            #[cfg(windows)]
835            broker_shutdown_event: None,
836            #[cfg(target_arch = "x86_64")]
837            bus_lock_ratelimit: 0,
838            #[cfg(any(target_os = "android", target_os = "linux"))]
839            coiommu_param: None,
840            core_scheduling: true,
841            #[cfg(feature = "crash-report")]
842            crash_pipe_name: None,
843            #[cfg(feature = "crash-report")]
844            crash_report_uuid: None,
845            cpu_capacity: BTreeMap::new(),
846            cpu_clusters: Vec::new(),
847            #[cfg(all(
848                target_arch = "aarch64",
849                any(target_os = "android", target_os = "linux")
850            ))]
851            cpu_frequencies_khz: BTreeMap::new(),
852            cpu_freq_domains: Vec::new(),
853            #[cfg(all(
854                target_arch = "aarch64",
855                any(target_os = "android", target_os = "linux")
856            ))]
857            cpu_ipc_ratio: BTreeMap::new(),
858            delay_rt: false,
859            device_tree_overlay: Vec::new(),
860            disks: Vec::new(),
861            disable_virtio_intx: false,
862            display_input_height: None,
863            display_input_width: None,
864            display_window_keyboard: false,
865            display_window_mouse: false,
866            dump_device_tree_blob: None,
867            dynamic_power_coefficient: BTreeMap::new(),
868            enable_fw_cfg: false,
869            enable_hwp: false,
870            executable_path: None,
871            #[cfg(windows)]
872            exit_stats: false,
873            fdt_position: None,
874            #[cfg(all(target_os = "android", target_arch = "aarch64"))]
875            ffa: None,
876            file_backed_mappings_mmio: Vec::new(),
877            file_backed_mappings_ram: Vec::new(),
878            force_calibrated_tsc_leaf: false,
879            force_disable_readonly_mem: false,
880            force_s2idle: false,
881            fw_cfg_parameters: Vec::new(),
882            #[cfg(feature = "gdb")]
883            gdb: None,
884            #[cfg(all(windows, feature = "gpu"))]
885            gpu_backend_config: None,
886            #[cfg(feature = "gpu")]
887            gpu_parameters: None,
888            #[cfg(all(unix, feature = "gpu"))]
889            gpu_render_server_parameters: None,
890            #[cfg(all(unix, feature = "gpu"))]
891            gpu_cgroup_path: None,
892            #[cfg(all(unix, feature = "gpu"))]
893            gpu_server_cgroup_path: None,
894            #[cfg(all(windows, feature = "gpu"))]
895            gpu_vmm_config: None,
896            host_cpu_topology: false,
897            #[cfg(windows)]
898            host_guid: None,
899            #[cfg(windows)]
900            product_version: None,
901            #[cfg(windows)]
902            product_channel: None,
903            hugepages: false,
904            hypervisor: None,
905            #[cfg(feature = "balloon")]
906            init_memory: None,
907            initrd_path: None,
908            #[cfg(all(windows, feature = "gpu"))]
909            input_event_split_config: None,
910            irq_chip: None,
911            itmt: false,
912            jail_config: if !cfg!(feature = "default-no-sandbox") {
913                Some(Default::default())
914            } else {
915                None
916            },
917            #[cfg(windows)]
918            kernel_log_file: None,
919            #[cfg(any(target_os = "android", target_os = "linux"))]
920            lock_guest_memory: false,
921            #[cfg(windows)]
922            log_file: None,
923            #[cfg(windows)]
924            logs_directory: None,
925            #[cfg(any(target_os = "android", target_os = "linux"))]
926            boost_uclamp: false,
927            #[cfg(all(feature = "media", feature = "video-decoder"))]
928            media_decoder: Default::default(),
929            memory: None,
930            memory_file: None,
931            mmio_address_ranges: Vec::new(),
932            #[cfg(target_arch = "aarch64")]
933            mte: false,
934            name: None,
935            #[cfg(feature = "net")]
936            net: Vec::new(),
937            #[cfg(windows)]
938            net_vhost_user_tube: None,
939            no_i8042: false,
940            no_pmu: false,
941            no_rtc: false,
942            no_smt: false,
943            params: Vec::new(),
944            pci_config: Default::default(),
945            #[cfg(feature = "pci-hotplug")]
946            pci_hotplug_slots: None,
947            per_vm_core_scheduling: false,
948            pflash_parameters: None,
949            #[cfg(any(target_os = "android", target_os = "linux"))]
950            pmem_ext2: Vec::new(),
951            pmems: Vec::new(),
952            #[cfg(feature = "process-invariants")]
953            process_invariants_data_handle: None,
954            #[cfg(feature = "process-invariants")]
955            process_invariants_data_size: None,
956            #[cfg(windows)]
957            product_name: None,
958            protection_type: ProtectionType::Unprotected,
959            pstore: None,
960            #[cfg(feature = "pvclock")]
961            pvclock: false,
962            pvm_fw: None,
963            restore_path: None,
964            rng: true,
965            rt_cpus: Default::default(),
966            serial_parameters: BTreeMap::new(),
967            scsis: Vec::new(),
968            #[cfg(windows)]
969            service_pipe_name: None,
970            #[cfg(any(target_os = "android", target_os = "linux"))]
971            shared_dirs: Vec::new(),
972            #[cfg(feature = "media")]
973            simple_media_device: Default::default(),
974            #[cfg(any(feature = "slirp-ring-capture", feature = "slirp-debug"))]
975            slirp_capture_file: None,
976            #[cfg(target_arch = "x86_64")]
977            smbios: SmbiosOptions::default(),
978            smccc_trng: false,
979            #[cfg(all(windows, feature = "audio"))]
980            snd_split_configs: Vec::new(),
981            socket_path: None,
982            #[cfg(feature = "audio")]
983            sound: None,
984            stub_pci_devices: Vec::new(),
985            suspended: false,
986            #[cfg(target_arch = "aarch64")]
987            sve: None,
988            swap_dir: None,
989            swiotlb: None,
990            #[cfg(target_os = "android")]
991            task_profiles: Vec::new(),
992            #[cfg(any(target_os = "android", target_os = "linux"))]
993            unmap_guest_memory_on_fork: false,
994            usb: true,
995            vcpu_affinity: None,
996            vcpu_cgroup_path: None,
997            vcpu_count: None,
998            #[cfg(target_arch = "x86_64")]
999            vcpu_hybrid_type: BTreeMap::new(),
1000            #[cfg(any(target_os = "android", target_os = "linux"))]
1001            vfio: Vec::new(),
1002            #[cfg(any(target_os = "android", target_os = "linux"))]
1003            vfio_isolate_hotplug: false,
1004            #[cfg(any(target_os = "android", target_os = "linux"))]
1005            #[cfg(target_arch = "aarch64")]
1006            vhost_scmi: false,
1007            #[cfg(any(target_os = "android", target_os = "linux"))]
1008            #[cfg(target_arch = "aarch64")]
1009            vhost_scmi_device: PathBuf::from(VHOST_SCMI_PATH),
1010            vhost_user: Vec::new(),
1011            vhost_user_connect_timeout_ms: None,
1012            vsock: None,
1013            #[cfg(feature = "video-decoder")]
1014            video_dec: Vec::new(),
1015            #[cfg(feature = "video-encoder")]
1016            video_enc: Vec::new(),
1017            #[cfg(all(
1018                target_arch = "aarch64",
1019                any(target_os = "android", target_os = "linux")
1020            ))]
1021            virt_cpufreq: false,
1022            virt_cpufreq_v2: false,
1023            virtio_input: Vec::new(),
1024            #[cfg(feature = "audio")]
1025            virtio_snds: Vec::new(),
1026            #[cfg(any(target_os = "android", target_os = "linux"))]
1027            #[cfg(feature = "media")]
1028            v4l2_proxy: Vec::new(),
1029            #[cfg(feature = "vtpm")]
1030            vtpm_proxy: false,
1031            wayland_socket_paths: BTreeMap::new(),
1032            #[cfg(windows)]
1033            window_procedure_thread_split_config: None,
1034            x_display: None,
1035        }
1036    }
1037}
1038
1039pub fn validate_config(cfg: &mut Config) -> std::result::Result<(), String> {
1040    if cfg.executable_path.is_none() {
1041        return Err("Executable is not specified".to_string());
1042    }
1043
1044    #[cfg(feature = "gpu")]
1045    {
1046        crate::crosvm::gpu_config::validate_gpu_config(cfg)?;
1047    }
1048    #[cfg(feature = "gdb")]
1049    if cfg.gdb.is_some() && cfg.vcpu_count.unwrap_or(1) != 1 {
1050        return Err("`gdb` requires the number of vCPU to be 1".to_string());
1051    }
1052    if cfg.host_cpu_topology {
1053        if cfg.no_smt {
1054            return Err(
1055                "`host-cpu-topology` cannot be set at the same time as `no_smt`, since \
1056                the smt of the Guest is the same as that of the Host when \
1057                `host-cpu-topology` is set."
1058                    .to_string(),
1059            );
1060        }
1061
1062        let pcpu_count =
1063            base::number_of_logical_cores().expect("Could not read number of logical cores");
1064        if let Some(vcpu_count) = cfg.vcpu_count {
1065            if pcpu_count != vcpu_count {
1066                return Err(format!(
1067                    "`host-cpu-topology` requires the count of vCPUs({vcpu_count}) to equal the \
1068                            count of CPUs({pcpu_count}) on host."
1069                ));
1070            }
1071        } else {
1072            cfg.vcpu_count = Some(pcpu_count);
1073        }
1074
1075        match &cfg.vcpu_affinity {
1076            None => {
1077                let mut affinity_map = BTreeMap::new();
1078                for cpu_id in 0..cfg.vcpu_count.unwrap() {
1079                    affinity_map.insert(cpu_id, CpuSet::new([cpu_id]));
1080                }
1081                cfg.vcpu_affinity = Some(VcpuAffinity::PerVcpu(affinity_map));
1082            }
1083            _ => {
1084                return Err(
1085                    "`host-cpu-topology` requires not to set `cpu-affinity` at the same time"
1086                        .to_string(),
1087                );
1088            }
1089        }
1090
1091        if !cfg.cpu_capacity.is_empty() {
1092            return Err(
1093                "`host-cpu-topology` requires not to set `cpu-capacity` at the same time"
1094                    .to_string(),
1095            );
1096        }
1097
1098        if !cfg.cpu_clusters.is_empty() {
1099            return Err(
1100                "`host-cpu-topology` requires not to set `cpu clusters` at the same time"
1101                    .to_string(),
1102            );
1103        }
1104    }
1105
1106    if cfg.boot_cpu >= cfg.vcpu_count.unwrap_or(1) {
1107        log::warn!("boot_cpu selection cannot be higher than vCPUs available, defaulting to 0");
1108        cfg.boot_cpu = 0;
1109    }
1110
1111    #[cfg(all(
1112        target_arch = "aarch64",
1113        any(target_os = "android", target_os = "linux")
1114    ))]
1115    if !cfg.cpu_frequencies_khz.is_empty() {
1116        if !cfg.virt_cpufreq_v2 {
1117            return Err("`cpu-frequencies` requires `virt-cpufreq-upstream`".to_string());
1118        }
1119
1120        if cfg.host_cpu_topology {
1121            return Err(
1122                "`host-cpu-topology` cannot be used with 'cpu-frequencies` at the same time"
1123                    .to_string(),
1124            );
1125        }
1126    }
1127
1128    #[cfg(all(
1129        target_arch = "aarch64",
1130        any(target_os = "android", target_os = "linux")
1131    ))]
1132    if cfg.virt_cpufreq {
1133        if !cfg.host_cpu_topology && (cfg.vcpu_affinity.is_none() || cfg.cpu_capacity.is_empty()) {
1134            return Err("`virt-cpufreq` requires 'host-cpu-topology' enabled or \
1135                       have vcpu_affinity and cpu_capacity configured"
1136                .to_string());
1137        }
1138    }
1139    #[cfg(target_arch = "x86_64")]
1140    if !cfg.vcpu_hybrid_type.is_empty() {
1141        if cfg.host_cpu_topology {
1142            return Err("`core-types` cannot be set with `host-cpu-topology`.".to_string());
1143        }
1144        check_host_hybrid_support(&CpuIdCall::new(__cpuid_count, __cpuid))
1145            .map_err(|e| format!("the cpu doesn't support `core-types`: {e}"))?;
1146        if cfg.vcpu_hybrid_type.len() != cfg.vcpu_count.unwrap_or(1) {
1147            return Err("`core-types` must be set for all virtual CPUs".to_string());
1148        }
1149        for cpu_id in 0..cfg.vcpu_count.unwrap_or(1) {
1150            if !cfg.vcpu_hybrid_type.contains_key(&cpu_id) {
1151                return Err("`core-types` must be set for all virtual CPUs".to_string());
1152            }
1153        }
1154    }
1155    #[cfg(target_arch = "x86_64")]
1156    if cfg.enable_hwp && !cfg.host_cpu_topology {
1157        return Err("setting `enable-hwp` requires `host-cpu-topology` is set.".to_string());
1158    }
1159    #[cfg(target_arch = "x86_64")]
1160    if cfg.itmt {
1161        use std::collections::BTreeSet;
1162        // ITMT only works on the case each vCPU is 1:1 mapping to a pCPU.
1163        // `host-cpu-topology` has already set this 1:1 mapping. If no
1164        // `host-cpu-topology`, we need check the cpu affinity setting.
1165        if !cfg.host_cpu_topology {
1166            // only VcpuAffinity::PerVcpu supports setting cpu affinity
1167            // for each vCPU.
1168            if let Some(VcpuAffinity::PerVcpu(v)) = &cfg.vcpu_affinity {
1169                // ITMT allows more pCPUs than vCPUs.
1170                if v.len() != cfg.vcpu_count.unwrap_or(1) {
1171                    return Err("`itmt` requires affinity to be set for every vCPU.".to_string());
1172                }
1173
1174                let mut pcpu_set = BTreeSet::new();
1175                for cpus in v.values() {
1176                    if cpus.len() != 1 {
1177                        return Err(
1178                            "`itmt` requires affinity to be set 1 pCPU for 1 vCPU.".to_owned()
1179                        );
1180                    }
1181                    // Ensure that each vCPU corresponds to a different pCPU to avoid pCPU sharing,
1182                    // otherwise it will seriously affect the ITMT scheduling optimization effect.
1183                    if !pcpu_set.insert(cpus[0]) {
1184                        return Err(
1185                            "`cpu_host` requires affinity to be set different pVPU for each vCPU."
1186                                .to_owned(),
1187                        );
1188                    }
1189                }
1190            } else {
1191                return Err("`itmt` requires affinity to be set for every vCPU.".to_string());
1192            }
1193        }
1194        if !cfg.enable_hwp {
1195            return Err("setting `itmt` requires `enable-hwp` is set.".to_string());
1196        }
1197    }
1198
1199    #[cfg(feature = "balloon")]
1200    {
1201        if !cfg.balloon && cfg.balloon_control.is_some() {
1202            return Err("'balloon-control' requires enabled balloon".to_string());
1203        }
1204
1205        if !cfg.balloon && cfg.balloon_page_reporting {
1206            return Err("'balloon_page_reporting' requires enabled balloon".to_string());
1207        }
1208    }
1209
1210    // TODO(b/253386409): Vmm-swap only support sandboxed devices until vmm-swap use
1211    // `devices::Suspendable` to suspend devices.
1212    #[cfg(feature = "swap")]
1213    if cfg.swap_dir.is_some() && cfg.jail_config.is_none() {
1214        return Err("'swap' and 'disable-sandbox' are mutually exclusive".to_string());
1215    }
1216
1217    set_default_serial_parameters(
1218        &mut cfg.serial_parameters,
1219        cfg.vhost_user
1220            .iter()
1221            .any(|opt| opt.type_ == DeviceType::Console),
1222    );
1223
1224    for mapping in cfg
1225        .file_backed_mappings_mmio
1226        .iter_mut()
1227        .chain(cfg.file_backed_mappings_ram.iter_mut())
1228    {
1229        validate_file_backed_mapping(mapping)?;
1230    }
1231
1232    for pmem in cfg.pmems.iter() {
1233        validate_pmem(pmem)?;
1234    }
1235
1236    // Validate platform specific things
1237    super::sys::config::validate_config(cfg)
1238}
1239
1240fn validate_file_backed_mapping(mapping: &mut FileBackedMappingParameters) -> Result<(), String> {
1241    let pagesize_mask = pagesize() as u64 - 1;
1242    let aligned_address = mapping.address & !pagesize_mask;
1243    let aligned_size =
1244        ((mapping.address + mapping.size + pagesize_mask) & !pagesize_mask) - aligned_address;
1245
1246    if mapping.align {
1247        mapping.address = aligned_address;
1248        mapping.size = aligned_size;
1249    } else if aligned_address != mapping.address || aligned_size != mapping.size {
1250        return Err(
1251            "--file-backed-mapping addr and size parameters must be page size aligned".to_string(),
1252        );
1253    }
1254
1255    Ok(())
1256}
1257
1258fn validate_pmem(pmem: &PmemOption) -> Result<(), String> {
1259    if (pmem.swap_interval.is_some() && pmem.vma_size.is_none())
1260        || (pmem.swap_interval.is_none() && pmem.vma_size.is_some())
1261    {
1262        return Err(
1263            "--pmem vma-size and swap-interval parameters must be specified together".to_string(),
1264        );
1265    }
1266
1267    if pmem.ro && pmem.swap_interval.is_some() {
1268        return Err(
1269            "--pmem swap-interval parameter can only be set for writable pmem device".to_string(),
1270        );
1271    }
1272
1273    Ok(())
1274}
1275
1276#[cfg(test)]
1277#[allow(clippy::needless_update)]
1278mod tests {
1279    use argh::FromArgs;
1280    use devices::PciClassCode;
1281    use devices::StubPciParameters;
1282    #[cfg(target_arch = "x86_64")]
1283    use uuid::uuid;
1284
1285    use super::*;
1286
1287    fn config_from_args(args: &[&str]) -> Config {
1288        crate::crosvm::cmdline::RunCommand::from_args(&[], args)
1289            .unwrap()
1290            .try_into()
1291            .unwrap()
1292    }
1293
1294    #[test]
1295    fn parse_cpu_opts() {
1296        let res: CpuOptions = from_key_values("").unwrap();
1297        assert_eq!(res, CpuOptions::default());
1298
1299        // num_cores
1300        let res: CpuOptions = from_key_values("12").unwrap();
1301        assert_eq!(
1302            res,
1303            CpuOptions {
1304                num_cores: Some(12),
1305                ..Default::default()
1306            }
1307        );
1308
1309        let res: CpuOptions = from_key_values("num-cores=16").unwrap();
1310        assert_eq!(
1311            res,
1312            CpuOptions {
1313                num_cores: Some(16),
1314                ..Default::default()
1315            }
1316        );
1317
1318        // clusters
1319        let res: CpuOptions = from_key_values("clusters=[[0],[1],[2],[3]]").unwrap();
1320        assert_eq!(
1321            res,
1322            CpuOptions {
1323                clusters: vec![
1324                    CpuSet::new([0]),
1325                    CpuSet::new([1]),
1326                    CpuSet::new([2]),
1327                    CpuSet::new([3])
1328                ],
1329                ..Default::default()
1330            }
1331        );
1332
1333        let res: CpuOptions = from_key_values("clusters=[[0-3]]").unwrap();
1334        assert_eq!(
1335            res,
1336            CpuOptions {
1337                clusters: vec![CpuSet::new([0, 1, 2, 3])],
1338                ..Default::default()
1339            }
1340        );
1341
1342        let res: CpuOptions = from_key_values("clusters=[[0,2],[1,3],[4-7,12]]").unwrap();
1343        assert_eq!(
1344            res,
1345            CpuOptions {
1346                clusters: vec![
1347                    CpuSet::new([0, 2]),
1348                    CpuSet::new([1, 3]),
1349                    CpuSet::new([4, 5, 6, 7, 12])
1350                ],
1351                ..Default::default()
1352            }
1353        );
1354
1355        #[cfg(target_arch = "x86_64")]
1356        {
1357            let res: CpuOptions = from_key_values("core-types=[atom=[1,3-7],core=[0,2]]").unwrap();
1358            assert_eq!(
1359                res,
1360                CpuOptions {
1361                    core_types: Some(CpuCoreType {
1362                        atom: CpuSet::new([1, 3, 4, 5, 6, 7]),
1363                        core: CpuSet::new([0, 2])
1364                    }),
1365                    ..Default::default()
1366                }
1367            );
1368        }
1369
1370        // All together
1371        let res: CpuOptions = from_key_values("16,clusters=[[0],[4-6],[7]]").unwrap();
1372        assert_eq!(
1373            res,
1374            CpuOptions {
1375                num_cores: Some(16),
1376                clusters: vec![CpuSet::new([0]), CpuSet::new([4, 5, 6]), CpuSet::new([7])],
1377                ..Default::default()
1378            }
1379        );
1380
1381        let res: CpuOptions = from_key_values("clusters=[[0-7],[30-31]],num-cores=32").unwrap();
1382        assert_eq!(
1383            res,
1384            CpuOptions {
1385                num_cores: Some(32),
1386                clusters: vec![CpuSet::new([0, 1, 2, 3, 4, 5, 6, 7]), CpuSet::new([30, 31])],
1387                ..Default::default()
1388            }
1389        );
1390    }
1391
1392    #[test]
1393    fn parse_cpu_set_single() {
1394        assert_eq!(
1395            CpuSet::from_str("123").expect("parse failed"),
1396            CpuSet::new([123])
1397        );
1398    }
1399
1400    #[test]
1401    fn parse_cpu_set_list() {
1402        assert_eq!(
1403            CpuSet::from_str("0,1,2,3").expect("parse failed"),
1404            CpuSet::new([0, 1, 2, 3])
1405        );
1406    }
1407
1408    #[test]
1409    fn parse_cpu_set_range() {
1410        assert_eq!(
1411            CpuSet::from_str("0-3").expect("parse failed"),
1412            CpuSet::new([0, 1, 2, 3])
1413        );
1414    }
1415
1416    #[test]
1417    fn parse_cpu_set_list_of_ranges() {
1418        assert_eq!(
1419            CpuSet::from_str("3-4,7-9,18").expect("parse failed"),
1420            CpuSet::new([3, 4, 7, 8, 9, 18])
1421        );
1422    }
1423
1424    #[test]
1425    fn parse_cpu_set_repeated() {
1426        // For now, allow duplicates - they will be handled gracefully by the vec to cpu_set_t
1427        // conversion.
1428        assert_eq!(
1429            CpuSet::from_str("1,1,1").expect("parse failed"),
1430            CpuSet::new([1, 1, 1])
1431        );
1432    }
1433
1434    #[test]
1435    fn parse_cpu_set_negative() {
1436        // Negative CPU numbers are not allowed.
1437        CpuSet::from_str("-3").expect_err("parse should have failed");
1438    }
1439
1440    #[test]
1441    fn parse_cpu_set_reverse_range() {
1442        // Ranges must be from low to high.
1443        CpuSet::from_str("5-2").expect_err("parse should have failed");
1444    }
1445
1446    #[test]
1447    fn parse_cpu_set_open_range() {
1448        CpuSet::from_str("3-").expect_err("parse should have failed");
1449    }
1450
1451    #[test]
1452    fn parse_cpu_set_extra_comma() {
1453        CpuSet::from_str("0,1,2,").expect_err("parse should have failed");
1454    }
1455
1456    #[test]
1457    fn parse_cpu_affinity_global() {
1458        assert_eq!(
1459            parse_cpu_affinity("0,5-7,9").expect("parse failed"),
1460            VcpuAffinity::Global(CpuSet::new([0, 5, 6, 7, 9])),
1461        );
1462    }
1463
1464    #[test]
1465    fn parse_cpu_affinity_per_vcpu_one_to_one() {
1466        let mut expected_map = BTreeMap::new();
1467        expected_map.insert(0, CpuSet::new([0]));
1468        expected_map.insert(1, CpuSet::new([1]));
1469        expected_map.insert(2, CpuSet::new([2]));
1470        expected_map.insert(3, CpuSet::new([3]));
1471        assert_eq!(
1472            parse_cpu_affinity("0=0:1=1:2=2:3=3").expect("parse failed"),
1473            VcpuAffinity::PerVcpu(expected_map),
1474        );
1475    }
1476
1477    #[test]
1478    fn parse_cpu_affinity_per_vcpu_sets() {
1479        let mut expected_map = BTreeMap::new();
1480        expected_map.insert(0, CpuSet::new([0, 1, 2]));
1481        expected_map.insert(1, CpuSet::new([3, 4, 5]));
1482        expected_map.insert(2, CpuSet::new([6, 7, 8]));
1483        assert_eq!(
1484            parse_cpu_affinity("0=0,1,2:1=3-5:2=6,7-8").expect("parse failed"),
1485            VcpuAffinity::PerVcpu(expected_map),
1486        );
1487    }
1488
1489    #[test]
1490    fn parse_mem_opts() {
1491        let res: MemOptions = from_key_values("").unwrap();
1492        assert_eq!(res.size, None);
1493
1494        let res: MemOptions = from_key_values("1024").unwrap();
1495        assert_eq!(res.size, Some(1024));
1496
1497        let res: MemOptions = from_key_values("size=0x4000").unwrap();
1498        assert_eq!(res.size, Some(16384));
1499    }
1500
1501    #[test]
1502    fn parse_serial_vaild() {
1503        parse_serial_options("type=syslog,num=1,console=true,stdin=true")
1504            .expect("parse should have succeded");
1505    }
1506
1507    #[test]
1508    fn parse_serial_virtio_console_vaild() {
1509        parse_serial_options("type=syslog,num=5,console=true,stdin=true,hardware=virtio-console")
1510            .expect("parse should have succeded");
1511    }
1512
1513    #[test]
1514    fn parse_serial_valid_no_num() {
1515        parse_serial_options("type=syslog").expect("parse should have succeded");
1516    }
1517
1518    #[test]
1519    fn parse_serial_equals_in_value() {
1520        let parsed = parse_serial_options("type=syslog,path=foo=bar==.log")
1521            .expect("parse should have succeded");
1522        assert_eq!(parsed.path, Some(PathBuf::from("foo=bar==.log")));
1523    }
1524
1525    #[test]
1526    fn parse_serial_invalid_type() {
1527        parse_serial_options("type=wormhole,num=1").expect_err("parse should have failed");
1528    }
1529
1530    #[test]
1531    fn parse_serial_invalid_num_upper() {
1532        parse_serial_options("type=syslog,num=5").expect_err("parse should have failed");
1533    }
1534
1535    #[test]
1536    fn parse_serial_invalid_num_lower() {
1537        parse_serial_options("type=syslog,num=0").expect_err("parse should have failed");
1538    }
1539
1540    #[test]
1541    fn parse_serial_virtio_console_invalid_num_lower() {
1542        parse_serial_options("type=syslog,hardware=virtio-console,num=0")
1543            .expect_err("parse should have failed");
1544    }
1545
1546    #[test]
1547    fn parse_serial_invalid_num_string() {
1548        parse_serial_options("type=syslog,num=number3").expect_err("parse should have failed");
1549    }
1550
1551    #[test]
1552    fn parse_serial_invalid_option() {
1553        parse_serial_options("type=syslog,speed=lightspeed").expect_err("parse should have failed");
1554    }
1555
1556    #[test]
1557    fn parse_serial_invalid_two_stdin() {
1558        assert!(TryInto::<Config>::try_into(
1559            crate::crosvm::cmdline::RunCommand::from_args(
1560                &[],
1561                &[
1562                    "--serial",
1563                    "num=1,type=stdout,stdin=true",
1564                    "--serial",
1565                    "num=2,type=stdout,stdin=true"
1566                ]
1567            )
1568            .unwrap()
1569        )
1570        .is_err())
1571    }
1572
1573    #[test]
1574    fn parse_serial_pci_address_valid_for_virtio() {
1575        let parsed =
1576            parse_serial_options("type=syslog,hardware=virtio-console,pci-address=00:0e.0")
1577                .expect("parse should have succeded");
1578        assert_eq!(
1579            parsed.pci_address,
1580            Some(PciAddress {
1581                bus: 0,
1582                dev: 14,
1583                func: 0
1584            })
1585        );
1586    }
1587
1588    #[test]
1589    fn parse_serial_pci_address_valid_for_legacy_virtio() {
1590        let parsed =
1591            parse_serial_options("type=syslog,hardware=legacy-virtio-console,pci-address=00:0e.0")
1592                .expect("parse should have succeded");
1593        assert_eq!(
1594            parsed.pci_address,
1595            Some(PciAddress {
1596                bus: 0,
1597                dev: 14,
1598                func: 0
1599            })
1600        );
1601    }
1602
1603    #[test]
1604    fn parse_serial_pci_address_failed_for_serial() {
1605        parse_serial_options("type=syslog,hardware=serial,pci-address=00:0e.0")
1606            .expect_err("expected pci-address error for serial hardware");
1607    }
1608
1609    #[test]
1610    fn parse_serial_pci_address_failed_for_debugcon() {
1611        parse_serial_options("type=syslog,hardware=debugcon,pci-address=00:0e.0")
1612            .expect_err("expected pci-address error for debugcon hardware");
1613    }
1614
1615    #[test]
1616    fn parse_battery_valid() {
1617        let bat_config: BatteryConfig = from_key_values("type=goldfish").unwrap();
1618        assert_eq!(bat_config.type_, BatteryType::Goldfish);
1619    }
1620
1621    #[test]
1622    fn parse_battery_valid_no_type() {
1623        let bat_config: BatteryConfig = from_key_values("").unwrap();
1624        assert_eq!(bat_config.type_, BatteryType::Goldfish);
1625    }
1626
1627    #[test]
1628    fn parse_battery_invalid_parameter() {
1629        from_key_values::<BatteryConfig>("tyep=goldfish").expect_err("parse should have failed");
1630    }
1631
1632    #[test]
1633    fn parse_battery_invalid_type_value() {
1634        from_key_values::<BatteryConfig>("type=xxx").expect_err("parse should have failed");
1635    }
1636
1637    #[test]
1638    fn parse_irqchip_kernel() {
1639        let cfg = TryInto::<Config>::try_into(
1640            crate::crosvm::cmdline::RunCommand::from_args(
1641                &[],
1642                &["--irqchip", "kernel", "/dev/null"],
1643            )
1644            .unwrap(),
1645        )
1646        .unwrap();
1647
1648        assert_eq!(
1649            cfg.irq_chip,
1650            Some(IrqChipKind::Kernel {
1651                #[cfg(target_arch = "aarch64")]
1652                allow_vgic_its: false
1653            })
1654        );
1655    }
1656
1657    #[test]
1658    #[cfg(target_arch = "aarch64")]
1659    fn parse_irqchip_kernel_with_its() {
1660        let cfg = TryInto::<Config>::try_into(
1661            crate::crosvm::cmdline::RunCommand::from_args(
1662                &[],
1663                &["--irqchip", "kernel[allow-vgic-its]", "/dev/null"],
1664            )
1665            .unwrap(),
1666        )
1667        .unwrap();
1668
1669        assert_eq!(
1670            cfg.irq_chip,
1671            Some(IrqChipKind::Kernel {
1672                allow_vgic_its: true
1673            })
1674        );
1675    }
1676
1677    #[test]
1678    fn parse_irqchip_split() {
1679        let cfg = TryInto::<Config>::try_into(
1680            crate::crosvm::cmdline::RunCommand::from_args(
1681                &[],
1682                &["--irqchip", "split", "/dev/null"],
1683            )
1684            .unwrap(),
1685        )
1686        .unwrap();
1687
1688        assert_eq!(cfg.irq_chip, Some(IrqChipKind::Split));
1689    }
1690
1691    #[test]
1692    fn parse_irqchip_userspace() {
1693        let cfg = TryInto::<Config>::try_into(
1694            crate::crosvm::cmdline::RunCommand::from_args(
1695                &[],
1696                &["--irqchip", "userspace", "/dev/null"],
1697            )
1698            .unwrap(),
1699        )
1700        .unwrap();
1701
1702        assert_eq!(cfg.irq_chip, Some(IrqChipKind::Userspace));
1703    }
1704
1705    #[test]
1706    fn parse_stub_pci() {
1707        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();
1708        assert_eq!(params.address.bus, 1);
1709        assert_eq!(params.address.dev, 2);
1710        assert_eq!(params.address.func, 3);
1711        assert_eq!(params.vendor, 0xfffe);
1712        assert_eq!(params.device, 0xfffd);
1713        assert_eq!(params.class.class as u8, PciClassCode::Other as u8);
1714        assert_eq!(params.class.subclass, 0xc1);
1715        assert_eq!(params.class.programming_interface, 0xc2);
1716        assert_eq!(params.subsystem_vendor, 0xfffc);
1717        assert_eq!(params.subsystem_device, 0xfffb);
1718        assert_eq!(params.revision, 0xa);
1719    }
1720
1721    #[test]
1722    fn parse_file_backed_mapping_valid() {
1723        let params = from_key_values::<FileBackedMappingParameters>(
1724            "addr=0x1000,size=0x2000,path=/dev/mem,offset=0x3000,rw,sync",
1725        )
1726        .unwrap();
1727        assert_eq!(params.address, 0x1000);
1728        assert_eq!(params.size, 0x2000);
1729        assert_eq!(params.path, PathBuf::from("/dev/mem"));
1730        assert_eq!(params.offset, 0x3000);
1731        assert!(params.writable);
1732        assert!(params.sync);
1733    }
1734
1735    #[test]
1736    fn parse_file_backed_mapping_incomplete() {
1737        assert!(
1738            from_key_values::<FileBackedMappingParameters>("addr=0x1000,size=0x2000")
1739                .unwrap_err()
1740                .contains("missing field `path`")
1741        );
1742        assert!(
1743            from_key_values::<FileBackedMappingParameters>("size=0x2000,path=/dev/mem")
1744                .unwrap_err()
1745                .contains("missing field `addr`")
1746        );
1747        assert!(
1748            from_key_values::<FileBackedMappingParameters>("addr=0x1000,path=/dev/mem")
1749                .unwrap_err()
1750                .contains("missing field `size`")
1751        );
1752    }
1753
1754    #[test]
1755    fn parse_file_backed_mapping_unaligned_addr() {
1756        let mut params =
1757            from_key_values::<FileBackedMappingParameters>("addr=0x1001,size=0x2000,path=/dev/mem")
1758                .unwrap();
1759        assert!(validate_file_backed_mapping(&mut params)
1760            .unwrap_err()
1761            .contains("aligned"));
1762    }
1763    #[test]
1764    fn parse_file_backed_mapping_unaligned_size() {
1765        let mut params =
1766            from_key_values::<FileBackedMappingParameters>("addr=0x1000,size=0x2001,path=/dev/mem")
1767                .unwrap();
1768        assert!(validate_file_backed_mapping(&mut params)
1769            .unwrap_err()
1770            .contains("aligned"));
1771    }
1772
1773    #[test]
1774    fn parse_file_backed_mapping_align() {
1775        let addr = pagesize() as u64 * 3 + 42;
1776        let size = pagesize() as u64 - 0xf;
1777        let mut params = from_key_values::<FileBackedMappingParameters>(&format!(
1778            "addr={addr},size={size},path=/dev/mem,align",
1779        ))
1780        .unwrap();
1781        assert_eq!(params.address, addr);
1782        assert_eq!(params.size, size);
1783        validate_file_backed_mapping(&mut params).unwrap();
1784        assert_eq!(params.address, pagesize() as u64 * 3);
1785        assert_eq!(params.size, pagesize() as u64 * 2);
1786    }
1787
1788    #[test]
1789    fn parse_fw_cfg_valid_path() {
1790        let cfg = TryInto::<Config>::try_into(
1791            crate::crosvm::cmdline::RunCommand::from_args(
1792                &[],
1793                &["--fw-cfg", "name=bar,path=data.bin", "/dev/null"],
1794            )
1795            .unwrap(),
1796        )
1797        .unwrap();
1798
1799        assert_eq!(cfg.fw_cfg_parameters.len(), 1);
1800        assert_eq!(cfg.fw_cfg_parameters[0].name, "bar".to_string());
1801        assert_eq!(cfg.fw_cfg_parameters[0].string, None);
1802        assert_eq!(cfg.fw_cfg_parameters[0].path, Some("data.bin".into()));
1803    }
1804
1805    #[test]
1806    fn parse_fw_cfg_valid_string() {
1807        let cfg = TryInto::<Config>::try_into(
1808            crate::crosvm::cmdline::RunCommand::from_args(
1809                &[],
1810                &["--fw-cfg", "name=bar,string=foo", "/dev/null"],
1811            )
1812            .unwrap(),
1813        )
1814        .unwrap();
1815
1816        assert_eq!(cfg.fw_cfg_parameters.len(), 1);
1817        assert_eq!(cfg.fw_cfg_parameters[0].name, "bar".to_string());
1818        assert_eq!(cfg.fw_cfg_parameters[0].string, Some("foo".to_string()));
1819        assert_eq!(cfg.fw_cfg_parameters[0].path, None);
1820    }
1821
1822    #[test]
1823    fn parse_dtbo() {
1824        let cfg: Config = crate::crosvm::cmdline::RunCommand::from_args(
1825            &[],
1826            &[
1827                "--device-tree-overlay",
1828                "/path/to/dtbo1",
1829                "--device-tree-overlay",
1830                "/path/to/dtbo2",
1831                "/dev/null",
1832            ],
1833        )
1834        .unwrap()
1835        .try_into()
1836        .unwrap();
1837
1838        assert_eq!(cfg.device_tree_overlay.len(), 2);
1839        for (opt, p) in cfg
1840            .device_tree_overlay
1841            .into_iter()
1842            .zip(["/path/to/dtbo1", "/path/to/dtbo2"])
1843        {
1844            assert_eq!(opt.path, PathBuf::from(p));
1845            assert!(!opt.filter_devs);
1846        }
1847    }
1848
1849    #[test]
1850    #[cfg(any(target_os = "android", target_os = "linux"))]
1851    fn parse_dtbo_filtered() {
1852        let cfg: Config = crate::crosvm::cmdline::RunCommand::from_args(
1853            &[],
1854            &[
1855                "--vfio",
1856                "/path/to/dev,dt-symbol=mydev",
1857                "--device-tree-overlay",
1858                "/path/to/dtbo1,filter",
1859                "--device-tree-overlay",
1860                "/path/to/dtbo2,filter",
1861                "/dev/null",
1862            ],
1863        )
1864        .unwrap()
1865        .try_into()
1866        .unwrap();
1867
1868        assert_eq!(cfg.device_tree_overlay.len(), 2);
1869        for (opt, p) in cfg
1870            .device_tree_overlay
1871            .into_iter()
1872            .zip(["/path/to/dtbo1", "/path/to/dtbo2"])
1873        {
1874            assert_eq!(opt.path, PathBuf::from(p));
1875            assert!(opt.filter_devs);
1876        }
1877
1878        assert!(TryInto::<Config>::try_into(
1879            crate::crosvm::cmdline::RunCommand::from_args(
1880                &[],
1881                &["--device-tree-overlay", "/path/to/dtbo,filter", "/dev/null"],
1882            )
1883            .unwrap(),
1884        )
1885        .is_err());
1886    }
1887
1888    #[test]
1889    fn parse_fw_cfg_invalid_no_name() {
1890        assert!(
1891            crate::crosvm::cmdline::RunCommand::from_args(&[], &["--fw-cfg", "string=foo",])
1892                .is_err()
1893        );
1894    }
1895
1896    #[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
1897    #[test]
1898    fn parse_video() {
1899        use devices::virtio::device_constants::video::VideoBackendType;
1900
1901        #[cfg(feature = "libvda")]
1902        {
1903            let params: VideoDeviceConfig = from_key_values("libvda").unwrap();
1904            assert_eq!(params.backend, VideoBackendType::Libvda);
1905
1906            let params: VideoDeviceConfig = from_key_values("libvda-vd").unwrap();
1907            assert_eq!(params.backend, VideoBackendType::LibvdaVd);
1908        }
1909
1910        #[cfg(feature = "ffmpeg")]
1911        {
1912            let params: VideoDeviceConfig = from_key_values("ffmpeg").unwrap();
1913            assert_eq!(params.backend, VideoBackendType::Ffmpeg);
1914        }
1915
1916        #[cfg(feature = "vaapi")]
1917        {
1918            let params: VideoDeviceConfig = from_key_values("vaapi").unwrap();
1919            assert_eq!(params.backend, VideoBackendType::Vaapi);
1920        }
1921    }
1922
1923    #[test]
1924    fn parse_vhost_user_option_all_device_types() {
1925        fn test_device_type(type_string: &str, type_: DeviceType) {
1926            let vhost_user_arg = format!("{type_string},socket=sock");
1927
1928            let cfg = TryInto::<Config>::try_into(
1929                crate::crosvm::cmdline::RunCommand::from_args(
1930                    &[],
1931                    &["--vhost-user", &vhost_user_arg, "/dev/null"],
1932                )
1933                .unwrap(),
1934            )
1935            .unwrap();
1936
1937            assert_eq!(cfg.vhost_user.len(), 1);
1938            let vu = &cfg.vhost_user[0];
1939            assert_eq!(vu.type_, type_);
1940        }
1941
1942        test_device_type("net", DeviceType::Net);
1943        test_device_type("block", DeviceType::Block);
1944        test_device_type("console", DeviceType::Console);
1945        test_device_type("rng", DeviceType::Rng);
1946        test_device_type("balloon", DeviceType::Balloon);
1947        test_device_type("scsi", DeviceType::Scsi);
1948        test_device_type("9p", DeviceType::P9);
1949        test_device_type("gpu", DeviceType::Gpu);
1950        test_device_type("input", DeviceType::Input);
1951        test_device_type("vsock", DeviceType::Vsock);
1952        test_device_type("iommu", DeviceType::Iommu);
1953        test_device_type("sound", DeviceType::Sound);
1954        test_device_type("fs", DeviceType::Fs);
1955        test_device_type("pmem", DeviceType::Pmem);
1956        test_device_type("mac80211-hwsim", DeviceType::Mac80211HwSim);
1957        test_device_type("video-encoder", DeviceType::VideoEncoder);
1958        test_device_type("video-decoder", DeviceType::VideoDecoder);
1959        test_device_type("scmi", DeviceType::Scmi);
1960        test_device_type("wl", DeviceType::Wl);
1961        test_device_type("tpm", DeviceType::Tpm);
1962        test_device_type("pvclock", DeviceType::Pvclock);
1963    }
1964
1965    #[cfg(target_arch = "x86_64")]
1966    #[test]
1967    fn parse_smbios_uuid() {
1968        let opt: SmbiosOptions =
1969            from_key_values("uuid=12e474af-2cc1-49d1-b0e5-d03a3e03ca03").unwrap();
1970        assert_eq!(
1971            opt.uuid,
1972            Some(uuid!("12e474af-2cc1-49d1-b0e5-d03a3e03ca03"))
1973        );
1974
1975        from_key_values::<SmbiosOptions>("uuid=zzzz").expect_err("expected error parsing uuid");
1976    }
1977
1978    #[test]
1979    fn parse_touch_legacy() {
1980        let cfg = TryInto::<Config>::try_into(
1981            crate::crosvm::cmdline::RunCommand::from_args(
1982                &[],
1983                &["--multi-touch", "my_socket:867:5309", "bzImage"],
1984            )
1985            .unwrap(),
1986        )
1987        .unwrap();
1988
1989        assert_eq!(cfg.virtio_input.len(), 1);
1990        let multi_touch = cfg
1991            .virtio_input
1992            .iter()
1993            .find(|input| matches!(input, InputDeviceOption::MultiTouch { .. }))
1994            .unwrap();
1995        assert_eq!(
1996            *multi_touch,
1997            InputDeviceOption::MultiTouch {
1998                path: PathBuf::from("my_socket"),
1999                width: Some(867),
2000                height: Some(5309),
2001                name: None
2002            }
2003        );
2004    }
2005
2006    #[test]
2007    fn parse_touch() {
2008        let cfg = TryInto::<Config>::try_into(
2009            crate::crosvm::cmdline::RunCommand::from_args(
2010                &[],
2011                &["--multi-touch", r"C:\path,width=867,height=5309", "bzImage"],
2012            )
2013            .unwrap(),
2014        )
2015        .unwrap();
2016
2017        assert_eq!(cfg.virtio_input.len(), 1);
2018        let multi_touch = cfg
2019            .virtio_input
2020            .iter()
2021            .find(|input| matches!(input, InputDeviceOption::MultiTouch { .. }))
2022            .unwrap();
2023        assert_eq!(
2024            *multi_touch,
2025            InputDeviceOption::MultiTouch {
2026                path: PathBuf::from(r"C:\path"),
2027                width: Some(867),
2028                height: Some(5309),
2029                name: None
2030            }
2031        );
2032    }
2033
2034    #[test]
2035    fn single_touch_spec_and_track_pad_spec_default_size() {
2036        let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2037            &[],
2038            &[
2039                "--single-touch",
2040                "/dev/single-touch-test",
2041                "--trackpad",
2042                "/dev/single-touch-test",
2043                "/dev/null",
2044            ],
2045        )
2046        .unwrap()
2047        .try_into()
2048        .unwrap();
2049
2050        let single_touch = config
2051            .virtio_input
2052            .iter()
2053            .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2054            .unwrap();
2055        let trackpad = config
2056            .virtio_input
2057            .iter()
2058            .find(|input| matches!(input, InputDeviceOption::Trackpad { .. }))
2059            .unwrap();
2060
2061        assert_eq!(
2062            *single_touch,
2063            InputDeviceOption::SingleTouch {
2064                path: PathBuf::from("/dev/single-touch-test"),
2065                width: None,
2066                height: None,
2067                name: None
2068            }
2069        );
2070        assert_eq!(
2071            *trackpad,
2072            InputDeviceOption::Trackpad {
2073                path: PathBuf::from("/dev/single-touch-test"),
2074                width: None,
2075                height: None,
2076                name: None
2077            }
2078        );
2079    }
2080
2081    #[cfg(feature = "gpu")]
2082    #[test]
2083    fn single_touch_spec_default_size_from_gpu() {
2084        let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2085            &[],
2086            &[
2087                "--single-touch",
2088                "/dev/single-touch-test",
2089                "--gpu",
2090                "width=1024,height=768",
2091                "/dev/null",
2092            ],
2093        )
2094        .unwrap()
2095        .try_into()
2096        .unwrap();
2097
2098        let single_touch = config
2099            .virtio_input
2100            .iter()
2101            .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2102            .unwrap();
2103        assert_eq!(
2104            *single_touch,
2105            InputDeviceOption::SingleTouch {
2106                path: PathBuf::from("/dev/single-touch-test"),
2107                width: None,
2108                height: None,
2109                name: None
2110            }
2111        );
2112
2113        assert_eq!(config.display_input_width, Some(1024));
2114        assert_eq!(config.display_input_height, Some(768));
2115    }
2116
2117    #[test]
2118    fn single_touch_spec_and_track_pad_spec_with_size() {
2119        let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2120            &[],
2121            &[
2122                "--single-touch",
2123                "/dev/single-touch-test:12345:54321",
2124                "--trackpad",
2125                "/dev/single-touch-test:5678:9876",
2126                "/dev/null",
2127            ],
2128        )
2129        .unwrap()
2130        .try_into()
2131        .unwrap();
2132
2133        let single_touch = config
2134            .virtio_input
2135            .iter()
2136            .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2137            .unwrap();
2138        let trackpad = config
2139            .virtio_input
2140            .iter()
2141            .find(|input| matches!(input, InputDeviceOption::Trackpad { .. }))
2142            .unwrap();
2143
2144        assert_eq!(
2145            *single_touch,
2146            InputDeviceOption::SingleTouch {
2147                path: PathBuf::from("/dev/single-touch-test"),
2148                width: Some(12345),
2149                height: Some(54321),
2150                name: None
2151            }
2152        );
2153        assert_eq!(
2154            *trackpad,
2155            InputDeviceOption::Trackpad {
2156                path: PathBuf::from("/dev/single-touch-test"),
2157                width: Some(5678),
2158                height: Some(9876),
2159                name: None
2160            }
2161        );
2162    }
2163
2164    #[cfg(feature = "gpu")]
2165    #[test]
2166    fn single_touch_spec_with_size_independent_from_gpu() {
2167        let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2168            &[],
2169            &[
2170                "--single-touch",
2171                "/dev/single-touch-test:12345:54321",
2172                "--gpu",
2173                "width=1024,height=768",
2174                "/dev/null",
2175            ],
2176        )
2177        .unwrap()
2178        .try_into()
2179        .unwrap();
2180
2181        let single_touch = config
2182            .virtio_input
2183            .iter()
2184            .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2185            .unwrap();
2186
2187        assert_eq!(
2188            *single_touch,
2189            InputDeviceOption::SingleTouch {
2190                path: PathBuf::from("/dev/single-touch-test"),
2191                width: Some(12345),
2192                height: Some(54321),
2193                name: None
2194            }
2195        );
2196
2197        assert_eq!(config.display_input_width, Some(1024));
2198        assert_eq!(config.display_input_height, Some(768));
2199    }
2200
2201    #[test]
2202    fn virtio_switches() {
2203        let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2204            &[],
2205            &["--switches", "/dev/switches-test", "/dev/null"],
2206        )
2207        .unwrap()
2208        .try_into()
2209        .unwrap();
2210
2211        let switches = config
2212            .virtio_input
2213            .iter()
2214            .find(|input| matches!(input, InputDeviceOption::Switches { .. }))
2215            .unwrap();
2216
2217        assert_eq!(
2218            *switches,
2219            InputDeviceOption::Switches {
2220                path: PathBuf::from("/dev/switches-test")
2221            }
2222        );
2223    }
2224
2225    #[test]
2226    fn virtio_rotary() {
2227        let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2228            &[],
2229            &["--rotary", "/dev/rotary-test", "/dev/null"],
2230        )
2231        .unwrap()
2232        .try_into()
2233        .unwrap();
2234
2235        let rotary = config
2236            .virtio_input
2237            .iter()
2238            .find(|input| matches!(input, InputDeviceOption::Rotary { .. }))
2239            .unwrap();
2240
2241        assert_eq!(
2242            *rotary,
2243            InputDeviceOption::Rotary {
2244                path: PathBuf::from("/dev/rotary-test")
2245            }
2246        );
2247    }
2248
2249    #[cfg(target_arch = "aarch64")]
2250    #[test]
2251    fn parse_pci_cam() {
2252        assert_eq!(
2253            config_from_args(&["--pci", "cam=[start=0x123]", "/dev/null"]).pci_config,
2254            PciConfig {
2255                cam: Some(arch::MemoryRegionConfig {
2256                    start: 0x123,
2257                    size: None,
2258                }),
2259                ..PciConfig::default()
2260            }
2261        );
2262        assert_eq!(
2263            config_from_args(&["--pci", "cam=[start=0x123,size=0x456]", "/dev/null"]).pci_config,
2264            PciConfig {
2265                cam: Some(arch::MemoryRegionConfig {
2266                    start: 0x123,
2267                    size: Some(0x456),
2268                }),
2269                ..PciConfig::default()
2270            },
2271        );
2272    }
2273
2274    #[cfg(target_arch = "x86_64")]
2275    #[test]
2276    fn parse_pci_ecam() {
2277        assert_eq!(
2278            config_from_args(&["--pci", "ecam=[start=0x123]", "/dev/null"]).pci_config,
2279            PciConfig {
2280                ecam: Some(arch::MemoryRegionConfig {
2281                    start: 0x123,
2282                    size: None,
2283                }),
2284                ..PciConfig::default()
2285            }
2286        );
2287        assert_eq!(
2288            config_from_args(&["--pci", "ecam=[start=0x123,size=0x456]", "/dev/null"]).pci_config,
2289            PciConfig {
2290                ecam: Some(arch::MemoryRegionConfig {
2291                    start: 0x123,
2292                    size: Some(0x456),
2293                }),
2294                ..PciConfig::default()
2295            },
2296        );
2297    }
2298
2299    #[test]
2300    fn parse_pci_mem() {
2301        assert_eq!(
2302            config_from_args(&["--pci", "mem=[start=0x123]", "/dev/null"]).pci_config,
2303            PciConfig {
2304                mem: Some(arch::MemoryRegionConfig {
2305                    start: 0x123,
2306                    size: None,
2307                }),
2308                ..PciConfig::default()
2309            }
2310        );
2311        assert_eq!(
2312            config_from_args(&["--pci", "mem=[start=0x123,size=0x456]", "/dev/null"]).pci_config,
2313            PciConfig {
2314                mem: Some(arch::MemoryRegionConfig {
2315                    start: 0x123,
2316                    size: Some(0x456),
2317                }),
2318                ..PciConfig::default()
2319            },
2320        );
2321    }
2322
2323    #[test]
2324    fn parse_pmem_options_missing_path() {
2325        assert!(from_key_values::<PmemOption>("")
2326            .unwrap_err()
2327            .contains("missing field `path`"));
2328    }
2329
2330    #[test]
2331    fn parse_pmem_options_default_values() {
2332        let pmem = from_key_values::<PmemOption>("/path/to/disk.img").unwrap();
2333        assert_eq!(
2334            pmem,
2335            PmemOption {
2336                path: "/path/to/disk.img".into(),
2337                ro: false,
2338                root: false,
2339                vma_size: None,
2340                swap_interval: None,
2341            }
2342        );
2343    }
2344
2345    #[test]
2346    fn parse_pmem_options_virtual_swap() {
2347        let pmem =
2348            from_key_values::<PmemOption>("virtual_path,vma-size=12345,swap-interval-ms=1000")
2349                .unwrap();
2350        assert_eq!(
2351            pmem,
2352            PmemOption {
2353                path: "virtual_path".into(),
2354                ro: false,
2355                root: false,
2356                vma_size: Some(12345),
2357                swap_interval: Some(Duration::new(1, 0)),
2358            }
2359        );
2360    }
2361
2362    #[test]
2363    fn validate_pmem_missing_virtual_swap_param() {
2364        let pmem = from_key_values::<PmemOption>("virtual_path,swap-interval-ms=1000").unwrap();
2365        assert!(validate_pmem(&pmem)
2366            .unwrap_err()
2367            .contains("vma-size and swap-interval parameters must be specified together"));
2368    }
2369
2370    #[test]
2371    fn validate_pmem_read_only_virtual_swap() {
2372        let pmem = from_key_values::<PmemOption>(
2373            "virtual_path,ro=true,vma-size=12345,swap-interval-ms=1000",
2374        )
2375        .unwrap();
2376        assert!(validate_pmem(&pmem)
2377            .unwrap_err()
2378            .contains("swap-interval parameter can only be set for writable pmem device"));
2379    }
2380}