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