crosvm/crosvm/
config.rs

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