mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-28 17:44:10 +00:00
Merge with upstream 2022-06-30 1/2
517bfb7a
crosvm_control: Add link to library docs1c80e062
crosvm: Remove --no-legacy flag.88b3f563
crosvm: move gpu arg parsing related code into sys/unixed071b6e
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 mods4f902ffc
ffmpeg: specify supported resolution range for input formats8c60b644
vcpu: refactor scheduler config into a new function1414622073..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:
commit
f5dfac0307
20 changed files with 979 additions and 775 deletions
|
@ -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(())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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(),
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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)?;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in a new issue