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