1#[cfg(target_arch = "x86_64")]
6use std::arch::x86_64::__cpuid;
7#[cfg(target_arch = "x86_64")]
8use std::arch::x86_64::__cpuid_count;
9use std::collections::BTreeMap;
10use std::path::PathBuf;
11use std::str::FromStr;
12use std::time::Duration;
13
14use arch::set_default_serial_parameters;
15use arch::CpuSet;
16use arch::DevicePowerManagerConfig;
17use arch::FdtPosition;
18#[cfg(all(target_os = "android", target_arch = "aarch64"))]
19use arch::FfaConfig;
20use arch::PciConfig;
21use arch::Pstore;
22#[cfg(target_arch = "x86_64")]
23use arch::SmbiosOptions;
24#[cfg(target_arch = "aarch64")]
25use arch::SveConfig;
26use arch::VcpuAffinity;
27use base::debug;
28use base::pagesize;
29use cros_async::ExecutorKind;
30use devices::serial_device::SerialHardware;
31use devices::serial_device::SerialParameters;
32use devices::virtio::block::DiskOption;
33#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
34use devices::virtio::device_constants::video::VideoDeviceConfig;
35#[cfg(feature = "gpu")]
36use devices::virtio::gpu::GpuParameters;
37use devices::virtio::scsi::ScsiOption;
38#[cfg(feature = "audio")]
39use devices::virtio::snd::parameters::Parameters as SndParameters;
40#[cfg(all(windows, feature = "gpu"))]
41use devices::virtio::vhost_user_backend::gpu::sys::windows::GpuBackendConfig;
42#[cfg(all(windows, feature = "gpu"))]
43use devices::virtio::vhost_user_backend::gpu::sys::windows::GpuVmmConfig;
44#[cfg(all(windows, feature = "gpu"))]
45use devices::virtio::vhost_user_backend::gpu::sys::windows::InputEventSplitConfig;
46#[cfg(all(windows, feature = "gpu"))]
47use devices::virtio::vhost_user_backend::gpu::sys::windows::WindowProcedureThreadSplitConfig;
48#[cfg(all(windows, feature = "audio"))]
49use devices::virtio::vhost_user_backend::snd::sys::windows::SndSplitConfig;
50use devices::virtio::vsock::VsockConfig;
51use devices::virtio::DeviceType;
52#[cfg(feature = "net")]
53use devices::virtio::NetParameters;
54use devices::FwCfgParameters;
55use devices::PciAddress;
56use devices::PflashParameters;
57use devices::StubPciParameters;
58#[cfg(target_arch = "x86_64")]
59use hypervisor::CpuHybridType;
60#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
61use hypervisor::NestedMode;
62use hypervisor::ProtectionType;
63use jail::JailConfig;
64use resources::AddressRange;
65use serde::Deserialize;
66use serde::Deserializer;
67use serde::Serialize;
68use serde_keyvalue::FromKeyValues;
69use vm_control::BatteryType;
70use vm_memory::FileBackedMappingParameters;
71#[cfg(target_arch = "x86_64")]
72use x86_64::check_host_hybrid_support;
73#[cfg(target_arch = "x86_64")]
74use x86_64::CpuIdCall;
75
76use super::any_device_module::AnyVirtioDeviceModule;
77pub(crate) use super::sys::HypervisorKind;
78#[cfg(any(target_os = "android", target_os = "linux"))]
79use crate::crosvm::sys::config::SharedDir;
80
81cfg_if::cfg_if! {
82 if #[cfg(any(target_os = "android", target_os = "linux"))] {
83 #[cfg(feature = "gpu")]
84 use crate::crosvm::sys::GpuRenderServerParameters;
85
86 #[cfg(target_arch = "aarch64")]
87 static VHOST_SCMI_PATH: &str = "/dev/vhost-scmi";
88 } else if #[cfg(windows)] {
89 use base::{Event, Tube};
90 }
91}
92
93#[cfg(feature = "balloon")]
95const VIRTIO_BALLOON_WS_DEFAULT_NUM_BINS: u8 = 4;
96
97#[allow(dead_code)]
99#[derive(Debug, Serialize, Deserialize)]
100pub enum Executable {
101 Bios(PathBuf),
103 Kernel(PathBuf),
105}
106
107#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, FromKeyValues)]
108#[serde(deny_unknown_fields, rename_all = "kebab-case")]
109pub enum IrqChipKind {
110 #[serde(rename_all = "kebab-case")]
112 Kernel {
113 #[cfg(target_arch = "aarch64")]
116 #[serde(default)]
117 allow_vgic_its: bool,
118 },
119 Split,
121 Userspace,
123}
124
125impl Default for IrqChipKind {
126 fn default() -> Self {
127 IrqChipKind::Kernel {
128 #[cfg(target_arch = "aarch64")]
129 allow_vgic_its: false,
130 }
131 }
132}
133
134#[cfg(target_arch = "x86_64")]
136#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
137#[serde(deny_unknown_fields, rename_all = "kebab-case")]
138pub struct CpuCoreType {
139 pub atom: CpuSet,
141 pub core: CpuSet,
143}
144
145#[derive(Debug, Default, PartialEq, Eq, Deserialize, Serialize, FromKeyValues)]
146#[serde(deny_unknown_fields, rename_all = "kebab-case")]
147pub struct CpuOptions {
148 #[serde(default)]
150 pub num_cores: Option<usize>,
151 #[serde(default)]
153 pub clusters: Vec<CpuSet>,
154 #[cfg(target_arch = "x86_64")]
156 pub core_types: Option<CpuCoreType>,
157 #[serde(default)]
159 pub boot_cpu: Option<usize>,
160 #[serde(default)]
162 pub freq_domains: Vec<CpuSet>,
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 pub disks: Vec<DiskOption>,
647 pub display_input_height: Option<u32>,
648 pub display_input_width: Option<u32>,
649 pub display_window_keyboard: bool,
650 pub display_window_mouse: bool,
651 pub dump_device_tree_blob: Option<PathBuf>,
652 pub dynamic_power_coefficient: BTreeMap<usize, u32>,
653 pub enable_fw_cfg: bool,
654 pub enable_hwp: bool,
655 pub executable_path: Option<Executable>,
656 #[cfg(windows)]
657 pub exit_stats: bool,
658 pub fdt_position: Option<FdtPosition>,
659 #[cfg(all(target_os = "android", target_arch = "aarch64"))]
660 pub ffa: Option<FfaConfig>,
661 pub file_backed_mappings_mmio: Vec<FileBackedMappingParameters>,
662 pub file_backed_mappings_ram: Vec<FileBackedMappingParameters>,
663 pub force_calibrated_tsc_leaf: bool,
664 pub force_disable_readonly_mem: bool,
665 pub force_s2idle: bool,
666 pub fw_cfg_parameters: Vec<FwCfgParameters>,
667 #[cfg(feature = "gdb")]
668 pub gdb: Option<u32>,
669 #[cfg(all(windows, feature = "gpu"))]
670 pub gpu_backend_config: Option<GpuBackendConfig>,
671 #[cfg(all(unix, feature = "gpu"))]
672 pub gpu_cgroup_path: Option<PathBuf>,
673 #[cfg(feature = "gpu")]
674 pub gpu_parameters: Option<GpuParameters>,
675 #[cfg(all(unix, feature = "gpu"))]
676 pub gpu_render_server_parameters: Option<GpuRenderServerParameters>,
677 #[cfg(all(unix, feature = "gpu"))]
678 pub gpu_server_cgroup_path: Option<PathBuf>,
679 #[cfg(all(windows, feature = "gpu"))]
680 pub gpu_vmm_config: Option<GpuVmmConfig>,
681 pub host_cpu_topology: bool,
682 #[cfg(windows)]
683 pub host_guid: Option<String>,
684 pub hugepages: bool,
685 pub hypervisor: Option<HypervisorKind>,
686 #[cfg(feature = "balloon")]
687 pub init_memory: Option<u64>,
688 pub initrd_path: Option<PathBuf>,
689 #[cfg(all(windows, feature = "gpu"))]
690 pub input_event_split_config: Option<InputEventSplitConfig>,
691 pub irq_chip: Option<IrqChipKind>,
692 pub itmt: bool,
693 pub jail_config: Option<JailConfig>,
694 #[cfg(windows)]
695 pub kernel_log_file: Option<String>,
696 #[cfg(any(target_os = "android", target_os = "linux"))]
697 pub lock_guest_memory: bool,
698 #[cfg(windows)]
699 pub log_file: Option<String>,
700 #[cfg(windows)]
701 pub logs_directory: Option<String>,
702 #[cfg(all(feature = "media", feature = "video-decoder"))]
703 pub media_decoder: Vec<VideoDeviceConfig>,
704 pub memory: Option<u64>,
705 pub memory_file: Option<PathBuf>,
706 pub mmio_address_ranges: Vec<AddressRange>,
707 #[cfg(target_arch = "aarch64")]
708 pub mte: bool,
709 pub name: Option<String>,
710 #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
711 pub nested: NestedConfig,
712 #[cfg(feature = "net")]
713 pub net: Vec<NetParameters>,
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 pub scsis: Vec<ScsiOption>,
748 #[serde(with = "serde_serial_params")]
749 pub serial_parameters: BTreeMap<(SerialHardware, u8), SerialParameters>,
750 #[cfg(windows)]
751 pub service_pipe_name: Option<String>,
752 #[cfg(any(target_os = "android", target_os = "linux"))]
753 #[serde(skip)]
754 pub shared_dirs: Vec<SharedDir>,
755 #[cfg(feature = "media")]
756 pub simple_media_device: bool,
757 #[cfg(any(feature = "slirp-ring-capture", feature = "slirp-debug"))]
758 pub slirp_capture_file: Option<String>,
759 #[cfg(target_arch = "x86_64")]
760 pub smbios: SmbiosOptions,
761 pub smccc_trng: bool,
762 #[cfg(all(windows, feature = "audio"))]
763 pub snd_split_configs: Vec<SndSplitConfig>,
764 pub socket_path: Option<PathBuf>,
765 #[cfg(feature = "audio")]
766 pub sound: Option<PathBuf>,
767 pub stub_pci_devices: Vec<StubPciParameters>,
768 pub suspended: bool,
769 #[cfg(target_arch = "aarch64")]
770 pub sve: Option<SveConfig>,
771 pub swap_dir: Option<PathBuf>,
772 pub swiotlb: Option<u64>,
773 #[cfg(target_os = "android")]
774 pub task_profiles: Vec<String>,
775 #[cfg(any(target_os = "android", target_os = "linux"))]
776 pub unmap_guest_memory_on_fork: bool,
777 pub usb: bool,
778 #[cfg(any(target_os = "android", target_os = "linux"))]
779 #[cfg(feature = "media")]
780 pub v4l2_proxy: Vec<PathBuf>,
781 pub vcpu_affinity: Option<VcpuAffinity>,
782 pub vcpu_cgroup_path: Option<PathBuf>,
783 pub vcpu_count: Option<usize>,
784 #[cfg(target_arch = "x86_64")]
785 pub vcpu_hybrid_type: BTreeMap<usize, CpuHybridType>, #[cfg(any(target_os = "android", target_os = "linux"))]
787 pub vfio: Vec<super::sys::config::VfioOption>,
788 #[cfg(any(target_os = "android", target_os = "linux"))]
789 pub vfio_isolate_hotplug: bool,
790 #[cfg(any(target_os = "android", target_os = "linux"))]
791 pub vfio_platform_pm: bool,
792 #[cfg(any(target_os = "android", target_os = "linux"))]
793 #[cfg(target_arch = "aarch64")]
794 pub vhost_scmi: bool,
795 #[cfg(any(target_os = "android", target_os = "linux"))]
796 #[cfg(target_arch = "aarch64")]
797 pub vhost_scmi_device: PathBuf,
798 pub vhost_user: Vec<VhostUserFrontendOption>,
799 pub vhost_user_connect_timeout_ms: Option<u64>,
800 #[cfg(feature = "video-decoder")]
801 pub video_dec: Vec<VideoDeviceConfig>,
802 #[cfg(feature = "video-encoder")]
803 pub video_enc: Vec<VideoDeviceConfig>,
804 #[cfg(all(
805 target_arch = "aarch64",
806 any(target_os = "android", target_os = "linux")
807 ))]
808 pub virt_cpufreq: bool,
809 pub virt_cpufreq_v2: bool,
810 #[serde(default)]
811 pub virtio_device_modules: Vec<AnyVirtioDeviceModule>,
812 pub virtio_input: Vec<InputDeviceOption>,
813 #[cfg(feature = "audio")]
814 #[serde(skip)]
815 pub virtio_snds: Vec<SndParameters>,
816 pub vsock: Option<VsockConfig>,
817 #[cfg(feature = "vtpm")]
818 pub vtpm_proxy: bool,
819 pub wayland_socket_paths: BTreeMap<String, PathBuf>,
820 #[cfg(all(windows, feature = "gpu"))]
821 pub window_procedure_thread_split_config: Option<WindowProcedureThreadSplitConfig>,
822 pub x_display: Option<String>,
823}
824
825impl Default for Config {
826 fn default() -> Config {
827 Config {
828 acpi_tables: Vec::new(),
829 #[cfg(feature = "android_display")]
830 android_display_service: None,
831 android_fstab: None,
832 async_executor: None,
833 #[cfg(feature = "balloon")]
834 balloon: true,
835 #[cfg(feature = "balloon")]
836 balloon_bias: 0,
837 #[cfg(feature = "balloon")]
838 balloon_control: None,
839 #[cfg(feature = "balloon")]
840 balloon_page_reporting: false,
841 #[cfg(feature = "balloon")]
842 balloon_ws_num_bins: VIRTIO_BALLOON_WS_DEFAULT_NUM_BINS,
843 #[cfg(feature = "balloon")]
844 balloon_ws_reporting: false,
845 battery_config: None,
846 boot_cpu: 0,
847 #[cfg(windows)]
848 block_control_tube: Vec::new(),
849 #[cfg(windows)]
850 block_vhost_user_tube: Vec::new(),
851 #[cfg(target_arch = "x86_64")]
852 break_linux_pci_config_io: false,
853 #[cfg(windows)]
854 broker_shutdown_event: None,
855 #[cfg(target_arch = "x86_64")]
856 bus_lock_ratelimit: 0,
857 #[cfg(any(target_os = "android", target_os = "linux"))]
858 coiommu_param: None,
859 core_scheduling: true,
860 #[cfg(feature = "crash-report")]
861 crash_pipe_name: None,
862 #[cfg(feature = "crash-report")]
863 crash_report_uuid: None,
864 cpu_capacity: BTreeMap::new(),
865 cpu_clusters: Vec::new(),
866 #[cfg(all(
867 target_arch = "aarch64",
868 any(target_os = "android", target_os = "linux")
869 ))]
870 cpu_frequencies_khz: BTreeMap::new(),
871 cpu_freq_domains: Vec::new(),
872 #[cfg(all(
873 target_arch = "aarch64",
874 any(target_os = "android", target_os = "linux")
875 ))]
876 cpu_ipc_ratio: BTreeMap::new(),
877 delay_rt: false,
878 device_tree_overlay: Vec::new(),
879 dev_pm: None,
880 disks: Vec::new(),
881 disable_virtio_intx: false,
882 display_input_height: None,
883 display_input_width: None,
884 display_window_keyboard: false,
885 display_window_mouse: false,
886 dump_device_tree_blob: None,
887 dynamic_power_coefficient: BTreeMap::new(),
888 enable_fw_cfg: false,
889 enable_hwp: false,
890 executable_path: None,
891 #[cfg(windows)]
892 exit_stats: false,
893 fdt_position: None,
894 #[cfg(all(target_os = "android", target_arch = "aarch64"))]
895 ffa: None,
896 file_backed_mappings_mmio: Vec::new(),
897 file_backed_mappings_ram: Vec::new(),
898 force_calibrated_tsc_leaf: false,
899 force_disable_readonly_mem: false,
900 force_s2idle: false,
901 fw_cfg_parameters: Vec::new(),
902 #[cfg(feature = "gdb")]
903 gdb: None,
904 #[cfg(all(windows, feature = "gpu"))]
905 gpu_backend_config: None,
906 #[cfg(feature = "gpu")]
907 gpu_parameters: None,
908 #[cfg(all(unix, feature = "gpu"))]
909 gpu_render_server_parameters: None,
910 #[cfg(all(unix, feature = "gpu"))]
911 gpu_cgroup_path: None,
912 #[cfg(all(unix, feature = "gpu"))]
913 gpu_server_cgroup_path: None,
914 #[cfg(all(windows, feature = "gpu"))]
915 gpu_vmm_config: None,
916 host_cpu_topology: false,
917 #[cfg(windows)]
918 host_guid: None,
919 #[cfg(windows)]
920 product_version: None,
921 #[cfg(windows)]
922 product_channel: None,
923 hugepages: false,
924 hypervisor: None,
925 #[cfg(feature = "balloon")]
926 init_memory: None,
927 initrd_path: None,
928 #[cfg(all(windows, feature = "gpu"))]
929 input_event_split_config: None,
930 irq_chip: None,
931 itmt: false,
932 jail_config: if !cfg!(feature = "default-no-sandbox") {
933 Some(Default::default())
934 } else {
935 None
936 },
937 #[cfg(windows)]
938 kernel_log_file: None,
939 #[cfg(any(target_os = "android", target_os = "linux"))]
940 lock_guest_memory: false,
941 #[cfg(windows)]
942 log_file: None,
943 #[cfg(windows)]
944 logs_directory: None,
945 #[cfg(any(target_os = "android", target_os = "linux"))]
946 boost_uclamp: false,
947 #[cfg(all(feature = "media", feature = "video-decoder"))]
948 media_decoder: Default::default(),
949 memory: None,
950 memory_file: None,
951 mmio_address_ranges: Vec::new(),
952 #[cfg(target_arch = "aarch64")]
953 mte: false,
954 name: None,
955 #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
956 nested: NestedConfig::default(),
957 #[cfg(feature = "net")]
958 net: Vec::new(),
959 #[cfg(windows)]
960 net_vhost_user_tube: None,
961 no_i8042: false,
962 no_pmu: false,
963 no_rtc: false,
964 no_smt: false,
965 params: Vec::new(),
966 pci_config: Default::default(),
967 #[cfg(feature = "pci-hotplug")]
968 pci_hotplug_slots: None,
969 per_vm_core_scheduling: false,
970 pflash_parameters: None,
971 #[cfg(any(target_os = "android", target_os = "linux"))]
972 pmem_ext2: Vec::new(),
973 pmems: Vec::new(),
974 #[cfg(feature = "process-invariants")]
975 process_invariants_data_handle: None,
976 #[cfg(feature = "process-invariants")]
977 process_invariants_data_size: None,
978 #[cfg(windows)]
979 product_name: None,
980 protection_type: ProtectionType::Unprotected,
981 pstore: None,
982 #[cfg(feature = "pvclock")]
983 pvclock: false,
984 pvm_fw: None,
985 restore_path: None,
986 rt_cpus: Default::default(),
987 serial_parameters: BTreeMap::new(),
988 scsis: Vec::new(),
989 #[cfg(windows)]
990 service_pipe_name: None,
991 #[cfg(any(target_os = "android", target_os = "linux"))]
992 shared_dirs: Vec::new(),
993 #[cfg(feature = "media")]
994 simple_media_device: Default::default(),
995 #[cfg(any(feature = "slirp-ring-capture", feature = "slirp-debug"))]
996 slirp_capture_file: None,
997 #[cfg(target_arch = "x86_64")]
998 smbios: SmbiosOptions::default(),
999 smccc_trng: false,
1000 #[cfg(all(windows, feature = "audio"))]
1001 snd_split_configs: Vec::new(),
1002 socket_path: None,
1003 #[cfg(feature = "audio")]
1004 sound: None,
1005 stub_pci_devices: Vec::new(),
1006 suspended: false,
1007 #[cfg(target_arch = "aarch64")]
1008 sve: None,
1009 swap_dir: None,
1010 swiotlb: None,
1011 #[cfg(target_os = "android")]
1012 task_profiles: Vec::new(),
1013 #[cfg(any(target_os = "android", target_os = "linux"))]
1014 unmap_guest_memory_on_fork: false,
1015 usb: true,
1016 vcpu_affinity: None,
1017 vcpu_cgroup_path: None,
1018 vcpu_count: None,
1019 #[cfg(target_arch = "x86_64")]
1020 vcpu_hybrid_type: BTreeMap::new(),
1021 #[cfg(any(target_os = "android", target_os = "linux"))]
1022 vfio: Vec::new(),
1023 #[cfg(any(target_os = "android", target_os = "linux"))]
1024 vfio_isolate_hotplug: false,
1025 #[cfg(any(target_os = "android", target_os = "linux"))]
1026 vfio_platform_pm: false,
1027 #[cfg(any(target_os = "android", target_os = "linux"))]
1028 #[cfg(target_arch = "aarch64")]
1029 vhost_scmi: false,
1030 #[cfg(any(target_os = "android", target_os = "linux"))]
1031 #[cfg(target_arch = "aarch64")]
1032 vhost_scmi_device: PathBuf::from(VHOST_SCMI_PATH),
1033 vhost_user: Vec::new(),
1034 vhost_user_connect_timeout_ms: None,
1035 vsock: None,
1036 #[cfg(feature = "video-decoder")]
1037 video_dec: Vec::new(),
1038 #[cfg(feature = "video-encoder")]
1039 video_enc: Vec::new(),
1040 #[cfg(all(
1041 target_arch = "aarch64",
1042 any(target_os = "android", target_os = "linux")
1043 ))]
1044 virt_cpufreq: false,
1045 virt_cpufreq_v2: false,
1046 virtio_device_modules: Vec::new(),
1047 virtio_input: Vec::new(),
1048 #[cfg(feature = "audio")]
1049 virtio_snds: Vec::new(),
1050 #[cfg(any(target_os = "android", target_os = "linux"))]
1051 #[cfg(feature = "media")]
1052 v4l2_proxy: Vec::new(),
1053 #[cfg(feature = "vtpm")]
1054 vtpm_proxy: false,
1055 wayland_socket_paths: BTreeMap::new(),
1056 #[cfg(windows)]
1057 window_procedure_thread_split_config: None,
1058 x_display: None,
1059 }
1060 }
1061}
1062
1063pub fn validate_config(cfg: &mut Config) -> std::result::Result<(), String> {
1064 if cfg.executable_path.is_none() {
1065 return Err("Executable is not specified".to_string());
1066 }
1067
1068 #[cfg(feature = "gpu")]
1069 {
1070 crate::crosvm::gpu_config::validate_gpu_config(cfg)?;
1071 }
1072 #[cfg(feature = "gdb")]
1073 if cfg.gdb.is_some() && cfg.vcpu_count.unwrap_or(1) != 1 {
1074 return Err("`gdb` requires the number of vCPU to be 1".to_string());
1075 }
1076 if cfg.host_cpu_topology {
1077 if cfg.no_smt {
1078 return Err(
1079 "`host-cpu-topology` cannot be set at the same time as `no_smt`, since \
1080 the smt of the Guest is the same as that of the Host when \
1081 `host-cpu-topology` is set."
1082 .to_string(),
1083 );
1084 }
1085
1086 let pcpu_count =
1087 base::number_of_online_cores().expect("Could not read number of online cores");
1088 if let Some(vcpu_count) = cfg.vcpu_count {
1089 if pcpu_count != vcpu_count {
1090 return Err(format!(
1091 "`host-cpu-topology` requires the count of vCPUs({vcpu_count}) to equal the \
1092 count of online CPUs({pcpu_count}) on host."
1093 ));
1094 }
1095 } else {
1096 cfg.vcpu_count = Some(pcpu_count);
1097 }
1098
1099 match &cfg.vcpu_affinity {
1100 None => {
1101 let vcpu_count = cfg.vcpu_count.unwrap();
1102 let max_cores = base::number_of_logical_cores()
1103 .expect("Could not read number of logical cores");
1104 let affinity_map =
1105 default_vcpu_affinity_map(vcpu_count, max_cores, base::is_cpu_online);
1106 cfg.vcpu_affinity = Some(VcpuAffinity::PerVcpu(affinity_map));
1107 }
1108 _ => {
1109 return Err(
1110 "`host-cpu-topology` requires not to set `cpu-affinity` at the same time"
1111 .to_string(),
1112 );
1113 }
1114 }
1115
1116 if !cfg.cpu_capacity.is_empty() {
1117 return Err(
1118 "`host-cpu-topology` requires not to set `cpu-capacity` at the same time"
1119 .to_string(),
1120 );
1121 }
1122
1123 if !cfg.cpu_clusters.is_empty() {
1124 return Err(
1125 "`host-cpu-topology` requires not to set `cpu clusters` at the same time"
1126 .to_string(),
1127 );
1128 }
1129 }
1130
1131 if cfg.boot_cpu >= cfg.vcpu_count.unwrap_or(1) {
1132 log::warn!("boot_cpu selection cannot be higher than vCPUs available, defaulting to 0");
1133 cfg.boot_cpu = 0;
1134 }
1135
1136 #[cfg(all(
1137 target_arch = "aarch64",
1138 any(target_os = "android", target_os = "linux")
1139 ))]
1140 if !cfg.cpu_frequencies_khz.is_empty() {
1141 if !cfg.virt_cpufreq_v2 {
1142 return Err("`cpu-frequencies` requires `virt-cpufreq-upstream`".to_string());
1143 }
1144
1145 if cfg.host_cpu_topology {
1146 return Err(
1147 "`host-cpu-topology` cannot be used with 'cpu-frequencies` at the same time"
1148 .to_string(),
1149 );
1150 }
1151 }
1152
1153 #[cfg(all(
1154 target_arch = "aarch64",
1155 any(target_os = "android", target_os = "linux")
1156 ))]
1157 if cfg.virt_cpufreq {
1158 if !cfg.host_cpu_topology && (cfg.vcpu_affinity.is_none() || cfg.cpu_capacity.is_empty()) {
1159 return Err("`virt-cpufreq` requires 'host-cpu-topology' enabled or \
1160 have vcpu_affinity and cpu_capacity configured"
1161 .to_string());
1162 }
1163 }
1164 #[cfg(target_arch = "x86_64")]
1165 if !cfg.vcpu_hybrid_type.is_empty() {
1166 if cfg.host_cpu_topology {
1167 return Err("`core-types` cannot be set with `host-cpu-topology`.".to_string());
1168 }
1169 check_host_hybrid_support(&CpuIdCall::new(__cpuid_count, __cpuid))
1170 .map_err(|e| format!("the cpu doesn't support `core-types`: {e}"))?;
1171 if cfg.vcpu_hybrid_type.len() != cfg.vcpu_count.unwrap_or(1) {
1172 return Err("`core-types` must be set for all virtual CPUs".to_string());
1173 }
1174 for cpu_id in 0..cfg.vcpu_count.unwrap_or(1) {
1175 if !cfg.vcpu_hybrid_type.contains_key(&cpu_id) {
1176 return Err("`core-types` must be set for all virtual CPUs".to_string());
1177 }
1178 }
1179 }
1180 #[cfg(target_arch = "x86_64")]
1181 if cfg.enable_hwp && !cfg.host_cpu_topology {
1182 return Err("setting `enable-hwp` requires `host-cpu-topology` is set.".to_string());
1183 }
1184 #[cfg(target_arch = "x86_64")]
1185 if cfg.itmt {
1186 use std::collections::BTreeSet;
1187 if !cfg.host_cpu_topology {
1191 if let Some(VcpuAffinity::PerVcpu(v)) = &cfg.vcpu_affinity {
1194 if v.len() != cfg.vcpu_count.unwrap_or(1) {
1196 return Err("`itmt` requires affinity to be set for every vCPU.".to_string());
1197 }
1198
1199 let mut pcpu_set = BTreeSet::new();
1200 for cpus in v.values() {
1201 if cpus.len() != 1 {
1202 return Err(
1203 "`itmt` requires affinity to be set 1 pCPU for 1 vCPU.".to_owned()
1204 );
1205 }
1206 if !pcpu_set.insert(cpus[0]) {
1209 return Err(
1210 "`cpu_host` requires affinity to be set different pVPU for each vCPU."
1211 .to_owned(),
1212 );
1213 }
1214 }
1215 } else {
1216 return Err("`itmt` requires affinity to be set for every vCPU.".to_string());
1217 }
1218 }
1219 if !cfg.enable_hwp {
1220 return Err("setting `itmt` requires `enable-hwp` is set.".to_string());
1221 }
1222 }
1223
1224 #[cfg(feature = "balloon")]
1225 {
1226 if !cfg.balloon && cfg.balloon_control.is_some() {
1227 return Err("'balloon-control' requires enabled balloon".to_string());
1228 }
1229
1230 if !cfg.balloon && cfg.balloon_page_reporting {
1231 return Err("'balloon_page_reporting' requires enabled balloon".to_string());
1232 }
1233 }
1234
1235 #[cfg(feature = "swap")]
1238 if cfg.swap_dir.is_some() && cfg.jail_config.is_none() {
1239 return Err("'swap' and 'disable-sandbox' are mutually exclusive".to_string());
1240 }
1241
1242 set_default_serial_parameters(
1243 &mut cfg.serial_parameters,
1244 cfg.vhost_user
1245 .iter()
1246 .any(|opt| opt.type_ == DeviceType::Console),
1247 );
1248
1249 for mapping in cfg
1250 .file_backed_mappings_mmio
1251 .iter_mut()
1252 .chain(cfg.file_backed_mappings_ram.iter_mut())
1253 {
1254 validate_file_backed_mapping(mapping)?;
1255 }
1256
1257 for pmem in cfg.pmems.iter() {
1258 validate_pmem(pmem)?;
1259 }
1260
1261 super::sys::config::validate_config(cfg)
1263}
1264
1265fn default_vcpu_affinity_map(
1266 vcpu_count: usize,
1267 max_cores: usize,
1268 is_cpu_online: impl Fn(usize) -> bool,
1269) -> BTreeMap<usize, CpuSet> {
1270 let mut affinity_map = BTreeMap::new();
1271 let mut vcpu_id = 0;
1272 for cpu_id in 0..max_cores {
1273 if is_cpu_online(cpu_id) {
1274 affinity_map.insert(vcpu_id, CpuSet::new([cpu_id]));
1275 vcpu_id += 1;
1276 }
1277 if vcpu_id >= vcpu_count {
1278 break;
1280 }
1281 }
1282 affinity_map
1283}
1284
1285fn validate_file_backed_mapping(mapping: &mut FileBackedMappingParameters) -> Result<(), String> {
1286 let pagesize_mask = pagesize() as u64 - 1;
1287 let aligned_address = mapping.address & !pagesize_mask;
1288 let aligned_size =
1289 ((mapping.address + mapping.size + pagesize_mask) & !pagesize_mask) - aligned_address;
1290
1291 if mapping.align {
1292 mapping.address = aligned_address;
1293 mapping.size = aligned_size;
1294 } else if aligned_address != mapping.address || aligned_size != mapping.size {
1295 return Err(
1296 "--file-backed-mapping addr and size parameters must be page size aligned".to_string(),
1297 );
1298 }
1299
1300 Ok(())
1301}
1302
1303fn validate_pmem(pmem: &PmemOption) -> Result<(), String> {
1304 if (pmem.swap_interval.is_some() && pmem.vma_size.is_none())
1305 || (pmem.swap_interval.is_none() && pmem.vma_size.is_some())
1306 {
1307 return Err(
1308 "--pmem vma-size and swap-interval parameters must be specified together".to_string(),
1309 );
1310 }
1311
1312 if pmem.ro && pmem.swap_interval.is_some() {
1313 return Err(
1314 "--pmem swap-interval parameter can only be set for writable pmem device".to_string(),
1315 );
1316 }
1317
1318 Ok(())
1319}
1320
1321#[cfg(test)]
1322#[allow(clippy::needless_update)]
1323mod tests {
1324 use argh::FromArgs;
1325 use devices::PciClassCode;
1326 use devices::StubPciParameters;
1327 #[cfg(target_arch = "x86_64")]
1328 use uuid::uuid;
1329
1330 use super::*;
1331
1332 fn config_from_args(args: &[&str]) -> Config {
1333 crate::crosvm::cmdline::RunCommand::from_args(&[], args)
1334 .unwrap()
1335 .try_into()
1336 .unwrap()
1337 }
1338
1339 #[test]
1340 fn parse_cpu_opts() {
1341 let res: CpuOptions = from_key_values("").unwrap();
1342 assert_eq!(res, CpuOptions::default());
1343
1344 let res: CpuOptions = from_key_values("12").unwrap();
1346 assert_eq!(
1347 res,
1348 CpuOptions {
1349 num_cores: Some(12),
1350 ..Default::default()
1351 }
1352 );
1353
1354 let res: CpuOptions = from_key_values("num-cores=16").unwrap();
1355 assert_eq!(
1356 res,
1357 CpuOptions {
1358 num_cores: Some(16),
1359 ..Default::default()
1360 }
1361 );
1362
1363 let res: CpuOptions = from_key_values("clusters=[[0],[1],[2],[3]]").unwrap();
1365 assert_eq!(
1366 res,
1367 CpuOptions {
1368 clusters: vec![
1369 CpuSet::new([0]),
1370 CpuSet::new([1]),
1371 CpuSet::new([2]),
1372 CpuSet::new([3])
1373 ],
1374 ..Default::default()
1375 }
1376 );
1377
1378 let res: CpuOptions = from_key_values("clusters=[[0-3]]").unwrap();
1379 assert_eq!(
1380 res,
1381 CpuOptions {
1382 clusters: vec![CpuSet::new([0, 1, 2, 3])],
1383 ..Default::default()
1384 }
1385 );
1386
1387 let res: CpuOptions = from_key_values("clusters=[[0,2],[1,3],[4-7,12]]").unwrap();
1388 assert_eq!(
1389 res,
1390 CpuOptions {
1391 clusters: vec![
1392 CpuSet::new([0, 2]),
1393 CpuSet::new([1, 3]),
1394 CpuSet::new([4, 5, 6, 7, 12])
1395 ],
1396 ..Default::default()
1397 }
1398 );
1399
1400 #[cfg(target_arch = "x86_64")]
1401 {
1402 let res: CpuOptions = from_key_values("core-types=[atom=[1,3-7],core=[0,2]]").unwrap();
1403 assert_eq!(
1404 res,
1405 CpuOptions {
1406 core_types: Some(CpuCoreType {
1407 atom: CpuSet::new([1, 3, 4, 5, 6, 7]),
1408 core: CpuSet::new([0, 2])
1409 }),
1410 ..Default::default()
1411 }
1412 );
1413 }
1414
1415 let res: CpuOptions = from_key_values("16,clusters=[[0],[4-6],[7]]").unwrap();
1417 assert_eq!(
1418 res,
1419 CpuOptions {
1420 num_cores: Some(16),
1421 clusters: vec![CpuSet::new([0]), CpuSet::new([4, 5, 6]), CpuSet::new([7])],
1422 ..Default::default()
1423 }
1424 );
1425
1426 let res: CpuOptions = from_key_values("clusters=[[0-7],[30-31]],num-cores=32").unwrap();
1427 assert_eq!(
1428 res,
1429 CpuOptions {
1430 num_cores: Some(32),
1431 clusters: vec![CpuSet::new([0, 1, 2, 3, 4, 5, 6, 7]), CpuSet::new([30, 31])],
1432 ..Default::default()
1433 }
1434 );
1435 }
1436
1437 #[test]
1438 #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
1439 fn parse_nested_config() {
1440 for (arg, mode) in [
1441 ("off", NestedMode::Off),
1442 ("auto", NestedMode::Auto),
1443 ("on", NestedMode::On),
1444 ("mode=off", NestedMode::Off),
1445 ("mode=auto", NestedMode::Auto),
1446 ("mode=on", NestedMode::On),
1447 ] {
1448 assert_eq!(
1449 from_key_values::<NestedConfig>(arg).unwrap(),
1450 NestedConfig { mode }
1451 );
1452 }
1453 #[cfg(target_arch = "x86_64")]
1454 assert_eq!(NestedConfig::default().mode, NestedMode::Auto);
1455 #[cfg(target_arch = "aarch64")]
1456 assert_eq!(NestedConfig::default().mode, NestedMode::Off);
1457
1458 from_key_values::<NestedConfig>("maybe").unwrap_err();
1459 from_key_values::<NestedConfig>("mode=maybe").unwrap_err();
1460 from_key_values::<NestedConfig>("bogus=true").unwrap_err();
1461 }
1462
1463 #[test]
1464 fn parse_cpu_set_single() {
1465 assert_eq!(
1466 CpuSet::from_str("123").expect("parse failed"),
1467 CpuSet::new([123])
1468 );
1469 }
1470
1471 #[test]
1472 fn parse_cpu_set_list() {
1473 assert_eq!(
1474 CpuSet::from_str("0,1,2,3").expect("parse failed"),
1475 CpuSet::new([0, 1, 2, 3])
1476 );
1477 }
1478
1479 #[test]
1480 fn parse_cpu_set_range() {
1481 assert_eq!(
1482 CpuSet::from_str("0-3").expect("parse failed"),
1483 CpuSet::new([0, 1, 2, 3])
1484 );
1485 }
1486
1487 #[test]
1488 fn parse_cpu_set_list_of_ranges() {
1489 assert_eq!(
1490 CpuSet::from_str("3-4,7-9,18").expect("parse failed"),
1491 CpuSet::new([3, 4, 7, 8, 9, 18])
1492 );
1493 }
1494
1495 #[test]
1496 fn parse_cpu_set_repeated() {
1497 assert_eq!(
1500 CpuSet::from_str("1,1,1").expect("parse failed"),
1501 CpuSet::new([1, 1, 1])
1502 );
1503 }
1504
1505 #[test]
1506 fn parse_cpu_set_negative() {
1507 CpuSet::from_str("-3").expect_err("parse should have failed");
1509 }
1510
1511 #[test]
1512 fn parse_cpu_set_reverse_range() {
1513 CpuSet::from_str("5-2").expect_err("parse should have failed");
1515 }
1516
1517 #[test]
1518 fn parse_cpu_set_open_range() {
1519 CpuSet::from_str("3-").expect_err("parse should have failed");
1520 }
1521
1522 #[test]
1523 fn parse_cpu_set_extra_comma() {
1524 CpuSet::from_str("0,1,2,").expect_err("parse should have failed");
1525 }
1526
1527 #[test]
1528 fn parse_cpu_affinity_global() {
1529 assert_eq!(
1530 parse_cpu_affinity("0,5-7,9").expect("parse failed"),
1531 VcpuAffinity::Global(CpuSet::new([0, 5, 6, 7, 9])),
1532 );
1533 }
1534
1535 #[test]
1536 fn parse_cpu_affinity_per_vcpu_one_to_one() {
1537 let mut expected_map = BTreeMap::new();
1538 expected_map.insert(0, CpuSet::new([0]));
1539 expected_map.insert(1, CpuSet::new([1]));
1540 expected_map.insert(2, CpuSet::new([2]));
1541 expected_map.insert(3, CpuSet::new([3]));
1542 assert_eq!(
1543 parse_cpu_affinity("0=0:1=1:2=2:3=3").expect("parse failed"),
1544 VcpuAffinity::PerVcpu(expected_map),
1545 );
1546 }
1547
1548 #[test]
1549 fn parse_cpu_affinity_per_vcpu_sets() {
1550 let mut expected_map = BTreeMap::new();
1551 expected_map.insert(0, CpuSet::new([0, 1, 2]));
1552 expected_map.insert(1, CpuSet::new([3, 4, 5]));
1553 expected_map.insert(2, CpuSet::new([6, 7, 8]));
1554 assert_eq!(
1555 parse_cpu_affinity("0=0,1,2:1=3-5:2=6,7-8").expect("parse failed"),
1556 VcpuAffinity::PerVcpu(expected_map),
1557 );
1558 }
1559
1560 #[test]
1561 fn parse_mem_opts() {
1562 let res: MemOptions = from_key_values("").unwrap();
1563 assert_eq!(res.size, None);
1564
1565 let res: MemOptions = from_key_values("1024").unwrap();
1566 assert_eq!(res.size, Some(1024));
1567
1568 let res: MemOptions = from_key_values("size=0x4000").unwrap();
1569 assert_eq!(res.size, Some(16384));
1570 }
1571
1572 #[test]
1573 fn parse_serial_vaild() {
1574 parse_serial_options("type=syslog,num=1,console=true,stdin=true")
1575 .expect("parse should have succeded");
1576 }
1577
1578 #[test]
1579 fn parse_serial_virtio_console_vaild() {
1580 parse_serial_options("type=syslog,num=5,console=true,stdin=true,hardware=virtio-console")
1581 .expect("parse should have succeded");
1582 }
1583
1584 #[test]
1585 fn parse_serial_valid_no_num() {
1586 parse_serial_options("type=syslog").expect("parse should have succeded");
1587 }
1588
1589 #[test]
1590 fn parse_serial_equals_in_value() {
1591 let parsed = parse_serial_options("type=syslog,path=foo=bar==.log")
1592 .expect("parse should have succeded");
1593 assert_eq!(parsed.path, Some(PathBuf::from("foo=bar==.log")));
1594 }
1595
1596 #[test]
1597 fn parse_serial_invalid_type() {
1598 parse_serial_options("type=wormhole,num=1").expect_err("parse should have failed");
1599 }
1600
1601 #[test]
1602 fn parse_serial_invalid_num_upper() {
1603 parse_serial_options("type=syslog,num=5").expect_err("parse should have failed");
1604 }
1605
1606 #[test]
1607 fn parse_serial_invalid_num_lower() {
1608 parse_serial_options("type=syslog,num=0").expect_err("parse should have failed");
1609 }
1610
1611 #[test]
1612 fn parse_serial_virtio_console_invalid_num_lower() {
1613 parse_serial_options("type=syslog,hardware=virtio-console,num=0")
1614 .expect_err("parse should have failed");
1615 }
1616
1617 #[test]
1618 fn parse_serial_invalid_num_string() {
1619 parse_serial_options("type=syslog,num=number3").expect_err("parse should have failed");
1620 }
1621
1622 #[test]
1623 fn parse_serial_invalid_option() {
1624 parse_serial_options("type=syslog,speed=lightspeed").expect_err("parse should have failed");
1625 }
1626
1627 #[test]
1628 fn parse_serial_invalid_two_stdin() {
1629 assert!(TryInto::<Config>::try_into(
1630 crate::crosvm::cmdline::RunCommand::from_args(
1631 &[],
1632 &[
1633 "--serial",
1634 "num=1,type=stdout,stdin=true",
1635 "--serial",
1636 "num=2,type=stdout,stdin=true"
1637 ]
1638 )
1639 .unwrap()
1640 )
1641 .is_err())
1642 }
1643
1644 #[test]
1645 fn parse_serial_pci_address_valid_for_virtio() {
1646 let parsed =
1647 parse_serial_options("type=syslog,hardware=virtio-console,pci-address=00:0e.0")
1648 .expect("parse should have succeded");
1649 assert_eq!(
1650 parsed.pci_address,
1651 Some(PciAddress {
1652 bus: 0,
1653 dev: 14,
1654 func: 0
1655 })
1656 );
1657 }
1658
1659 #[test]
1660 fn parse_serial_pci_address_valid_for_legacy_virtio() {
1661 let parsed =
1662 parse_serial_options("type=syslog,hardware=legacy-virtio-console,pci-address=00:0e.0")
1663 .expect("parse should have succeded");
1664 assert_eq!(
1665 parsed.pci_address,
1666 Some(PciAddress {
1667 bus: 0,
1668 dev: 14,
1669 func: 0
1670 })
1671 );
1672 }
1673
1674 #[test]
1675 fn parse_serial_pci_address_failed_for_serial() {
1676 parse_serial_options("type=syslog,hardware=serial,pci-address=00:0e.0")
1677 .expect_err("expected pci-address error for serial hardware");
1678 }
1679
1680 #[test]
1681 fn parse_serial_pci_address_failed_for_debugcon() {
1682 parse_serial_options("type=syslog,hardware=debugcon,pci-address=00:0e.0")
1683 .expect_err("expected pci-address error for debugcon hardware");
1684 }
1685
1686 #[test]
1687 fn parse_battery_valid() {
1688 let bat_config: BatteryConfig = from_key_values("type=goldfish").unwrap();
1689 assert_eq!(bat_config.type_, BatteryType::Goldfish);
1690 }
1691
1692 #[test]
1693 fn parse_battery_valid_no_type() {
1694 let bat_config: BatteryConfig = from_key_values("").unwrap();
1695 assert_eq!(bat_config.type_, BatteryType::Goldfish);
1696 }
1697
1698 #[test]
1699 fn parse_battery_invalid_parameter() {
1700 from_key_values::<BatteryConfig>("tyep=goldfish").expect_err("parse should have failed");
1701 }
1702
1703 #[test]
1704 fn parse_battery_invalid_type_value() {
1705 from_key_values::<BatteryConfig>("type=xxx").expect_err("parse should have failed");
1706 }
1707
1708 #[test]
1709 fn parse_irqchip_kernel() {
1710 let cfg = TryInto::<Config>::try_into(
1711 crate::crosvm::cmdline::RunCommand::from_args(
1712 &[],
1713 &["--irqchip", "kernel", "/dev/null"],
1714 )
1715 .unwrap(),
1716 )
1717 .unwrap();
1718
1719 assert_eq!(
1720 cfg.irq_chip,
1721 Some(IrqChipKind::Kernel {
1722 #[cfg(target_arch = "aarch64")]
1723 allow_vgic_its: false
1724 })
1725 );
1726 }
1727
1728 #[test]
1729 #[cfg(target_arch = "aarch64")]
1730 fn parse_irqchip_kernel_with_its() {
1731 let cfg = TryInto::<Config>::try_into(
1732 crate::crosvm::cmdline::RunCommand::from_args(
1733 &[],
1734 &["--irqchip", "kernel[allow-vgic-its]", "/dev/null"],
1735 )
1736 .unwrap(),
1737 )
1738 .unwrap();
1739
1740 assert_eq!(
1741 cfg.irq_chip,
1742 Some(IrqChipKind::Kernel {
1743 allow_vgic_its: true
1744 })
1745 );
1746 }
1747
1748 #[test]
1749 fn parse_irqchip_split() {
1750 let cfg = TryInto::<Config>::try_into(
1751 crate::crosvm::cmdline::RunCommand::from_args(
1752 &[],
1753 &["--irqchip", "split", "/dev/null"],
1754 )
1755 .unwrap(),
1756 )
1757 .unwrap();
1758
1759 assert_eq!(cfg.irq_chip, Some(IrqChipKind::Split));
1760 }
1761
1762 #[test]
1763 fn parse_irqchip_userspace() {
1764 let cfg = TryInto::<Config>::try_into(
1765 crate::crosvm::cmdline::RunCommand::from_args(
1766 &[],
1767 &["--irqchip", "userspace", "/dev/null"],
1768 )
1769 .unwrap(),
1770 )
1771 .unwrap();
1772
1773 assert_eq!(cfg.irq_chip, Some(IrqChipKind::Userspace));
1774 }
1775
1776 #[test]
1777 fn parse_stub_pci() {
1778 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();
1779 assert_eq!(params.address.bus, 1);
1780 assert_eq!(params.address.dev, 2);
1781 assert_eq!(params.address.func, 3);
1782 assert_eq!(params.vendor, 0xfffe);
1783 assert_eq!(params.device, 0xfffd);
1784 assert_eq!(params.class.class as u8, PciClassCode::Other as u8);
1785 assert_eq!(params.class.subclass, 0xc1);
1786 assert_eq!(params.class.programming_interface, 0xc2);
1787 assert_eq!(params.subsystem_vendor, 0xfffc);
1788 assert_eq!(params.subsystem_device, 0xfffb);
1789 assert_eq!(params.revision, 0xa);
1790 }
1791
1792 #[test]
1793 fn parse_file_backed_mapping_valid() {
1794 let params = from_key_values::<FileBackedMappingParameters>(
1795 "addr=0x1000,size=0x2000,path=/dev/mem,offset=0x3000,rw,sync",
1796 )
1797 .unwrap();
1798 assert_eq!(params.address, 0x1000);
1799 assert_eq!(params.size, 0x2000);
1800 assert_eq!(params.path, PathBuf::from("/dev/mem"));
1801 assert_eq!(params.offset, 0x3000);
1802 assert!(params.writable);
1803 assert!(params.sync);
1804 }
1805
1806 #[test]
1807 fn parse_file_backed_mapping_incomplete() {
1808 assert!(
1809 from_key_values::<FileBackedMappingParameters>("addr=0x1000,size=0x2000")
1810 .unwrap_err()
1811 .contains("missing field `path`")
1812 );
1813 assert!(
1814 from_key_values::<FileBackedMappingParameters>("size=0x2000,path=/dev/mem")
1815 .unwrap_err()
1816 .contains("missing field `addr`")
1817 );
1818 assert!(
1819 from_key_values::<FileBackedMappingParameters>("addr=0x1000,path=/dev/mem")
1820 .unwrap_err()
1821 .contains("missing field `size`")
1822 );
1823 }
1824
1825 #[test]
1826 fn parse_file_backed_mapping_unaligned_addr() {
1827 let mut params =
1828 from_key_values::<FileBackedMappingParameters>("addr=0x1001,size=0x2000,path=/dev/mem")
1829 .unwrap();
1830 assert!(validate_file_backed_mapping(&mut params)
1831 .unwrap_err()
1832 .contains("aligned"));
1833 }
1834 #[test]
1835 fn parse_file_backed_mapping_unaligned_size() {
1836 let mut params =
1837 from_key_values::<FileBackedMappingParameters>("addr=0x1000,size=0x2001,path=/dev/mem")
1838 .unwrap();
1839 assert!(validate_file_backed_mapping(&mut params)
1840 .unwrap_err()
1841 .contains("aligned"));
1842 }
1843
1844 #[test]
1845 fn parse_file_backed_mapping_align() {
1846 let addr = pagesize() as u64 * 3 + 42;
1847 let size = pagesize() as u64 - 0xf;
1848 let mut params = from_key_values::<FileBackedMappingParameters>(&format!(
1849 "addr={addr},size={size},path=/dev/mem,align",
1850 ))
1851 .unwrap();
1852 assert_eq!(params.address, addr);
1853 assert_eq!(params.size, size);
1854 validate_file_backed_mapping(&mut params).unwrap();
1855 assert_eq!(params.address, pagesize() as u64 * 3);
1856 assert_eq!(params.size, pagesize() as u64 * 2);
1857 }
1858
1859 #[test]
1860 fn parse_fw_cfg_valid_path() {
1861 let cfg = TryInto::<Config>::try_into(
1862 crate::crosvm::cmdline::RunCommand::from_args(
1863 &[],
1864 &["--fw-cfg", "name=bar,path=data.bin", "/dev/null"],
1865 )
1866 .unwrap(),
1867 )
1868 .unwrap();
1869
1870 assert_eq!(cfg.fw_cfg_parameters.len(), 1);
1871 assert_eq!(cfg.fw_cfg_parameters[0].name, "bar".to_string());
1872 assert_eq!(cfg.fw_cfg_parameters[0].string, None);
1873 assert_eq!(cfg.fw_cfg_parameters[0].path, Some("data.bin".into()));
1874 }
1875
1876 #[test]
1877 fn parse_fw_cfg_valid_string() {
1878 let cfg = TryInto::<Config>::try_into(
1879 crate::crosvm::cmdline::RunCommand::from_args(
1880 &[],
1881 &["--fw-cfg", "name=bar,string=foo", "/dev/null"],
1882 )
1883 .unwrap(),
1884 )
1885 .unwrap();
1886
1887 assert_eq!(cfg.fw_cfg_parameters.len(), 1);
1888 assert_eq!(cfg.fw_cfg_parameters[0].name, "bar".to_string());
1889 assert_eq!(cfg.fw_cfg_parameters[0].string, Some("foo".to_string()));
1890 assert_eq!(cfg.fw_cfg_parameters[0].path, None);
1891 }
1892
1893 #[test]
1894 fn parse_dtbo() {
1895 let cfg: Config = crate::crosvm::cmdline::RunCommand::from_args(
1896 &[],
1897 &[
1898 "--device-tree-overlay",
1899 "/path/to/dtbo1",
1900 "--device-tree-overlay",
1901 "/path/to/dtbo2",
1902 "/dev/null",
1903 ],
1904 )
1905 .unwrap()
1906 .try_into()
1907 .unwrap();
1908
1909 assert_eq!(cfg.device_tree_overlay.len(), 2);
1910 for (opt, p) in cfg
1911 .device_tree_overlay
1912 .into_iter()
1913 .zip(["/path/to/dtbo1", "/path/to/dtbo2"])
1914 {
1915 assert_eq!(opt.path, PathBuf::from(p));
1916 assert!(opt.select_symbols.is_none());
1917 }
1918 }
1919
1920 #[test]
1921 #[cfg(any(target_os = "android", target_os = "linux"))]
1922 fn parse_dtbo_filtered() {
1923 let cfg: Config = crate::crosvm::cmdline::RunCommand::from_args(
1924 &[],
1925 &[
1926 "--vfio",
1927 "/path/to/dev",
1928 "--device-tree-overlay",
1929 "/path/to/dtbo1,select-symbols=[mydev]",
1930 "--device-tree-overlay",
1931 "/path/to/dtbo2,select-symbols=[mydev]",
1932 "/dev/null",
1933 ],
1934 )
1935 .unwrap()
1936 .try_into()
1937 .unwrap();
1938
1939 assert_eq!(cfg.device_tree_overlay.len(), 2);
1940 for (opt, p) in cfg
1941 .device_tree_overlay
1942 .into_iter()
1943 .zip(["/path/to/dtbo1", "/path/to/dtbo2"])
1944 {
1945 assert_eq!(opt.path, PathBuf::from(p));
1946 assert_eq!(opt.select_symbols, Some(vec!["mydev".to_string()]));
1947 }
1948 }
1949
1950 #[test]
1951 #[cfg(any(target_os = "android", target_os = "linux"))]
1952 fn parse_dtbo_legacy_filter() {
1953 let cfg: Config = crate::crosvm::cmdline::RunCommand::from_args(
1954 &[],
1955 &[
1956 "--vfio",
1957 "/path/to/dev,dt-symbol=mydev",
1958 "--device-tree-overlay",
1959 "/path/to/dtbo1,filter",
1960 "/dev/null",
1961 ],
1962 )
1963 .unwrap()
1964 .try_into()
1965 .unwrap();
1966
1967 assert_eq!(cfg.device_tree_overlay.len(), 1);
1968 let opt = cfg.device_tree_overlay.first().unwrap();
1969 assert_eq!(opt.path, PathBuf::from("/path/to/dtbo1"));
1970 assert_eq!(opt.select_symbols, Some(vec!["mydev".to_string()]));
1971 }
1972
1973 #[test]
1974 #[cfg(any(target_os = "android", target_os = "linux"))]
1975 fn parse_dtbo_filter_and_select_symbols() {
1976 let cfg: Config = crate::crosvm::cmdline::RunCommand::from_args(
1977 &[],
1978 &[
1979 "--vfio",
1980 "/path/to/dev,dt-symbol=mydev",
1981 "--device-tree-overlay",
1982 "/path/to/dtbo1,filter,select-symbols=[otherdev]",
1983 "/dev/null",
1984 ],
1985 )
1986 .unwrap()
1987 .try_into()
1988 .unwrap();
1989
1990 assert_eq!(cfg.device_tree_overlay.len(), 1);
1991 let opt = cfg.device_tree_overlay.first().unwrap();
1992 assert_eq!(opt.path, PathBuf::from("/path/to/dtbo1"));
1993 assert_eq!(
1994 opt.select_symbols,
1995 Some(vec!["otherdev".to_string(), "mydev".to_string()])
1996 );
1997 }
1998
1999 #[test]
2000 fn parse_fw_cfg_invalid_no_name() {
2001 assert!(
2002 crate::crosvm::cmdline::RunCommand::from_args(&[], &["--fw-cfg", "string=foo",])
2003 .is_err()
2004 );
2005 }
2006
2007 #[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
2008 #[test]
2009 fn parse_video() {
2010 use devices::virtio::device_constants::video::VideoBackendType;
2011
2012 #[cfg(feature = "libvda")]
2013 {
2014 let params: VideoDeviceConfig = from_key_values("libvda").unwrap();
2015 assert_eq!(params.backend, VideoBackendType::Libvda);
2016
2017 let params: VideoDeviceConfig = from_key_values("libvda-vd").unwrap();
2018 assert_eq!(params.backend, VideoBackendType::LibvdaVd);
2019 }
2020
2021 #[cfg(feature = "ffmpeg")]
2022 {
2023 let params: VideoDeviceConfig = from_key_values("ffmpeg").unwrap();
2024 assert_eq!(params.backend, VideoBackendType::Ffmpeg);
2025 }
2026
2027 #[cfg(feature = "vaapi")]
2028 {
2029 let params: VideoDeviceConfig = from_key_values("vaapi").unwrap();
2030 assert_eq!(params.backend, VideoBackendType::Vaapi);
2031 }
2032 }
2033
2034 #[test]
2035 fn parse_vhost_user_option_all_device_types() {
2036 fn test_device_type(type_string: &str, type_: DeviceType) {
2037 let vhost_user_arg = format!("{type_string},socket=sock");
2038
2039 let cfg = TryInto::<Config>::try_into(
2040 crate::crosvm::cmdline::RunCommand::from_args(
2041 &[],
2042 &["--vhost-user", &vhost_user_arg, "/dev/null"],
2043 )
2044 .unwrap(),
2045 )
2046 .unwrap();
2047
2048 assert_eq!(cfg.vhost_user.len(), 1);
2049 let vu = &cfg.vhost_user[0];
2050 assert_eq!(vu.type_, type_);
2051 }
2052
2053 test_device_type("net", DeviceType::Net);
2054 test_device_type("block", DeviceType::Block);
2055 test_device_type("console", DeviceType::Console);
2056 test_device_type("rng", DeviceType::Rng);
2057 test_device_type("balloon", DeviceType::Balloon);
2058 test_device_type("scsi", DeviceType::Scsi);
2059 test_device_type("9p", DeviceType::P9);
2060 test_device_type("gpu", DeviceType::Gpu);
2061 test_device_type("input", DeviceType::Input);
2062 test_device_type("vsock", DeviceType::Vsock);
2063 test_device_type("iommu", DeviceType::Iommu);
2064 test_device_type("sound", DeviceType::Sound);
2065 test_device_type("fs", DeviceType::Fs);
2066 test_device_type("pmem", DeviceType::Pmem);
2067 test_device_type("mac80211-hwsim", DeviceType::Mac80211HwSim);
2068 test_device_type("video-encoder", DeviceType::VideoEncoder);
2069 test_device_type("video-decoder", DeviceType::VideoDecoder);
2070 test_device_type("scmi", DeviceType::Scmi);
2071 test_device_type("wl", DeviceType::Wl);
2072 test_device_type("tpm", DeviceType::Tpm);
2073 test_device_type("pvclock", DeviceType::Pvclock);
2074 }
2075
2076 #[cfg(target_arch = "x86_64")]
2077 #[test]
2078 fn parse_smbios_uuid() {
2079 let opt: SmbiosOptions =
2080 from_key_values("uuid=12e474af-2cc1-49d1-b0e5-d03a3e03ca03").unwrap();
2081 assert_eq!(
2082 opt.uuid,
2083 Some(uuid!("12e474af-2cc1-49d1-b0e5-d03a3e03ca03"))
2084 );
2085
2086 from_key_values::<SmbiosOptions>("uuid=zzzz").expect_err("expected error parsing uuid");
2087 }
2088
2089 #[test]
2090 fn parse_touch_legacy() {
2091 let cfg = TryInto::<Config>::try_into(
2092 crate::crosvm::cmdline::RunCommand::from_args(
2093 &[],
2094 &["--multi-touch", "my_socket:867:5309", "bzImage"],
2095 )
2096 .unwrap(),
2097 )
2098 .unwrap();
2099
2100 assert_eq!(cfg.virtio_input.len(), 1);
2101 let multi_touch = cfg
2102 .virtio_input
2103 .iter()
2104 .find(|input| matches!(input, InputDeviceOption::MultiTouch { .. }))
2105 .unwrap();
2106 assert_eq!(
2107 *multi_touch,
2108 InputDeviceOption::MultiTouch {
2109 path: PathBuf::from("my_socket"),
2110 width: Some(867),
2111 height: Some(5309),
2112 name: None
2113 }
2114 );
2115 }
2116
2117 #[test]
2118 fn parse_touch() {
2119 let cfg = TryInto::<Config>::try_into(
2120 crate::crosvm::cmdline::RunCommand::from_args(
2121 &[],
2122 &["--multi-touch", r"C:\path,width=867,height=5309", "bzImage"],
2123 )
2124 .unwrap(),
2125 )
2126 .unwrap();
2127
2128 assert_eq!(cfg.virtio_input.len(), 1);
2129 let multi_touch = cfg
2130 .virtio_input
2131 .iter()
2132 .find(|input| matches!(input, InputDeviceOption::MultiTouch { .. }))
2133 .unwrap();
2134 assert_eq!(
2135 *multi_touch,
2136 InputDeviceOption::MultiTouch {
2137 path: PathBuf::from(r"C:\path"),
2138 width: Some(867),
2139 height: Some(5309),
2140 name: None
2141 }
2142 );
2143 }
2144
2145 #[test]
2146 fn single_touch_spec_and_track_pad_spec_default_size() {
2147 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2148 &[],
2149 &[
2150 "--single-touch",
2151 "/dev/single-touch-test",
2152 "--trackpad",
2153 "/dev/single-touch-test",
2154 "/dev/null",
2155 ],
2156 )
2157 .unwrap()
2158 .try_into()
2159 .unwrap();
2160
2161 let single_touch = config
2162 .virtio_input
2163 .iter()
2164 .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2165 .unwrap();
2166 let trackpad = config
2167 .virtio_input
2168 .iter()
2169 .find(|input| matches!(input, InputDeviceOption::Trackpad { .. }))
2170 .unwrap();
2171
2172 assert_eq!(
2173 *single_touch,
2174 InputDeviceOption::SingleTouch {
2175 path: PathBuf::from("/dev/single-touch-test"),
2176 width: None,
2177 height: None,
2178 name: None
2179 }
2180 );
2181 assert_eq!(
2182 *trackpad,
2183 InputDeviceOption::Trackpad {
2184 path: PathBuf::from("/dev/single-touch-test"),
2185 width: None,
2186 height: None,
2187 name: None
2188 }
2189 );
2190 }
2191
2192 #[cfg(feature = "gpu")]
2193 #[test]
2194 fn single_touch_spec_default_size_from_gpu() {
2195 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2196 &[],
2197 &[
2198 "--single-touch",
2199 "/dev/single-touch-test",
2200 "--gpu",
2201 "width=1024,height=768",
2202 "/dev/null",
2203 ],
2204 )
2205 .unwrap()
2206 .try_into()
2207 .unwrap();
2208
2209 let single_touch = config
2210 .virtio_input
2211 .iter()
2212 .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2213 .unwrap();
2214 assert_eq!(
2215 *single_touch,
2216 InputDeviceOption::SingleTouch {
2217 path: PathBuf::from("/dev/single-touch-test"),
2218 width: None,
2219 height: None,
2220 name: None
2221 }
2222 );
2223
2224 assert_eq!(config.display_input_width, Some(1024));
2225 assert_eq!(config.display_input_height, Some(768));
2226 }
2227
2228 #[test]
2229 fn single_touch_spec_and_track_pad_spec_with_size() {
2230 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2231 &[],
2232 &[
2233 "--single-touch",
2234 "/dev/single-touch-test:12345:54321",
2235 "--trackpad",
2236 "/dev/single-touch-test:5678:9876",
2237 "/dev/null",
2238 ],
2239 )
2240 .unwrap()
2241 .try_into()
2242 .unwrap();
2243
2244 let single_touch = config
2245 .virtio_input
2246 .iter()
2247 .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2248 .unwrap();
2249 let trackpad = config
2250 .virtio_input
2251 .iter()
2252 .find(|input| matches!(input, InputDeviceOption::Trackpad { .. }))
2253 .unwrap();
2254
2255 assert_eq!(
2256 *single_touch,
2257 InputDeviceOption::SingleTouch {
2258 path: PathBuf::from("/dev/single-touch-test"),
2259 width: Some(12345),
2260 height: Some(54321),
2261 name: None
2262 }
2263 );
2264 assert_eq!(
2265 *trackpad,
2266 InputDeviceOption::Trackpad {
2267 path: PathBuf::from("/dev/single-touch-test"),
2268 width: Some(5678),
2269 height: Some(9876),
2270 name: None
2271 }
2272 );
2273 }
2274
2275 #[cfg(feature = "gpu")]
2276 #[test]
2277 fn single_touch_spec_with_size_independent_from_gpu() {
2278 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2279 &[],
2280 &[
2281 "--single-touch",
2282 "/dev/single-touch-test:12345:54321",
2283 "--gpu",
2284 "width=1024,height=768",
2285 "/dev/null",
2286 ],
2287 )
2288 .unwrap()
2289 .try_into()
2290 .unwrap();
2291
2292 let single_touch = config
2293 .virtio_input
2294 .iter()
2295 .find(|input| matches!(input, InputDeviceOption::SingleTouch { .. }))
2296 .unwrap();
2297
2298 assert_eq!(
2299 *single_touch,
2300 InputDeviceOption::SingleTouch {
2301 path: PathBuf::from("/dev/single-touch-test"),
2302 width: Some(12345),
2303 height: Some(54321),
2304 name: None
2305 }
2306 );
2307
2308 assert_eq!(config.display_input_width, Some(1024));
2309 assert_eq!(config.display_input_height, Some(768));
2310 }
2311
2312 #[test]
2313 fn virtio_switches() {
2314 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2315 &[],
2316 &["--switches", "/dev/switches-test", "/dev/null"],
2317 )
2318 .unwrap()
2319 .try_into()
2320 .unwrap();
2321
2322 let switches = config
2323 .virtio_input
2324 .iter()
2325 .find(|input| matches!(input, InputDeviceOption::Switches { .. }))
2326 .unwrap();
2327
2328 assert_eq!(
2329 *switches,
2330 InputDeviceOption::Switches {
2331 path: PathBuf::from("/dev/switches-test")
2332 }
2333 );
2334 }
2335
2336 #[test]
2337 fn virtio_rotary() {
2338 let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
2339 &[],
2340 &["--rotary", "/dev/rotary-test", "/dev/null"],
2341 )
2342 .unwrap()
2343 .try_into()
2344 .unwrap();
2345
2346 let rotary = config
2347 .virtio_input
2348 .iter()
2349 .find(|input| matches!(input, InputDeviceOption::Rotary { .. }))
2350 .unwrap();
2351
2352 assert_eq!(
2353 *rotary,
2354 InputDeviceOption::Rotary {
2355 path: PathBuf::from("/dev/rotary-test")
2356 }
2357 );
2358 }
2359
2360 #[cfg(target_arch = "aarch64")]
2361 #[test]
2362 fn parse_pci_cam() {
2363 assert_eq!(
2364 config_from_args(&["--pci", "cam=[start=0x123]", "/dev/null"]).pci_config,
2365 PciConfig {
2366 cam: Some(arch::MemoryRegionConfig {
2367 start: 0x123,
2368 size: None,
2369 }),
2370 ..PciConfig::default()
2371 }
2372 );
2373 assert_eq!(
2374 config_from_args(&["--pci", "cam=[start=0x123,size=0x456]", "/dev/null"]).pci_config,
2375 PciConfig {
2376 cam: Some(arch::MemoryRegionConfig {
2377 start: 0x123,
2378 size: Some(0x456),
2379 }),
2380 ..PciConfig::default()
2381 },
2382 );
2383 }
2384
2385 #[cfg(target_arch = "x86_64")]
2386 #[test]
2387 fn parse_pci_ecam() {
2388 assert_eq!(
2389 config_from_args(&["--pci", "ecam=[start=0x123]", "/dev/null"]).pci_config,
2390 PciConfig {
2391 ecam: Some(arch::MemoryRegionConfig {
2392 start: 0x123,
2393 size: None,
2394 }),
2395 ..PciConfig::default()
2396 }
2397 );
2398 assert_eq!(
2399 config_from_args(&["--pci", "ecam=[start=0x123,size=0x456]", "/dev/null"]).pci_config,
2400 PciConfig {
2401 ecam: Some(arch::MemoryRegionConfig {
2402 start: 0x123,
2403 size: Some(0x456),
2404 }),
2405 ..PciConfig::default()
2406 },
2407 );
2408 }
2409
2410 #[test]
2411 fn parse_pci_mem() {
2412 assert_eq!(
2413 config_from_args(&["--pci", "mem=[start=0x123]", "/dev/null"]).pci_config,
2414 PciConfig {
2415 mem: Some(arch::MemoryRegionConfig {
2416 start: 0x123,
2417 size: None,
2418 }),
2419 ..PciConfig::default()
2420 }
2421 );
2422 assert_eq!(
2423 config_from_args(&["--pci", "mem=[start=0x123,size=0x456]", "/dev/null"]).pci_config,
2424 PciConfig {
2425 mem: Some(arch::MemoryRegionConfig {
2426 start: 0x123,
2427 size: Some(0x456),
2428 }),
2429 ..PciConfig::default()
2430 },
2431 );
2432 }
2433
2434 #[test]
2435 fn parse_pmem_options_missing_path() {
2436 assert!(from_key_values::<PmemOption>("")
2437 .unwrap_err()
2438 .contains("missing field `path`"));
2439 }
2440
2441 #[test]
2442 fn parse_pmem_options_default_values() {
2443 let pmem = from_key_values::<PmemOption>("/path/to/disk.img").unwrap();
2444 assert_eq!(
2445 pmem,
2446 PmemOption {
2447 path: "/path/to/disk.img".into(),
2448 ro: false,
2449 root: false,
2450 vma_size: None,
2451 swap_interval: None,
2452 }
2453 );
2454 }
2455
2456 #[test]
2457 fn parse_pmem_options_virtual_swap() {
2458 let pmem =
2459 from_key_values::<PmemOption>("virtual_path,vma-size=12345,swap-interval-ms=1000")
2460 .unwrap();
2461 assert_eq!(
2462 pmem,
2463 PmemOption {
2464 path: "virtual_path".into(),
2465 ro: false,
2466 root: false,
2467 vma_size: Some(12345),
2468 swap_interval: Some(Duration::new(1, 0)),
2469 }
2470 );
2471 }
2472
2473 #[test]
2474 fn validate_pmem_missing_virtual_swap_param() {
2475 let pmem = from_key_values::<PmemOption>("virtual_path,swap-interval-ms=1000").unwrap();
2476 assert!(validate_pmem(&pmem)
2477 .unwrap_err()
2478 .contains("vma-size and swap-interval parameters must be specified together"));
2479 }
2480
2481 #[test]
2482 fn validate_pmem_read_only_virtual_swap() {
2483 let pmem = from_key_values::<PmemOption>(
2484 "virtual_path,ro=true,vma-size=12345,swap-interval-ms=1000",
2485 )
2486 .unwrap();
2487 assert!(validate_pmem(&pmem)
2488 .unwrap_err()
2489 .contains("swap-interval parameter can only be set for writable pmem device"));
2490 }
2491
2492 #[test]
2493 fn test_default_vcpu_affinity_map() {
2494 let affinity = default_vcpu_affinity_map(4, 4, |_| true);
2496 assert_eq!(affinity.len(), 4);
2497 assert_eq!(affinity.get(&0), Some(&CpuSet::new([0])));
2498 assert_eq!(affinity.get(&1), Some(&CpuSet::new([1])));
2499 assert_eq!(affinity.get(&2), Some(&CpuSet::new([2])));
2500 assert_eq!(affinity.get(&3), Some(&CpuSet::new([3])));
2501
2502 let affinity = default_vcpu_affinity_map(3, 4, |id| id != 1);
2504 assert_eq!(affinity.len(), 3);
2505 assert_eq!(affinity.get(&0), Some(&CpuSet::new([0])));
2506 assert_eq!(affinity.get(&1), Some(&CpuSet::new([2])));
2507 assert_eq!(affinity.get(&2), Some(&CpuSet::new([3])));
2508 }
2509}