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