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