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