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