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