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