Merge with upstream 2022-06-30 1/2

517bfb7a crosvm_control: Add link to library docs
1c80e062 crosvm: Remove --no-legacy flag.
88b3f563 crosvm: move gpu arg parsing related code into sys/unix
ed071b6e crosvm: implement lock-guest-memory feature.
10574227 hypervisor: enable TSC cpuid leaf synthesis for haxm.
4ea25d1e x86_64: add TSC leaf synthesis.
efde16f4 virtio: Enable build and tests for some virtio mods
4f902ffc ffmpeg: specify supported resolution range for input formats
8c60b644 vcpu: refactor scheduler config into a new function

1414622073..517bfb7acf

BUG=b:213146388
BUG=b:236210703
BUG=b:237011316
BUG=b:236909264
BUG=b:169295147
BUG=b:236574949
BUG=b:213152505

Change-Id: I8a317fe6a9f2b42c512240279bbbb1d8c8da567b
This commit is contained in:
crosvm-luci-ci-builder 2022-06-30 07:01:27 -07:00
commit f5dfac0307
20 changed files with 979 additions and 775 deletions

View file

@ -561,6 +561,7 @@ impl arch::LinuxArch for AArch64 {
_host_cpu_topology: bool,
_enable_pnp_data: bool,
_itmt: bool,
_force_calibrated_tsc_leaf: bool,
) -> std::result::Result<(), Self::Error> {
// AArch64 doesn't configure vcpus on the vcpu thread, so nothing to do here.
Ok(())

View file

@ -238,6 +238,7 @@ pub trait LinuxArch {
/// * `host_cpu_topology` - whether enabling host cpu topology.
/// * `enable_pnp_data` - whether enabling PnP statistics data.
/// * `itmt` - whether enabling ITMT scheduler
/// * `force_calibrated_tsc_leaf` - whether to force using a calibrated TSC leaf (0x15).
fn configure_vcpu<V: Vm>(
vm: &V,
hypervisor: &dyn HypervisorArch,
@ -251,6 +252,7 @@ pub trait LinuxArch {
host_cpu_topology: bool,
enable_pnp_data: bool,
itmt: bool,
force_calibrated_tsc_leaf: bool,
) -> Result<(), Self::Error>;
/// Configures and add a pci device into vm

View file

@ -12,6 +12,8 @@ use std::{
ptr::{copy_nonoverlapping, null_mut, read_unaligned, write_unaligned},
};
use log::warn;
use crate::{
external_mapping::ExternalMapping, AsRawDescriptor, Descriptor,
MemoryMapping as CrateMemoryMapping, MemoryMappingBuilder, RawDescriptor, SafeDescriptor,
@ -703,6 +705,34 @@ impl MemoryMapping {
}
}
/// Disable host swap for this mapping.
pub fn lock_all(&self) -> Result<()> {
let ret = unsafe {
// Safe because MLOCK_ONFAULT only affects the swap behavior of the kernel, so it
// has no impact on rust semantics.
// TODO(raging): use the explicit declaration of mlock2, which was being merged
// as of when the call below was being worked on.
libc::syscall(
libc::SYS_mlock2,
(self.addr as usize) as *const libc::c_void,
self.size(),
libc::MLOCK_ONFAULT,
)
};
if ret < 0 {
let errno = super::Error::last();
warn!(
"failed to mlock at {:#x} with length {}: {}",
(self.addr as usize) as u64,
self.size(),
errno,
);
Err(Error::SystemCallFailed(errno))
} else {
Ok(())
}
}
// Check that offset+count is valid and return the sum.
fn range_end(&self, offset: usize, count: usize) -> Result<usize> {
let mem_end = offset.checked_add(count).ok_or(Error::InvalidAddress)?;
@ -990,13 +1020,19 @@ impl CrateMemoryMapping {
}
pub trait Unix {
/// Remove the specified range from the mapping.
fn remove_range(&self, mem_offset: usize, count: usize) -> Result<()>;
/// Disable host swap for this mapping.
fn lock_all(&self) -> Result<()>;
}
impl Unix for CrateMemoryMapping {
fn remove_range(&self, mem_offset: usize, count: usize) -> Result<()> {
self.mapping.remove_range(mem_offset, count)
}
fn lock_all(&self) -> Result<()> {
self.mapping.lock_all()
}
}
pub trait MemoryMappingBuilderUnix<'a> {

View file

@ -6,6 +6,12 @@
//!
//! This crate is a programmatic alternative to invoking crosvm with subcommands that produce the
//! result on stdout.
//!
//! Downstream projects rely on this library maintaining a stable API surface.
//! Do not make changes to this library without consulting the crosvm externalization team.
//! Email: crosvm-dev@chromium.org
//! For more information see:
//! <https://google.github.io/crosvm/running_crosvm/programmatic_interaction.html#usage>
use std::convert::{TryFrom, TryInto};
use std::ffi::CStr;

View file

@ -4,67 +4,73 @@
//! Implements virtio devices, queues, and transport mechanisms.
mod async_device;
mod async_utils;
mod descriptor_utils;
mod input;
mod interrupt;
mod iommu;
mod queue;
mod rng;
#[cfg(feature = "tpm")]
mod tpm;
#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
mod video;
mod virtio_device;
mod virtio_pci_common_config;
mod virtio_pci_device;
pub mod block;
pub mod resource_bridge;
pub mod vhost;
pub use self::block::*;
pub use self::descriptor_utils::Error as DescriptorError;
pub use self::descriptor_utils::*;
pub use self::input::*;
pub use self::interrupt::*;
pub use self::iommu::*;
pub use self::queue::*;
pub use self::rng::*;
#[cfg(feature = "tpm")]
pub use self::tpm::*;
#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
pub use self::video::*;
pub use self::virtio_device::*;
pub use self::virtio_pci_device::*;
cfg_if::cfg_if! {
if #[cfg(unix)] {
mod async_device;
mod balloon;
mod input;
mod p9;
mod pmem;
mod rng;
#[cfg(feature = "tpm")]
mod tpm;
#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
mod video;
mod virtio_pci_common_config;
mod virtio_pci_device;
pub mod wl;
pub mod block;
pub mod console;
pub mod fs;
#[cfg(feature = "gpu")]
pub mod gpu;
pub mod net;
pub mod resource_bridge;
#[cfg(feature = "audio")]
pub mod snd;
pub mod vhost;
pub use self::balloon::*;
pub use self::block::*;
pub use self::console::*;
#[cfg(feature = "gpu")]
pub use self::gpu::*;
pub use self::input::*;
#[cfg(unix)]
pub use self::iommu::sys::unix::vfio_wrapper;
pub use self::net::*;
pub use self::p9::*;
pub use self::pmem::*;
pub use self::rng::*;
#[cfg(feature = "audio")]
pub use self::snd::*;
#[cfg(feature = "tpm")]
pub use self::tpm::*;
#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
pub use self::video::*;
pub use self::virtio_pci_device::*;
pub use self::wl::*;
} else if #[cfg(windows)] {
#[cfg(feature = "slirp")]
pub mod net;
#[cfg(feature = "slirp")]
pub use self::net::*;
} else {
compile_error!("Unsupported platform");
}

View file

@ -2,24 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
mod block;
mod handler;
pub use block::{run_block_device, Options as BlockOptions};
cfg_if::cfg_if! {
if #[cfg(unix)] {
mod block;
#[cfg(feature = "gpu")]
mod gpu;
mod console;
#[cfg(feature = "audio_cras")]
mod cras_snd;
mod fs;
mod handler;
mod net;
mod vsock;
mod vvu;
mod wl;
pub use block::{run_block_device, Options as BlockOptions};
pub use vsock::{run_vsock_device, Options as VsockOptions};
pub use wl::{run_wl_device, parse_wayland_sock, Options as WlOptions};
pub use console::{run_console_device, Options as ConsoleOptions};

View file

@ -661,7 +661,22 @@ impl DecoderBackend for FfmpegDecoder {
in_formats.push(FormatDesc {
mask: !(u64::MAX << SUPPORTED_OUTPUT_FORMATS.len()),
format,
frame_formats: vec![Default::default()],
frame_formats: vec![FrameFormat {
// These frame sizes are arbitrary, but avcodec does not seem to have any
// specific restriction in that regard (or any way to query the supported
// resolutions).
width: FormatRange {
min: 64,
max: 16384,
step: 1,
},
height: FormatRange {
min: 64,
max: 16384,
step: 1,
},
bitrates: Default::default(),
}],
});
}

View file

@ -12,6 +12,18 @@ pub enum HypervisorCap {
UserMemory,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
Xcrs,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
/// CPUID leaf 0x15 is available on some Intel chips and contains the TSC
/// frequency, which can be used to calibrate the guest's TSC clocksource;
/// however, it is not typically accurate enough (being off by 1-2% is a
/// big problem for a clocksource), and inside the guest, calibration by
/// other means is not always reliable.
///
/// Hypervisors which do not provide the TSC frequency (e.g. via the kvm
/// pvclock) or have another suitable calibration source can declare this
/// capability, which causes CrosVM to substitute a calibrated value in leaf
/// 0x15 that will be accurate enough for use in a clocksource.
CalibratedTscLeafRequired,
}
/// A capability the `Vm` can possibly expose.

View file

@ -182,8 +182,13 @@ impl Vm for HaxmVm {
})
}
fn check_capability(&self, _c: VmCap) -> bool {
false
fn check_capability(&self, c: VmCap) -> bool {
match c {
// under haxm, guests rely on this leaf to calibrate their
// clocksource.
VmCap::CalibratedTscLeafRequired => true,
_ => false,
}
}
fn get_memory(&self) -> &GuestMemory {

View file

@ -1152,6 +1152,8 @@ impl TryFrom<HypervisorCap> for KvmCap {
HypervisorCap::UserMemory => Ok(KvmCap::UserMemory),
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
HypervisorCap::Xcrs => Ok(KvmCap::Xcrs),
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
HypervisorCap::CalibratedTscLeafRequired => Err(Error::new(libc::EINVAL)),
}
}
}

View file

@ -375,6 +375,8 @@ impl Vm for WhpxVm {
VmCap::Protected => false,
// whpx initializes cpuid early during VM creation.
VmCap::EarlyInitCpuid => true,
// under whpx, guests rely on this leaf to calibrate their clocksource.
VmCap::CalibratedTscLeafRequired => true,
}
}

View file

@ -13,6 +13,8 @@ use super::platform::GpuRenderServerParameters;
use super::sys::config::parse_coiommu_params;
#[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
use super::sys::config::parse_gpu_render_server_options;
#[cfg(feature = "gpu")]
use super::sys::config::{parse_gpu_display_options, parse_gpu_options};
#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
use super::config::parse_video_options;
@ -559,6 +561,11 @@ pub struct RunCommand {
/// align - whether to adjust addr and size to page
/// boundaries implicitly
pub file_backed_mappings: Vec<FileBackedMappingParameters>,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[argh(switch)]
/// force use of a calibrated TSC cpuid leaf (0x15) even if the hypervisor
/// doesn't require one.
pub force_calibrated_tsc_leaf: bool,
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
#[argh(option, arg_name = "PORT")]
/// (EXPERIMENTAL) gdb on the given port
@ -646,6 +653,9 @@ pub struct RunCommand {
#[argh(option, long = "kvm-device", arg_name = "PATH")]
/// path to the KVM device. (default /dev/kvm)
pub kvm_device_path: Option<PathBuf>,
#[argh(switch)]
/// disable host swap on guest VM pages.
pub lock_guest_memory: bool,
#[cfg(unix)]
#[argh(option, arg_name = "MAC", long = "mac")]
/// MAC address for VM
@ -675,10 +685,6 @@ pub struct RunCommand {
#[argh(switch)]
/// don't use legacy KBD devices emulation
pub no_i8042: bool,
#[cfg(unix)]
#[argh(switch)]
/// don't use legacy KBD/RTC devices emulation
pub no_legacy: bool,
#[argh(switch)]
/// don't create RNG device in the guest
pub no_rng: bool,
@ -1192,6 +1198,8 @@ impl TryFrom<RunCommand> for super::config::Config {
cfg.hugepages = cmd.hugepages;
cfg.lock_guest_memory = cmd.lock_guest_memory;
#[cfg(feature = "audio")]
{
cfg.ac97_parameters = cmd.ac97;
@ -1446,9 +1454,6 @@ impl TryFrom<RunCommand> for super::config::Config {
#[cfg(unix)]
{
cfg.no_i8042 = cmd.no_legacy;
cfg.no_rtc = cmd.no_legacy;
if cmd.vhost_vsock_device.is_some() && cmd.vhost_vsock_fd.is_some() {
return Err(
"Only one of vhost-vsock-device vhost-vsock-fd has to be specified".to_string(),
@ -1554,8 +1559,8 @@ impl TryFrom<RunCommand> for super::config::Config {
cfg.force_s2idle = cmd.s2idle;
cfg.pcie_ecam = cmd.pcie_ecam;
cfg.pci_low_start = cmd.pci_low_start;
cfg.no_i8042 |= cmd.no_i8042;
cfg.no_rtc |= cmd.no_rtc;
cfg.no_i8042 = cmd.no_i8042;
cfg.no_rtc = cmd.no_rtc;
for (index, msr_config) in cmd.userspace_msr {
if cfg.userspace_msr.insert(index, msr_config).is_some() {
@ -1594,8 +1599,21 @@ impl TryFrom<RunCommand> for super::config::Config {
cfg.itmt = cmd.itmt;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if cmd.enable_pnp_data && cmd.force_calibrated_tsc_leaf {
return Err(
"Only one of [enable_pnp_data,force_calibrated_tsc_leaf] can be specified"
.to_string(),
);
}
cfg.enable_pnp_data = cmd.enable_pnp_data;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
cfg.force_calibrated_tsc_leaf = cmd.force_calibrated_tsc_leaf;
}
cfg.privileged_vm = cmd.privileged_vm;
cfg.stub_pci_devices = cmd.stub_pci_devices;

View file

@ -1324,350 +1324,6 @@ pub fn parse_stub_pci_parameters(s: &str) -> Result<StubPciParameters, String> {
Ok(params)
}
#[cfg(feature = "gpu")]
#[derive(Default)]
struct GpuDisplayParametersBuilder {
width: Option<u32>,
height: Option<u32>,
args: Vec<String>,
}
#[cfg(feature = "gpu")]
impl GpuDisplayParametersBuilder {
fn parse(&mut self, arg: &str) -> argument::Result<()> {
let mut kv = arg.split('=');
let k = kv.next().unwrap_or("");
let v = kv.next().unwrap_or("");
match k {
"width" => {
let width = v
.parse::<u32>()
.map_err(|_| argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'width' must be a valid integer"),
})?;
self.width = Some(width);
}
"height" => {
let height = v
.parse::<u32>()
.map_err(|_| argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'height' must be a valid integer"),
})?;
self.height = Some(height);
}
_ => {
return Err(argument::Error::UnknownArgument(format!(
"gpu-display parameter {}",
k
)))
}
}
self.args.push(arg.to_string());
Ok(())
}
fn build(&self) -> Result<Option<GpuDisplayParameters>, String> {
match (self.width, self.height) {
(None, None) => Ok(None),
(None, Some(_)) | (Some(_), None) => {
let mut value = self
.args
.clone()
.into_iter()
.fold(String::new(), |args_so_far, arg| args_so_far + &arg + ",");
value.pop();
return Err(invalid_value_err(
value,
"gpu must include both 'width' and 'height' if either is supplied",
));
}
(Some(width), Some(height)) => Ok(Some(GpuDisplayParameters { width, height })),
}
}
}
#[cfg(feature = "gpu")]
pub fn parse_gpu_options(s: &str) -> Result<GpuParameters, String> {
use devices::virtio::GpuMode;
use rutabaga_gfx::RutabagaWsi;
use crate::crosvm::sys::config::is_gpu_backend_deprecated;
#[cfg(feature = "gfxstream")]
let mut vulkan_specified = false;
#[cfg(feature = "gfxstream")]
let mut syncfd_specified = false;
#[cfg(feature = "gfxstream")]
let mut angle_specified = false;
let mut display_param_builder: GpuDisplayParametersBuilder = Default::default();
let mut gpu_params = GpuParameters::default();
for frag in s.split(',') {
let mut rest: Option<&str> = None;
let mut kv = frag.split('=');
let k = kv.next().unwrap_or("");
let v = kv.next().unwrap_or("");
match k {
// Deprecated: Specifying --gpu=<mode> Not great as the mode can be set multiple
// times if the user specifies several modes (--gpu=2d,virglrenderer,gfxstream)
"2d" | "2D" => {
gpu_params.mode = GpuMode::Mode2D;
}
"3d" | "3D" | "virglrenderer" => {
gpu_params.mode = GpuMode::ModeVirglRenderer;
}
#[cfg(feature = "gfxstream")]
"gfxstream" => {
gpu_params.mode = GpuMode::ModeGfxstream;
}
// Preferred: Specifying --gpu,backend=<mode>
"backend" => match v {
"2d" | "2D" => {
if is_gpu_backend_deprecated(v) {
return Err(invalid_value_err(
v,
"this backend type is deprecated, please use gfxstream.",
));
} else {
gpu_params.mode = GpuMode::Mode2D;
}
}
"3d" | "3D" | "virglrenderer" => {
if is_gpu_backend_deprecated(v) {
return Err(invalid_value_err(
v,
"this backend type is deprecated, please use gfxstream.",
));
} else {
gpu_params.mode = GpuMode::ModeVirglRenderer;
}
}
#[cfg(feature = "gfxstream")]
"gfxstream" => {
gpu_params.mode = GpuMode::ModeGfxstream;
}
_ => {
return Err(invalid_value_err(
v,
#[cfg(feature = "gfxstream")]
"gpu parameter 'backend' should be one of (2d|virglrenderer|gfxstream)",
#[cfg(not(feature = "gfxstream"))]
"gpu parameter 'backend' should be one of (2d|3d)",
));
}
},
"egl" => match v {
"true" | "" => {
gpu_params.renderer_use_egl = true;
}
"false" => {
gpu_params.renderer_use_egl = false;
}
_ => {
return Err(invalid_value_err(
v,
"gpu parameter 'egl' should be a boolean",
));
}
},
"gles" => match v {
"true" | "" => {
gpu_params.renderer_use_gles = true;
}
"false" => {
gpu_params.renderer_use_gles = false;
}
_ => {
return Err(invalid_value_err(
v,
"gpu parameter 'gles' should be a boolean",
));
}
},
"glx" => match v {
"true" | "" => {
gpu_params.renderer_use_glx = true;
}
"false" => {
gpu_params.renderer_use_glx = false;
}
_ => {
return Err(invalid_value_err(
v,
"gpu parameter 'glx' should be a boolean",
));
}
},
"surfaceless" => match v {
"true" | "" => {
gpu_params.renderer_use_surfaceless = true;
}
"false" => {
gpu_params.renderer_use_surfaceless = false;
}
_ => {
return Err(invalid_value_err(
v,
"gpu parameter 'surfaceless' should be a boolean",
));
}
},
#[cfg(feature = "gfxstream")]
"syncfd" => {
syncfd_specified = true;
match v {
"true" | "" => {
gpu_params.gfxstream_use_syncfd = true;
}
"false" => {
gpu_params.gfxstream_use_syncfd = false;
}
_ => {
return Err(argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'syncfd' should be a boolean"),
});
}
}
}
#[cfg(feature = "gfxstream")]
"angle" => {
angle_specified = true;
match v {
"true" | "" => {
gpu_params.gfxstream_use_guest_angle = true;
}
"false" => {
gpu_params.gfxstream_use_guest_angle = false;
}
_ => {
return Err(invalid_value_err(
v,
"gpu parameter 'angle' should be a boolean",
));
}
}
}
"vulkan" => {
#[cfg(feature = "gfxstream")]
{
vulkan_specified = true;
}
match v {
"true" | "" => {
gpu_params.use_vulkan = true;
}
"false" => {
gpu_params.use_vulkan = false;
}
_ => {
return Err(invalid_value_err(
v,
"gpu parameter 'vulkan' should be a boolean",
));
}
}
}
"wsi" => match v {
"vk" => {
gpu_params.wsi = Some(RutabagaWsi::Vulkan);
}
_ => {
return Err(invalid_value_err(v, "gpu parameter 'wsi' should be vk"));
}
},
"cache-path" => gpu_params.cache_path = Some(v.to_string()),
"cache-size" => gpu_params.cache_size = Some(v.to_string()),
"pci-bar-size" => {
let size = parse_hex_or_decimal(v).map_err(|_| {
"gpu parameter `pci-bar-size` must be a valid hex or decimal value"
})?;
gpu_params.pci_bar_size = size;
}
"udmabuf" => match v {
"true" | "" => {
gpu_params.udmabuf = true;
}
"false" => {
gpu_params.udmabuf = false;
}
_ => {
return Err(invalid_value_err(
v,
"gpu parameter 'udmabuf' should be a boolean",
));
}
},
"context-types" => {
let context_types: Vec<String> = v.split(':').map(|s| s.to_string()).collect();
gpu_params.context_mask = rutabaga_gfx::calculate_context_mask(context_types);
}
"" => {}
_ => {
rest = Some(frag);
}
}
if let Some(arg) = rest.take() {
match display_param_builder.parse(arg) {
Ok(()) => {}
Err(argument::Error::UnknownArgument(_)) => {
rest = Some(arg);
}
Err(err) => return Err(err.to_string()),
}
}
if let Some(arg) = rest.take() {
return Err(format!("unknown gpu parameter {}", arg));
}
}
if let Some(display_param) = display_param_builder.build()?.take() {
gpu_params.displays.push(display_param);
}
#[cfg(feature = "gfxstream")]
{
if !vulkan_specified && gpu_params.mode == GpuMode::ModeGfxstream {
gpu_params.use_vulkan = sys::use_vulkan();
}
if syncfd_specified || angle_specified {
match gpu_params.mode {
GpuMode::ModeGfxstream => {}
_ => {
return Err(
"gpu parameter syncfd and angle are only supported for gfxstream backend"
.to_string(),
);
}
}
}
}
Ok(gpu_params)
}
#[cfg(feature = "gpu")]
pub fn parse_gpu_display_options(s: &str) -> Result<GpuDisplayParameters, String> {
let mut display_param_builder: GpuDisplayParametersBuilder = Default::default();
for arg in s.split(',') {
display_param_builder
.parse(arg)
.map_err(|e| e.to_string())?;
}
let display_param = display_param_builder.build()?;
let display_param = display_param.ok_or_else(|| {
invalid_value_err(s, "gpu-display must include both 'width' and 'height'")
})?;
Ok(display_param)
}
/// Aggregate of all configurable options for a running VM.
#[remain::sorted]
pub struct Config {
@ -1704,6 +1360,7 @@ pub struct Config {
pub enable_pnp_data: bool,
pub executable_path: Option<Executable>,
pub file_backed_mappings: Vec<FileBackedMappingParameters>,
pub force_calibrated_tsc_leaf: bool,
pub force_s2idle: bool,
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
pub gdb: Option<u32>,
@ -1719,6 +1376,7 @@ pub struct Config {
pub itmt: bool,
pub jail_config: Option<JailConfig>,
pub kvm_device_path: PathBuf,
pub lock_guest_memory: bool,
pub mac_address: Option<net_util::MacAddress>,
pub memory: Option<u64>,
pub memory_file: Option<PathBuf>,
@ -1835,6 +1493,7 @@ impl Default for Config {
enable_pnp_data: false,
executable_path: None,
file_backed_mappings: Vec::new(),
force_calibrated_tsc_leaf: false,
force_s2idle: false,
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
gdb: None,
@ -1854,6 +1513,7 @@ impl Default for Config {
None
},
kvm_device_path: PathBuf::from(KVM_PATH),
lock_guest_memory: false,
mac_address: None,
memory: None,
memory_file: None,
@ -2135,6 +1795,10 @@ pub fn validate_config(cfg: &mut Config) -> std::result::Result<(), String> {
return Err("'balloon-control' requires enabled balloon".to_string());
}
if cfg.lock_guest_memory && cfg.jail_config.is_none() {
return Err("'lock-guest-memory' and 'disable-sandbox' are mutually exclusive".to_string());
}
set_default_serial_parameters(
&mut cfg.serial_parameters,
!cfg.vhost_user_console.is_empty(),
@ -2268,20 +1932,6 @@ mod tests {
.expect_err("parse should have failed");
}
#[cfg(feature = "audio_cras")]
#[test]
fn parse_ac97_socket_type() {
parse_ac97_options("socket_type=unified").expect("parse should have succeded");
parse_ac97_options("socket_type=legacy").expect("parse should have succeded");
}
#[cfg(feature = "audio")]
#[test]
fn parse_ac97_vios_valid() {
parse_ac97_options("backend=vios,server=/path/to/server")
.expect("parse should have succeded");
}
#[test]
fn parse_serial_vaild() {
parse_serial_options("type=syslog,num=1,console=true,stdin=true")
@ -2354,30 +2004,6 @@ mod tests {
.is_err())
}
#[test]
fn parse_plugin_mount_valid() {
let opt: BindMount = "/dev/null:/dev/zero:true".parse().unwrap();
assert_eq!(opt.src, PathBuf::from("/dev/null"));
assert_eq!(opt.dst, PathBuf::from("/dev/zero"));
assert!(opt.writable);
}
#[test]
fn parse_plugin_mount_valid_shorthand() {
let opt: BindMount = "/dev/null".parse().unwrap();
assert_eq!(opt.dst, PathBuf::from("/dev/null"));
assert!(!opt.writable);
let opt: BindMount = "/dev/null:/dev/zero".parse().unwrap();
assert_eq!(opt.dst, PathBuf::from("/dev/zero"));
assert!(!opt.writable);
let opt: BindMount = "/dev/null::true".parse().unwrap();
assert_eq!(opt.dst, PathBuf::from("/dev/null"));
assert!(opt.writable);
}
#[test]
fn parse_plugin_mount_invalid() {
"".parse::<BindMount>().expect_err("parse should fail");
@ -2396,6 +2022,7 @@ mod tests {
.expect_err("parse should fail because flag is not boolean");
}
#[cfg(feature = "plugin")]
#[test]
fn parse_plugin_gid_map_valid() {
let opt: GidMap = "1:2:3".parse().expect("parse should succeed");
@ -2404,6 +2031,7 @@ mod tests {
assert_eq!(opt.count, 3);
}
#[cfg(feature = "plugin")]
#[test]
fn parse_plugin_gid_map_valid_shorthand() {
let opt: GidMap = "1".parse().expect("parse should succeed");
@ -2422,6 +2050,7 @@ mod tests {
assert_eq!(opt.count, 3);
}
#[cfg(feature = "plugin")]
#[test]
fn parse_plugin_gid_map_invalid() {
"".parse::<GidMap>().expect_err("parse should fail");
@ -2439,243 +2068,6 @@ mod tests {
.expect_err("parse should fail because count is not a number");
}
#[test]
fn single_touch_spec_and_track_pad_spec_default_size() {
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--single-touch",
"/dev/single-touch-test",
"--trackpad",
"/dev/single-touch-test",
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
assert_eq!(
config.virtio_single_touch.first().unwrap().get_size(),
(DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
);
assert_eq!(
config.virtio_trackpad.first().unwrap().get_size(),
(DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
);
}
#[cfg(feature = "gpu")]
#[test]
fn single_touch_spec_default_size_from_gpu() {
let width = 12345u32;
let height = 54321u32;
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--single-touch",
"/dev/single-touch-test",
"--gpu",
&format!("width={},height={}", width, height),
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
assert_eq!(
config.virtio_single_touch.first().unwrap().get_size(),
(width, height)
);
}
#[test]
fn single_touch_spec_and_track_pad_spec_with_size() {
let width = 12345u32;
let height = 54321u32;
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--single-touch",
&format!("/dev/single-touch-test:{}:{}", width, height),
"--trackpad",
&format!("/dev/single-touch-test:{}:{}", width, height),
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
assert_eq!(
config.virtio_single_touch.first().unwrap().get_size(),
(width, height)
);
assert_eq!(
config.virtio_trackpad.first().unwrap().get_size(),
(width, height)
);
}
#[cfg(feature = "gpu")]
#[test]
fn single_touch_spec_with_size_independent_from_gpu() {
let touch_width = 12345u32;
let touch_height = 54321u32;
let display_width = 1234u32;
let display_height = 5432u32;
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--single-touch",
&format!("/dev/single-touch-test:{}:{}", touch_width, touch_height),
"--gpu",
&format!("width={},height={}", display_width, display_height),
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
assert_eq!(
config.virtio_single_touch.first().unwrap().get_size(),
(touch_width, touch_height)
);
}
#[test]
fn virtio_switches() {
let mut config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&["--switches", "/dev/switches-test", "/dev/null"],
)
.unwrap()
.try_into()
.unwrap();
assert_eq!(
config.virtio_switches.pop().unwrap(),
PathBuf::from("/dev/switches-test")
);
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_default_vulkan_support() {
{
let gpu_params: GpuParameters = parse_gpu_options("backend=virglrenderer").unwrap();
assert!(!gpu_params.use_vulkan);
}
#[cfg(feature = "gfxstream")]
{
let gpu_params: GpuParameters = parse_gpu_options("backend=gfxstream").unwrap();
assert!(gpu_params.use_vulkan);
}
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_with_vulkan_specified() {
{
let gpu_params: GpuParameters = parse_gpu_options("vulkan=true").unwrap();
assert!(gpu_params.use_vulkan);
}
{
let gpu_params: GpuParameters =
parse_gpu_options("backend=virglrenderer,vulkan=true").unwrap();
assert!(gpu_params.use_vulkan);
}
{
let gpu_params: GpuParameters =
parse_gpu_options("vulkan=true,backend=virglrenderer").unwrap();
assert!(gpu_params.use_vulkan);
}
{
let gpu_params: GpuParameters = parse_gpu_options("vulkan=false").unwrap();
assert!(!gpu_params.use_vulkan);
}
{
let gpu_params: GpuParameters =
parse_gpu_options("backend=virglrenderer,vulkan=false").unwrap();
assert!(!gpu_params.use_vulkan);
}
{
let gpu_params: GpuParameters =
parse_gpu_options("vulkan=false,backend=virglrenderer").unwrap();
assert!(!gpu_params.use_vulkan);
}
{
assert!(parse_gpu_options("backend=virglrenderer,vulkan=invalid_value").is_err());
}
{
assert!(parse_gpu_options("vulkan=invalid_value,backend=virglrenderer").is_err());
}
}
#[cfg(all(feature = "gpu", feature = "gfxstream"))]
#[test]
fn parse_gpu_options_gfxstream_with_syncfd_specified() {
{
let gpu_params: GpuParameters =
parse_gpu_options("backend=gfxstream,syncfd=true").unwrap();
assert!(gpu_params.gfxstream_use_syncfd);
}
{
let gpu_params: GpuParameters =
parse_gpu_options("syncfd=true,backend=gfxstream").unwrap();
assert!(gpu_params.gfxstream_use_syncfd);
}
{
let gpu_params: GpuParameters =
parse_gpu_options("backend=gfxstream,syncfd=false").unwrap();
assert!(!gpu_params.gfxstream_use_syncfd);
}
{
let gpu_params: GpuParameters =
parse_gpu_options("syncfd=false,backend=gfxstream").unwrap();
assert!(!gpu_params.gfxstream_use_syncfd);
}
{
assert!(parse_gpu_options("backend=gfxstream,syncfd=invalid_value").is_err());
}
{
assert!(parse_gpu_options("syncfd=invalid_value,backend=gfxstream").is_err());
}
}
#[cfg(all(feature = "gpu", feature = "gfxstream"))]
#[test]
fn parse_gpu_options_not_gfxstream_with_syncfd_specified() {
{
assert!(parse_gpu_options("backend=virglrenderer,syncfd=true").is_err());
}
{
assert!(parse_gpu_options("syncfd=true,backend=virglrenderer").is_err());
}
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_gfxstream_with_wsi_specified() {
use rutabaga_gfx::RutabagaWsi;
let gpu_params: GpuParameters = parse_gpu_options("backend=virglrenderer,wsi=vk").unwrap();
assert!(matches!(gpu_params.wsi, Some(RutabagaWsi::Vulkan)));
let gpu_params: GpuParameters = parse_gpu_options("wsi=vk,backend=virglrenderer").unwrap();
assert!(matches!(gpu_params.wsi, Some(RutabagaWsi::Vulkan)));
assert!(parse_gpu_options("backend=virglrenderer,wsi=invalid_value").is_err());
assert!(parse_gpu_options("wsi=invalid_value,backend=virglrenderer").is_err());
}
#[test]
fn parse_battery_vaild() {
parse_battery_options("type=goldfish").expect("parse should have succeded");
@ -2838,81 +2230,4 @@ mod tests {
assert!(parse_userspace_msr_options("0x10").is_err());
assert!(parse_userspace_msr_options("hoge").is_err());
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_display_options_valid() {
{
let gpu_params: GpuDisplayParameters =
parse_gpu_display_options("width=500,height=600").unwrap();
assert_eq!(gpu_params.width, 500);
assert_eq!(gpu_params.height, 600);
}
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_display_options_invalid() {
{
assert!(parse_gpu_display_options("width=500").is_err());
}
{
assert!(parse_gpu_display_options("height=500").is_err());
}
{
assert!(parse_gpu_display_options("width").is_err());
}
{
assert!(parse_gpu_display_options("blah").is_err());
}
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_and_gpu_display_options_valid() {
{
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--gpu",
"2D,width=500,height=600",
"--gpu-display",
"width=700,height=800",
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
let gpu_params = config.gpu_parameters.unwrap();
assert_eq!(gpu_params.displays.len(), 2);
assert_eq!(gpu_params.displays[0].width, 500);
assert_eq!(gpu_params.displays[0].height, 600);
assert_eq!(gpu_params.displays[1].width, 700);
assert_eq!(gpu_params.displays[1].height, 800);
}
{
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--gpu",
"2D",
"--gpu-display",
"width=700,height=800",
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
let gpu_params = config.gpu_parameters.unwrap();
assert_eq!(gpu_params.displays.len(), 1);
assert_eq!(gpu_params.displays[0].width, 700);
assert_eq!(gpu_params.displays[0].height, 800);
}
}
}

View file

@ -1201,6 +1201,10 @@ pub fn run_config(cfg: Config) -> Result<ExitState> {
if components.hugepages {
mem_policy |= MemoryPolicy::USE_HUGEPAGES;
}
if cfg.lock_guest_memory {
mem_policy |= MemoryPolicy::LOCK_GUEST_MEMORY;
}
guest_mem.set_memory_policy(mem_policy);
if cfg.kvm_device_path.exists() {
@ -1986,6 +1990,7 @@ fn run_control<V: VmArch + 'static, Vcpu: VcpuArch + 'static>(
cfg.host_cpu_topology,
cfg.enable_pnp_data,
cfg.itmt,
cfg.force_calibrated_tsc_leaf,
cfg.privileged_vm,
match vcpu_cgroup_tasks_file {
None => None,

View file

@ -89,6 +89,45 @@ fn bus_io_handler(bus: &Bus) -> impl FnMut(IoParams) -> Option<[u8; 8]> + '_ {
}
}
/// Set the VCPU thread affinity and other per-thread scheduler properties.
/// This function will be called from each VCPU thread at startup.
pub fn set_vcpu_thread_scheduling(
vcpu_affinity: Vec<usize>,
enable_per_vm_core_scheduling: bool,
vcpu_cgroup_tasks_file: Option<File>,
run_rt: bool,
) -> anyhow::Result<()> {
if !vcpu_affinity.is_empty() {
if let Err(e) = set_cpu_affinity(vcpu_affinity) {
error!("Failed to set CPU affinity: {}", e);
}
}
if !enable_per_vm_core_scheduling {
// Do per-vCPU core scheduling by setting a unique cookie to each vCPU.
if let Err(e) = enable_core_scheduling() {
error!("Failed to enable core scheduling: {}", e);
}
}
// Move vcpu thread to cgroup
if let Some(mut f) = vcpu_cgroup_tasks_file {
f.write_all(base::gettid().to_string().as_bytes())
.context("failed to write vcpu tid to cgroup tasks")?;
}
if run_rt {
const DEFAULT_VCPU_RT_LEVEL: u16 = 6;
if let Err(e) = set_rt_prio_limit(u64::from(DEFAULT_VCPU_RT_LEVEL))
.and_then(|_| set_rt_round_robin(i32::from(DEFAULT_VCPU_RT_LEVEL)))
{
warn!("Failed to set vcpu to real time: {}", e);
}
}
Ok(())
}
// Sets up a vcpu and converts it into a runnable vcpu.
pub fn runnable_vcpu<V>(
cpu_id: usize,
@ -98,16 +137,13 @@ pub fn runnable_vcpu<V>(
vm: impl VmArch,
irq_chip: &mut dyn IrqChipArch,
vcpu_count: usize,
run_rt: bool,
vcpu_affinity: Vec<usize>,
no_smt: bool,
has_bios: bool,
use_hypervisor_signals: bool,
enable_per_vm_core_scheduling: bool,
host_cpu_topology: bool,
enable_pnp_data: bool,
itmt: bool,
vcpu_cgroup_tasks_file: Option<File>,
force_calibrated_tsc_leaf: bool,
) -> Result<(V, VcpuRunHandle)>
where
V: VcpuArch,
@ -132,12 +168,6 @@ where
.add_vcpu(cpu_id, &vcpu)
.context("failed to add vcpu to irq chip")?;
if !vcpu_affinity.is_empty() {
if let Err(e) = set_cpu_affinity(vcpu_affinity) {
error!("Failed to set CPU affinity: {}", e);
}
}
Arch::configure_vcpu(
&vm,
vm.get_hypervisor(),
@ -151,31 +181,10 @@ where
host_cpu_topology,
enable_pnp_data,
itmt,
force_calibrated_tsc_leaf,
)
.context("failed to configure vcpu")?;
if !enable_per_vm_core_scheduling {
// Do per-vCPU core scheduling by setting a unique cookie to each vCPU.
if let Err(e) = enable_core_scheduling() {
error!("Failed to enable core scheduling: {}", e);
}
}
// Move vcpu thread to cgroup
if let Some(mut f) = vcpu_cgroup_tasks_file {
f.write_all(base::gettid().to_string().as_bytes())
.context("failed to write vcpu tid to cgroup tasks")?;
}
if run_rt {
const DEFAULT_VCPU_RT_LEVEL: u16 = 6;
if let Err(e) = set_rt_prio_limit(u64::from(DEFAULT_VCPU_RT_LEVEL))
.and_then(|_| set_rt_round_robin(i32::from(DEFAULT_VCPU_RT_LEVEL)))
{
warn!("Failed to set vcpu to real time: {}", e);
}
}
if use_hypervisor_signals {
let mut v = get_blocked_signals().context("failed to retrieve signal mask for vcpu")?;
v.retain(|&x| x != SIGRTMIN() + 0);
@ -568,6 +577,7 @@ pub fn run_vcpu<V>(
host_cpu_topology: bool,
enable_pnp_data: bool,
itmt: bool,
force_calibrated_tsc_leaf: bool,
privileged_vm: bool,
vcpu_cgroup_tasks_file: Option<File>,
userspace_msr: BTreeMap<u32, MsrConfig>,
@ -583,6 +593,16 @@ where
// send a VmEventType on all code paths after the closure
// returns.
let vcpu_fn = || -> ExitState {
if let Err(e) = set_vcpu_thread_scheduling(
vcpu_affinity,
enable_per_vm_core_scheduling,
vcpu_cgroup_tasks_file,
run_rt && !delay_rt,
) {
error!("vcpu thread setup failed: {:#}", e);
return ExitState::Stop;
}
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
let guest_mem = vm.get_memory().clone();
let runnable_vcpu = runnable_vcpu(
@ -593,16 +613,13 @@ where
vm,
irq_chip.as_mut(),
vcpu_count,
run_rt && !delay_rt,
vcpu_affinity,
no_smt,
has_bios,
use_hypervisor_signals,
enable_per_vm_core_scheduling,
host_cpu_topology,
enable_pnp_data,
itmt,
vcpu_cgroup_tasks_file,
force_calibrated_tsc_leaf,
);
// Add MSR handlers after CPU affinity setting.

View file

@ -6,7 +6,12 @@ use std::time::Duration;
use devices::SerialParameters;
#[cfg(feature = "gpu")]
use devices::virtio::{GpuDisplayParameters, GpuParameters};
use crate::crosvm::config::Config;
#[cfg(feature = "gpu")]
use crate::crosvm::{argument, argument::parse_hex_or_decimal, config::invalid_value_err};
#[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
pub fn parse_gpu_render_server_options(
@ -182,3 +187,712 @@ pub fn validate_config(cfg: &mut Config) -> std::result::Result<(), String> {
Ok(())
}
#[cfg(feature = "gpu")]
#[derive(Default)]
struct GpuDisplayParametersBuilder {
width: Option<u32>,
height: Option<u32>,
args: Vec<String>,
}
#[cfg(feature = "gpu")]
impl GpuDisplayParametersBuilder {
fn parse(&mut self, arg: &str) -> argument::Result<()> {
let mut kv = arg.split('=');
let k = kv.next().unwrap_or("");
let v = kv.next().unwrap_or("");
match k {
"width" => {
let width = v
.parse::<u32>()
.map_err(|_| argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'width' must be a valid integer"),
})?;
self.width = Some(width);
}
"height" => {
let height = v
.parse::<u32>()
.map_err(|_| argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'height' must be a valid integer"),
})?;
self.height = Some(height);
}
_ => {
return Err(argument::Error::UnknownArgument(format!(
"gpu-display parameter {}",
k
)))
}
}
self.args.push(arg.to_string());
Ok(())
}
fn build(&self) -> Result<Option<GpuDisplayParameters>, String> {
match (self.width, self.height) {
(None, None) => Ok(None),
(None, Some(_)) | (Some(_), None) => {
let mut value = self
.args
.clone()
.into_iter()
.fold(String::new(), |args_so_far, arg| args_so_far + &arg + ",");
value.pop();
return Err(invalid_value_err(
value,
"gpu must include both 'width' and 'height' if either is supplied",
));
}
(Some(width), Some(height)) => Ok(Some(GpuDisplayParameters { width, height })),
}
}
}
#[cfg(feature = "gpu")]
pub fn parse_gpu_options(s: &str) -> Result<GpuParameters, String> {
use devices::virtio::GpuMode;
use rutabaga_gfx::RutabagaWsi;
use crate::crosvm::sys::config::is_gpu_backend_deprecated;
#[cfg(feature = "gfxstream")]
let mut vulkan_specified = false;
#[cfg(feature = "gfxstream")]
let mut syncfd_specified = false;
#[cfg(feature = "gfxstream")]
let mut angle_specified = false;
let mut display_param_builder: GpuDisplayParametersBuilder = Default::default();
let mut gpu_params = GpuParameters::default();
for frag in s.split(',') {
let mut rest: Option<&str> = None;
let mut kv = frag.split('=');
let k = kv.next().unwrap_or("");
let v = kv.next().unwrap_or("");
match k {
// Deprecated: Specifying --gpu=<mode> Not great as the mode can be set multiple
// times if the user specifies several modes (--gpu=2d,virglrenderer,gfxstream)
"2d" | "2D" => {
gpu_params.mode = GpuMode::Mode2D;
}
"3d" | "3D" | "virglrenderer" => {
gpu_params.mode = GpuMode::ModeVirglRenderer;
}
#[cfg(feature = "gfxstream")]
"gfxstream" => {
gpu_params.mode = GpuMode::ModeGfxstream;
}
// Preferred: Specifying --gpu,backend=<mode>
"backend" => match v {
"2d" | "2D" => {
if is_gpu_backend_deprecated(v) {
return Err(invalid_value_err(
v,
"this backend type is deprecated, please use gfxstream.",
));
} else {
gpu_params.mode = GpuMode::Mode2D;
}
}
"3d" | "3D" | "virglrenderer" => {
if is_gpu_backend_deprecated(v) {
return Err(invalid_value_err(
v,
"this backend type is deprecated, please use gfxstream.",
));
} else {
gpu_params.mode = GpuMode::ModeVirglRenderer;
}
}
#[cfg(feature = "gfxstream")]
"gfxstream" => {
gpu_params.mode = GpuMode::ModeGfxstream;
}
_ => {
return Err(invalid_value_err(
v,
#[cfg(feature = "gfxstream")]
"gpu parameter 'backend' should be one of (2d|virglrenderer|gfxstream)",
#[cfg(not(feature = "gfxstream"))]
"gpu parameter 'backend' should be one of (2d|3d)",
));
}
},
"egl" => match v {
"true" | "" => {
gpu_params.renderer_use_egl = true;
}
"false" => {
gpu_params.renderer_use_egl = false;
}
_ => {
return Err(invalid_value_err(
v,
"gpu parameter 'egl' should be a boolean",
));
}
},
"gles" => match v {
"true" | "" => {
gpu_params.renderer_use_gles = true;
}
"false" => {
gpu_params.renderer_use_gles = false;
}
_ => {
return Err(invalid_value_err(
v,
"gpu parameter 'gles' should be a boolean",
));
}
},
"glx" => match v {
"true" | "" => {
gpu_params.renderer_use_glx = true;
}
"false" => {
gpu_params.renderer_use_glx = false;
}
_ => {
return Err(invalid_value_err(
v,
"gpu parameter 'glx' should be a boolean",
));
}
},
"surfaceless" => match v {
"true" | "" => {
gpu_params.renderer_use_surfaceless = true;
}
"false" => {
gpu_params.renderer_use_surfaceless = false;
}
_ => {
return Err(invalid_value_err(
v,
"gpu parameter 'surfaceless' should be a boolean",
));
}
},
#[cfg(feature = "gfxstream")]
"syncfd" => {
syncfd_specified = true;
match v {
"true" | "" => {
gpu_params.gfxstream_use_syncfd = true;
}
"false" => {
gpu_params.gfxstream_use_syncfd = false;
}
_ => {
return Err(argument::Error::InvalidValue {
value: v.to_string(),
expected: String::from("gpu parameter 'syncfd' should be a boolean"),
});
}
}
}
#[cfg(feature = "gfxstream")]
"angle" => {
angle_specified = true;
match v {
"true" | "" => {
gpu_params.gfxstream_use_guest_angle = true;
}
"false" => {
gpu_params.gfxstream_use_guest_angle = false;
}
_ => {
return Err(invalid_value_err(
v,
"gpu parameter 'angle' should be a boolean",
));
}
}
}
"vulkan" => {
#[cfg(feature = "gfxstream")]
{
vulkan_specified = true;
}
match v {
"true" | "" => {
gpu_params.use_vulkan = true;
}
"false" => {
gpu_params.use_vulkan = false;
}
_ => {
return Err(invalid_value_err(
v,
"gpu parameter 'vulkan' should be a boolean",
));
}
}
}
"wsi" => match v {
"vk" => {
gpu_params.wsi = Some(RutabagaWsi::Vulkan);
}
_ => {
return Err(invalid_value_err(v, "gpu parameter 'wsi' should be vk"));
}
},
"cache-path" => gpu_params.cache_path = Some(v.to_string()),
"cache-size" => gpu_params.cache_size = Some(v.to_string()),
"pci-bar-size" => {
let size = parse_hex_or_decimal(v).map_err(|_| {
"gpu parameter `pci-bar-size` must be a valid hex or decimal value"
})?;
gpu_params.pci_bar_size = size;
}
"udmabuf" => match v {
"true" | "" => {
gpu_params.udmabuf = true;
}
"false" => {
gpu_params.udmabuf = false;
}
_ => {
return Err(invalid_value_err(
v,
"gpu parameter 'udmabuf' should be a boolean",
));
}
},
"context-types" => {
let context_types: Vec<String> = v.split(':').map(|s| s.to_string()).collect();
gpu_params.context_mask = rutabaga_gfx::calculate_context_mask(context_types);
}
"" => {}
_ => {
rest = Some(frag);
}
}
if let Some(arg) = rest.take() {
match display_param_builder.parse(arg) {
Ok(()) => {}
Err(argument::Error::UnknownArgument(_)) => {
rest = Some(arg);
}
Err(err) => return Err(err.to_string()),
}
}
if let Some(arg) = rest.take() {
return Err(format!("unknown gpu parameter {}", arg));
}
}
if let Some(display_param) = display_param_builder.build()?.take() {
gpu_params.displays.push(display_param);
}
#[cfg(feature = "gfxstream")]
{
if !vulkan_specified && gpu_params.mode == GpuMode::ModeGfxstream {
gpu_params.use_vulkan = sys::use_vulkan();
}
if syncfd_specified || angle_specified {
match gpu_params.mode {
GpuMode::ModeGfxstream => {}
_ => {
return Err(
"gpu parameter syncfd and angle are only supported for gfxstream backend"
.to_string(),
);
}
}
}
}
Ok(gpu_params)
}
#[cfg(feature = "gpu")]
pub fn parse_gpu_display_options(s: &str) -> Result<GpuDisplayParameters, String> {
let mut display_param_builder: GpuDisplayParametersBuilder = Default::default();
for arg in s.split(',') {
display_param_builder
.parse(arg)
.map_err(|e| e.to_string())?;
}
let display_param = display_param_builder.build()?;
let display_param = display_param.ok_or_else(|| {
invalid_value_err(s, "gpu-display must include both 'width' and 'height'")
})?;
Ok(display_param)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crosvm::config::{DEFAULT_TOUCH_DEVICE_HEIGHT, DEFAULT_TOUCH_DEVICE_WIDTH};
use argh::FromArgs;
use std::path::PathBuf;
#[cfg(feature = "audio")]
use crate::crosvm::config::parse_ac97_options;
use crate::crosvm::config::BindMount;
#[cfg(feature = "audio_cras")]
#[test]
fn parse_ac97_socket_type() {
parse_ac97_options("socket_type=unified").expect("parse should have succeded");
parse_ac97_options("socket_type=legacy").expect("parse should have succeded");
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_default_vulkan_support() {
{
let gpu_params: GpuParameters = parse_gpu_options("backend=virglrenderer").unwrap();
assert!(!gpu_params.use_vulkan);
}
#[cfg(feature = "gfxstream")]
{
let gpu_params: GpuParameters = parse_gpu_options("backend=gfxstream").unwrap();
assert!(gpu_params.use_vulkan);
}
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_with_vulkan_specified() {
{
let gpu_params: GpuParameters = parse_gpu_options("vulkan=true").unwrap();
assert!(gpu_params.use_vulkan);
}
{
let gpu_params: GpuParameters =
parse_gpu_options("backend=virglrenderer,vulkan=true").unwrap();
assert!(gpu_params.use_vulkan);
}
{
let gpu_params: GpuParameters =
parse_gpu_options("vulkan=true,backend=virglrenderer").unwrap();
assert!(gpu_params.use_vulkan);
}
{
let gpu_params: GpuParameters = parse_gpu_options("vulkan=false").unwrap();
assert!(!gpu_params.use_vulkan);
}
{
let gpu_params: GpuParameters =
parse_gpu_options("backend=virglrenderer,vulkan=false").unwrap();
assert!(!gpu_params.use_vulkan);
}
{
let gpu_params: GpuParameters =
parse_gpu_options("vulkan=false,backend=virglrenderer").unwrap();
assert!(!gpu_params.use_vulkan);
}
{
assert!(parse_gpu_options("backend=virglrenderer,vulkan=invalid_value").is_err());
}
{
assert!(parse_gpu_options("vulkan=invalid_value,backend=virglrenderer").is_err());
}
}
#[cfg(all(feature = "gpu", feature = "gfxstream"))]
#[test]
fn parse_gpu_options_gfxstream_with_syncfd_specified() {
{
let gpu_params: GpuParameters =
parse_gpu_options("backend=gfxstream,syncfd=true").unwrap();
assert!(gpu_params.gfxstream_use_syncfd);
}
{
let gpu_params: GpuParameters =
parse_gpu_options("syncfd=true,backend=gfxstream").unwrap();
assert!(gpu_params.gfxstream_use_syncfd);
}
{
let gpu_params: GpuParameters =
parse_gpu_options("backend=gfxstream,syncfd=false").unwrap();
assert!(!gpu_params.gfxstream_use_syncfd);
}
{
let gpu_params: GpuParameters =
parse_gpu_options("syncfd=false,backend=gfxstream").unwrap();
assert!(!gpu_params.gfxstream_use_syncfd);
}
{
assert!(parse_gpu_options("backend=gfxstream,syncfd=invalid_value").is_err());
}
{
assert!(parse_gpu_options("syncfd=invalid_value,backend=gfxstream").is_err());
}
}
#[cfg(all(feature = "gpu", feature = "gfxstream"))]
#[test]
fn parse_gpu_options_not_gfxstream_with_syncfd_specified() {
{
assert!(parse_gpu_options("backend=virglrenderer,syncfd=true").is_err());
}
{
assert!(parse_gpu_options("syncfd=true,backend=virglrenderer").is_err());
}
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_gfxstream_with_wsi_specified() {
use rutabaga_gfx::RutabagaWsi;
let gpu_params: GpuParameters = parse_gpu_options("backend=virglrenderer,wsi=vk").unwrap();
assert!(matches!(gpu_params.wsi, Some(RutabagaWsi::Vulkan)));
let gpu_params: GpuParameters = parse_gpu_options("wsi=vk,backend=virglrenderer").unwrap();
assert!(matches!(gpu_params.wsi, Some(RutabagaWsi::Vulkan)));
assert!(parse_gpu_options("backend=virglrenderer,wsi=invalid_value").is_err());
assert!(parse_gpu_options("wsi=invalid_value,backend=virglrenderer").is_err());
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_display_options_valid() {
{
let gpu_params: GpuDisplayParameters =
parse_gpu_display_options("width=500,height=600").unwrap();
assert_eq!(gpu_params.width, 500);
assert_eq!(gpu_params.height, 600);
}
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_display_options_invalid() {
{
assert!(parse_gpu_display_options("width=500").is_err());
}
{
assert!(parse_gpu_display_options("height=500").is_err());
}
{
assert!(parse_gpu_display_options("width").is_err());
}
{
assert!(parse_gpu_display_options("blah").is_err());
}
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_and_gpu_display_options_valid() {
{
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--gpu",
"2D,width=500,height=600",
"--gpu-display",
"width=700,height=800",
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
let gpu_params = config.gpu_parameters.unwrap();
assert_eq!(gpu_params.displays.len(), 2);
assert_eq!(gpu_params.displays[0].width, 500);
assert_eq!(gpu_params.displays[0].height, 600);
assert_eq!(gpu_params.displays[1].width, 700);
assert_eq!(gpu_params.displays[1].height, 800);
}
{
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--gpu",
"2D",
"--gpu-display",
"width=700,height=800",
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
let gpu_params = config.gpu_parameters.unwrap();
assert_eq!(gpu_params.displays.len(), 1);
assert_eq!(gpu_params.displays[0].width, 700);
assert_eq!(gpu_params.displays[0].height, 800);
}
}
#[cfg(feature = "audio")]
#[test]
fn parse_ac97_vios_valid() {
parse_ac97_options("backend=vios,server=/path/to/server")
.expect("parse should have succeded");
}
#[test]
fn parse_plugin_mount_valid() {
let opt: BindMount = "/dev/null:/dev/zero:true".parse().unwrap();
assert_eq!(opt.src, PathBuf::from("/dev/null"));
assert_eq!(opt.dst, PathBuf::from("/dev/zero"));
assert!(opt.writable);
}
#[test]
fn parse_plugin_mount_valid_shorthand() {
let opt: BindMount = "/dev/null".parse().unwrap();
assert_eq!(opt.dst, PathBuf::from("/dev/null"));
assert!(!opt.writable);
let opt: BindMount = "/dev/null:/dev/zero".parse().unwrap();
assert_eq!(opt.dst, PathBuf::from("/dev/zero"));
assert!(!opt.writable);
let opt: BindMount = "/dev/null::true".parse().unwrap();
assert_eq!(opt.dst, PathBuf::from("/dev/null"));
assert!(opt.writable);
}
#[test]
fn single_touch_spec_and_track_pad_spec_default_size() {
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--single-touch",
"/dev/single-touch-test",
"--trackpad",
"/dev/single-touch-test",
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
assert_eq!(
config.virtio_single_touch.first().unwrap().get_size(),
(DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
);
assert_eq!(
config.virtio_trackpad.first().unwrap().get_size(),
(DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
);
}
#[cfg(feature = "gpu")]
#[test]
fn single_touch_spec_default_size_from_gpu() {
let width = 12345u32;
let height = 54321u32;
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--single-touch",
"/dev/single-touch-test",
"--gpu",
&format!("width={},height={}", width, height),
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
assert_eq!(
config.virtio_single_touch.first().unwrap().get_size(),
(width, height)
);
}
#[test]
fn single_touch_spec_and_track_pad_spec_with_size() {
let width = 12345u32;
let height = 54321u32;
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--single-touch",
&format!("/dev/single-touch-test:{}:{}", width, height),
"--trackpad",
&format!("/dev/single-touch-test:{}:{}", width, height),
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
assert_eq!(
config.virtio_single_touch.first().unwrap().get_size(),
(width, height)
);
assert_eq!(
config.virtio_trackpad.first().unwrap().get_size(),
(width, height)
);
}
#[cfg(feature = "gpu")]
#[test]
fn single_touch_spec_with_size_independent_from_gpu() {
let touch_width = 12345u32;
let touch_height = 54321u32;
let display_width = 1234u32;
let display_height = 5432u32;
let config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&[
"--single-touch",
&format!("/dev/single-touch-test:{}:{}", touch_width, touch_height),
"--gpu",
&format!("width={},height={}", display_width, display_height),
"/dev/null",
],
)
.unwrap()
.try_into()
.unwrap();
assert_eq!(
config.virtio_single_touch.first().unwrap().get_size(),
(touch_width, touch_height)
);
}
#[test]
fn virtio_switches() {
let mut config: Config = crate::crosvm::cmdline::RunCommand::from_args(
&[],
&["--switches", "/dev/switches-test", "/dev/null"],
)
.unwrap()
.try_into()
.unwrap();
assert_eq!(
config.virtio_switches.pop().unwrap(),
PathBuf::from("/dev/switches-test")
);
}
}

View file

@ -11,6 +11,7 @@ use crate::{Error, GuestAddress, GuestMemory, Result};
bitflags! {
pub struct MemoryPolicy: u32 {
const USE_HUGEPAGES = 1;
const LOCK_GUEST_MEMORY = (1 << 1);
}
}
@ -41,14 +42,31 @@ impl GuestMemory {
/// Handles guest memory policy hints/advices.
pub fn set_memory_policy(&self, mem_policy: MemoryPolicy) {
if mem_policy.contains(MemoryPolicy::USE_HUGEPAGES) {
for (_, region) in self.regions.iter().enumerate() {
if mem_policy.is_empty() {
return;
}
for (_, region) in self.regions.iter().enumerate() {
if mem_policy.contains(MemoryPolicy::USE_HUGEPAGES) {
let ret = region.mapping.use_hugepages();
if let Err(err) = ret {
println!("Failed to enable HUGEPAGE for mapping {}", err);
}
}
if mem_policy.contains(MemoryPolicy::LOCK_GUEST_MEMORY) {
// This is done in coordination with remove_range() calls, which are
// performed by the virtio-balloon process (they must be performed by
// a different process from the one that issues the locks).
// We also prevent this from happening in single-process configurations,
// when we compute configuration flags.
let ret = region.mapping.lock_all();
if let Err(err) = ret {
println!("Failed to lock memory for mapping {}", err);
}
}
}
}
}

View file

@ -7,7 +7,7 @@ use std::cmp;
use std::result;
use devices::{Apic, IrqChipCap, IrqChipX86_64};
use hypervisor::{CpuIdEntry, HypervisorX86_64, VcpuX86_64};
use hypervisor::{CpuIdEntry, HypervisorCap, HypervisorX86_64, VcpuX86_64};
use crate::CpuManufacturer;
use remain::sorted;
@ -63,6 +63,11 @@ pub struct CpuIdContext {
apic_frequency: u32,
/// The TSC frequency in Hz, if it could be determined.
tsc_frequency: Option<u64>,
/// Whether to force the use of a calibrated TSC cpuid leaf (0x15) even if
/// the hypervisor doesn't require it.
force_calibrated_tsc_leaf: bool,
/// Whether the hypervisor requires a calibrated TSC cpuid leaf (0x15).
calibrated_tsc_leaf_required: bool,
/// Whether or not VCPU IDs and APIC IDs should match host cpu IDs.
host_cpu_topology: bool,
enable_pnp_data: bool,
@ -83,6 +88,8 @@ impl CpuIdContext {
irq_chip: Option<&dyn IrqChipX86_64>,
enable_pnp_data: bool,
itmt: bool,
force_calibrated_tsc_leaf: bool,
calibrated_tsc_leaf_required: bool,
cpuid_count: unsafe fn(u32, u32) -> CpuidResult,
cpuid: unsafe fn(u32) -> CpuidResult,
) -> CpuIdContext {
@ -96,6 +103,8 @@ impl CpuIdContext {
}),
apic_frequency: irq_chip.map_or(Apic::frequency(), |chip| chip.lapic_frequency()),
tsc_frequency: devices::tsc::tsc_frequency().ok(),
force_calibrated_tsc_leaf,
calibrated_tsc_leaf_required,
host_cpu_topology,
enable_pnp_data,
itmt,
@ -210,7 +219,18 @@ pub fn adjust_cpuid(entry: &mut CpuIdEntry, ctx: &CpuIdContext) {
}
}
0x15 => {
if ctx.enable_pnp_data {
if ctx.calibrated_tsc_leaf_required
|| ctx.force_calibrated_tsc_leaf {
let cpuid_15 = ctx
.tsc_frequency
.map(|tsc_freq| devices::tsc::fake_tsc_frequency_cpuid(
tsc_freq, ctx.apic_frequency));
if let Some(new_entry) = cpuid_15 {
entry.cpuid = new_entry.cpuid;
}
} else if ctx.enable_pnp_data {
// Expose TSC frequency to guest
// Safe because we pass 0x15 for this call and the host
// supports the `cpuid` instruction
@ -321,6 +341,7 @@ pub fn setup_cpuid(
host_cpu_topology: bool,
enable_pnp_data: bool,
itmt: bool,
force_calibrated_tsc_leaf: bool,
) -> Result<()> {
let mut cpuid = hypervisor
.get_supported_cpuid()
@ -336,6 +357,8 @@ pub fn setup_cpuid(
Some(irq_chip),
enable_pnp_data,
itmt,
force_calibrated_tsc_leaf,
hypervisor.check_capability(HypervisorCap::CalibratedTscLeafRequired),
__cpuid_count,
__cpuid,
),
@ -421,6 +444,8 @@ mod tests {
Some(&irq_chip),
false,
false,
false,
false,
__cpuid_count,
__cpuid,
),
@ -466,6 +491,8 @@ mod tests {
host_cpu_topology: true,
enable_pnp_data: false,
itmt: false,
force_calibrated_tsc_leaf: false,
calibrated_tsc_leaf_required: false,
cpuid_count: fake_cpuid_count,
cpuid: fake_cpuid,
};

View file

@ -770,6 +770,7 @@ impl arch::LinuxArch for X8664arch {
host_cpu_topology: bool,
enable_pnp_data: bool,
itmt: bool,
force_calibrated_tsc_leaf: bool,
) -> Result<()> {
if !vm.check_capability(VmCap::EarlyInitCpuid) {
cpuid::setup_cpuid(
@ -782,6 +783,7 @@ impl arch::LinuxArch for X8664arch {
host_cpu_topology,
enable_pnp_data,
itmt,
force_calibrated_tsc_leaf,
)
.map_err(Error::SetupCpuid)?;
}

View file

@ -240,7 +240,10 @@ where
.expect("failed to add vcpu to irqchip");
if !vm.check_capability(VmCap::EarlyInitCpuid) {
setup_cpuid(&hyp, &irq_chip, &vcpu, 0, 1, false, false, false, false).unwrap();
setup_cpuid(
&hyp, &irq_chip, &vcpu, 0, 1, false, false, false, false, false,
)
.unwrap();
}
setup_msrs(&vm, &vcpu, read_pci_mmio_before_32bit().start).unwrap();