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