crosvm/crosvm/
config.rs

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