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