Merge with main 2022-05-16

8958e112 crosvm: Sort struct Config default initializers.
90f281c1 crosvm: Use /dev/zero instead of /dev/mem
cc3fcb8e crosvm: carve-out unix specific parts of parse_ac97_options
4281630b crosvm: carve-out unix specific parts of start_device
e3cae727 crosvm: drop a few useless legacy I/O ports
5b3cfb91 crosvm: carve-out unix specific set and get arguments
a6945f4a aarch64: Add support for loading kernel image formatted as ELF
646de85e gfxstream: fix fence_handler not stored in fencing cookie
eb0cf097 devices: vhost_user: Split up system specific code in fs device
bb30f23c hypervisor: Upstream traits changes for Vm
1b27aef2 x86_64: Add parameter to speicify pcie cfg mmio base and size
f0e09415 rutabaga_gfx: silence unused_mut warning for non-virgl builds
96e50eb7 base: return SmallVec directly from platform wait impls
65b3c0fe devices: Upstream Windows vhost-user block vmm code
0e5ee848 devices: Upstream Windows vhost-user block device

c3c170f780..8958e11283

BUG=b:223443221
BUG=b:227659915
BUG=b:227364312
BUG=b:229902431
BUG=b:213153157
BUG=b:213150327
BUG=b:197877871
BUG=b:232360323
BUG=b:228051513,b:175527587

Change-Id: I16b3d0de446a7c0841686a0357b1090e8f1cc40c
This commit is contained in:
Crosvm Bot 2022-05-16 07:01:05 -07:00
commit f421479729
34 changed files with 1424 additions and 976 deletions

View file

@ -10,6 +10,7 @@ data_model = { path = "../common/data_model" }
devices = { path = "../devices" }
hypervisor = { path = "../hypervisor" }
kernel_cmdline = { path = "../kernel_cmdline" }
kernel_loader = { path = "../kernel_loader" }
kvm = { path = "../kvm" }
kvm_sys = { path = "../kvm_sys" }
libc = "*"

View file

@ -155,6 +155,8 @@ pub enum Error {
InitrdLoadFailure(arch::LoadImageError),
#[error("kernel could not be loaded: {0}")]
KernelLoadFailure(arch::LoadImageError),
#[error("error loading Kernel from Elf image: {0}")]
LoadElfKernel(kernel_loader::Error),
#[error("failed to map arm pvtime memory: {0}")]
MapPvtimeError(base::Error),
#[error("failed to protect vm: {0}")]
@ -262,10 +264,18 @@ impl arch::LinuxArch for AArch64 {
.map_err(Error::BiosLoadFailure)?
}
VmImage::Kernel(ref mut kernel_image) => {
let kernel_size =
arch::load_image(&mem, kernel_image, get_kernel_addr(), u64::max_value())
.map_err(Error::KernelLoadFailure)?;
let kernel_end = get_kernel_addr().offset() + kernel_size as u64;
let kernel_end: u64;
let kernel_size: usize;
let elf_result = kernel_loader::load_kernel(&mem, get_kernel_addr(), kernel_image);
if elf_result == Err(kernel_loader::Error::InvalidElfMagicNumber) {
kernel_size =
arch::load_image(&mem, kernel_image, get_kernel_addr(), u64::max_value())
.map_err(Error::KernelLoadFailure)?;
kernel_end = get_kernel_addr().offset() + kernel_size as u64;
} else {
kernel_end = elf_result.map_err(Error::LoadElfKernel)?;
kernel_size = kernel_end as usize - get_kernel_addr().offset() as usize;
}
initrd = match components.initrd_image {
Some(initrd_file) => {
let mut initrd_file = initrd_file;

View file

@ -48,6 +48,7 @@ use {
use {
devices::IrqChipX86_64 as IrqChipArch,
hypervisor::{HypervisorX86_64 as HypervisorArch, VcpuX86_64 as VcpuArch, VmX86_64 as VmArch},
resources::MemRegion,
};
pub use serial::{
@ -111,6 +112,8 @@ pub struct VmComponents {
/// A file to load as pVM firmware. Must be `Some` iff
/// `protected_vm == ProtectionType::UnprotectedWithFirmware`.
pub pvm_fw: Option<File>,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub pcie_ecam: Option<MemRegion>,
}
/// Holds the elements needed to run a Linux VM. Created by `build_vm`.

View file

@ -696,7 +696,7 @@ impl MemoryMapping {
)
};
if ret < 0 {
Err(Error::InvalidRange(mem_offset, count, self.size()))
Err(Error::SystemCallFailed(super::Error::last()))
} else {
Ok(())
}

View file

@ -18,9 +18,12 @@ use libc::{
EPOLLRDHUP, EPOLL_CLOEXEC, EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD,
};
use log::warn;
use smallvec::SmallVec;
use super::{errno_result, Result};
use crate::{AsRawDescriptor, EventType, FromRawDescriptor, IntoRawDescriptor, RawDescriptor};
use crate::{
AsRawDescriptor, EventType, FromRawDescriptor, IntoRawDescriptor, RawDescriptor, TriggeredEvent,
};
const POLL_CONTEXT_MAX_EVENTS: usize = 16;
@ -188,16 +191,6 @@ pub struct PollEvents<'a, T> {
}
impl<'a, T: PollToken> PollEvents<'a, T> {
/// Copies the events to an owned structure so the reference to this (and by extension
/// `PollContext`) can be dropped.
pub fn to_owned(&self) -> PollEventsOwned<T> {
PollEventsOwned {
count: self.count,
events: RefCell::new(*self.events),
tokens: PhantomData,
}
}
/// Iterates over each event.
pub fn iter(&self) -> PollEventIter<slice::Iter<epoll_event>, T> {
PollEventIter {
@ -244,24 +237,6 @@ impl<'a, T: PollToken> IntoIterator for &'a PollEvents<'_, T> {
}
}
/// A deep copy of the event records from `PollEvents`.
pub struct PollEventsOwned<T> {
count: usize,
events: RefCell<[epoll_event; POLL_CONTEXT_MAX_EVENTS]>,
tokens: PhantomData<T>, // Needed to satisfy usage of T
}
impl<T: PollToken> PollEventsOwned<T> {
/// Takes a reference to the events so that they can be iterated via methods in `PollEvents`.
pub fn as_ref(&self) -> PollEvents<T> {
PollEvents {
count: self.count,
events: self.events.borrow(),
tokens: PhantomData,
}
}
}
/// Watching events taken by PollContext.
pub struct WatchingEvents(u32);
@ -506,25 +481,7 @@ impl<T: PollToken> IntoRawDescriptor for EpollContext<T> {
/// Used to poll multiple objects that have file descriptors.
///
/// # Example
///
/// ```
/// # use base::platform::{Result, Event, PollContext, PollEvents};
/// # fn test() -> Result<()> {
/// let evt1 = Event::new()?;
/// let evt2 = Event::new()?;
/// evt2.write(1)?;
///
/// let ctx: PollContext<u32> = PollContext::new()?;
/// ctx.add(&evt1, 1)?;
/// ctx.add(&evt2, 2)?;
///
/// let pollevents: PollEvents<u32> = ctx.wait()?;
/// let tokens: Vec<u32> = pollevents.iter_readable().map(|e| e.token()).collect();
/// assert_eq!(&tokens[..], &[2]);
/// # Ok(())
/// # }
/// ```
/// See [`crate::WaitContext`] for an example that uses the cross-platform wrapper.
pub struct PollContext<T> {
epoll_ctx: EpollContext<T>,
@ -679,10 +636,7 @@ impl<T: PollToken> PollContext<T> {
/// return immediately. The consequence of not handling an event perpetually while calling
/// `wait` is that the callers loop will degenerated to busy loop polling, pinning a CPU to
/// ~100% usage.
///
/// # Panics
/// Panics if the returned `PollEvents` structure is not dropped before subsequent `wait` calls.
pub fn wait(&self) -> Result<PollEvents<T>> {
pub fn wait(&self) -> Result<SmallVec<[TriggeredEvent<T>; 16]>> {
self.wait_timeout(Duration::new(i64::MAX as u64, 0))
}
@ -690,11 +644,19 @@ impl<T: PollToken> PollContext<T> {
///
/// This may return earlier than `timeout` with zero events if the duration indicated exceeds
/// system limits.
pub fn wait_timeout(&self, timeout: Duration) -> Result<PollEvents<T>> {
pub fn wait_timeout(&self, timeout: Duration) -> Result<SmallVec<[TriggeredEvent<T>; 16]>> {
let events = self.epoll_ctx.wait_timeout(&self.events, timeout)?;
let hangups = events.iter_hungup().count();
self.check_for_hungup_busy_loop(hangups);
Ok(events)
Ok(events
.iter()
.map(|event| TriggeredEvent {
token: event.token(),
is_readable: event.readable(),
is_writable: event.writable(),
is_hungup: event.hungup(),
})
.collect())
}
}
@ -726,9 +688,9 @@ mod tests {
let mut evt_count = 0;
while evt_count < 2 {
for event in ctx.wait().unwrap().iter_readable() {
for event in ctx.wait().unwrap().iter().filter(|e| e.is_readable) {
evt_count += 1;
match event.token() {
match event.token {
1 => {
evt1.read().unwrap();
ctx.delete(&evt1).unwrap();
@ -757,8 +719,8 @@ mod tests {
}
let mut evt_count = 0;
while evt_count < EVT_COUNT {
for event in ctx.wait().unwrap().iter_readable() {
evts[event.token()].read().unwrap();
for event in ctx.wait().unwrap().iter().filter(|e| e.is_readable) {
evts[event.token].read().unwrap();
evt_count += 1;
}
}

View file

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::{clone::Clone, default::Default, marker::Copy};
pub use super::wait::*;
use super::{PollToken, RawDescriptor};
use crate::descriptor::AsRawDescriptor;
@ -34,48 +32,6 @@ impl<T: PollToken> Clone for EventTrigger<T> {
}
}
/// Represents an event that has been signaled and waited for via a wait function.
#[derive(Copy, Clone, Debug)]
pub struct TriggeredEvent<T: PollToken> {
pub token: T,
pub is_readable: bool,
pub is_writable: bool,
pub is_hungup: bool,
}
impl<T: PollToken> Default for TriggeredEvent<T> {
fn default() -> Self {
TriggeredEvent {
token: T::from_raw_token(0),
is_readable: false,
is_writable: false,
is_hungup: false,
}
}
}
impl<T: PollToken> TriggeredEvent<T> {
/// Gets the token associated in `PollContext::add` with this event.
pub fn token(&self) -> T {
T::from_raw_token(self.token.as_raw_token())
}
/// True if the `fd` associated with this token in `PollContext::add` is readable.
pub fn readable(&self) -> bool {
self.is_readable
}
/// True if the `fd` associated with this token in `PollContext::add` is writable.
pub fn writable(&self) -> bool {
self.is_writable
}
/// True if the `fd` associated with this token in `PollContext::add` has been hungup on.
pub fn hungup(&self) -> bool {
self.is_hungup
}
}
#[cfg(test)]
mod tests {
use super::{super::Event, *};

View file

@ -4,6 +4,7 @@
use std::{cmp::min, collections::HashMap, os::windows::io::RawHandle, sync::Arc, time::Duration};
use smallvec::SmallVec;
use sync::Mutex;
use winapi::{
shared::{
@ -13,9 +14,10 @@ use winapi::{
um::{synchapi::WaitForMultipleObjects, winbase::WAIT_OBJECT_0},
};
use super::{errno_result, Error, Event, EventTrigger, PollToken, Result, TriggeredEvent};
use super::{errno_result, Error, Event, EventTrigger, PollToken, Result};
use crate::descriptor::{AsRawDescriptor, Descriptor};
use crate::{error, EventToken, EventType, RawDescriptor, WaitContext};
use crate::{error, EventToken, EventType, RawDescriptor, TriggeredEvent, WaitContext};
// MAXIMUM_WAIT_OBJECTS = 64
pub const MAXIMUM_WAIT_OBJECTS: usize = winapi::um::winnt::MAXIMUM_WAIT_OBJECTS as usize;
@ -165,11 +167,11 @@ impl<T: PollToken> EventContext<T> {
}
/// Waits for one or more of the registered triggers to become signaled.
pub fn wait(&self) -> Result<Vec<TriggeredEvent<T>>> {
pub fn wait(&self) -> Result<SmallVec<[TriggeredEvent<T>; 16]>> {
self.wait_timeout(Duration::new(i64::MAX as u64, 0))
}
pub fn wait_timeout(&self, timeout: Duration) -> Result<Vec<TriggeredEvent<T>>> {
pub fn wait_timeout(&self, timeout: Duration) -> Result<SmallVec<[TriggeredEvent<T>; 16]>> {
let raw_handles_list: Vec<RawHandle> = self
.registered_handles
.lock()
@ -212,7 +214,7 @@ impl<T: PollToken> EventContext<T> {
return self.wait_timeout(timeout);
}
let mut events_to_return: Vec<TriggeredEvent<T>> = vec![];
let mut events_to_return = SmallVec::<[TriggeredEvent<T>; 16]>::new();
// Multiple events may be triggered at once, but WaitForMultipleObjects will only return one.
// Once it returns, loop through the remaining triggers checking each to ensure they haven't
// also been triggered.
@ -257,7 +259,7 @@ impl<T: PollToken> EventContext<T> {
Ok(events_to_return)
}
WAIT_TIMEOUT => Ok(vec![]),
WAIT_TIMEOUT => Ok(Default::default()),
// Invalid cases. This is most likely an WAIT_FAILED, but anything not matched by the
// above is an error case.
_ => errno_result(),

View file

@ -146,19 +146,11 @@ impl<T: EventToken> WaitContext<T> {
pub fn wait(&self) -> Result<SmallVec<[TriggeredEvent<T>; 16]>> {
self.wait_timeout(Duration::new(i64::MAX as u64, 0))
}
/// Waits for one or more of the registered triggers to become signaled, failing if no triggers
/// are signaled before the designated timeout has elapsed.
pub fn wait_timeout(&self, timeout: Duration) -> Result<SmallVec<[TriggeredEvent<T>; 16]>> {
let events = self.0.wait_timeout(timeout)?;
Ok(events
.iter()
.map(|event| TriggeredEvent {
token: event.token(),
is_readable: event.readable(),
is_writable: event.writable(),
is_hungup: event.hungup(),
})
.collect())
self.0.wait_timeout(timeout)
}
}

View file

@ -2,31 +2,27 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
mod sys;
use std::cell::RefCell;
use std::fs::OpenOptions;
use std::rc::Rc;
use std::sync::{atomic::AtomicU64, atomic::Ordering, Arc};
use anyhow::{anyhow, bail, Context};
use argh::FromArgs;
use futures::future::{AbortHandle, Abortable};
use sync::Mutex;
use vmm_vhost::message::*;
use base::{warn, Event, Timer};
use cros_async::{sync::Mutex as AsyncMutex, EventAsync, Executor, TimerAsync};
use data_model::DataInit;
use disk::create_async_disk_file;
use hypervisor::ProtectionType;
use disk::AsyncDisk;
use futures::future::{AbortHandle, Abortable};
use sync::Mutex;
use vm_memory::GuestMemory;
use vmm_vhost::message::*;
use crate::virtio::block::asynchronous::{flush_disk, handle_queue};
use crate::virtio::block::*;
use crate::virtio::vhost::user::device::{
handler::{DeviceRequestHandler, Doorbell, VhostUserBackend},
vvu::pci::VvuPciDevice,
};
use crate::virtio::{self, base_features, block::sys::*, copy_config};
use crate::virtio::vhost::user::device::block::sys::start_device;
use crate::virtio::vhost::user::device::handler::{Doorbell, VhostUserBackend};
use crate::virtio::{self, block::sys::*, copy_config};
const QUEUE_SIZE: u16 = 256;
const NUM_QUEUES: u16 = 16;
@ -46,26 +42,14 @@ pub(crate) struct BlockBackend {
}
impl BlockBackend {
/// Creates a new block backend.
///
/// * `ex`: executor used to run this device task.
/// * `filename`: Name of the disk image file.
/// * `options`: Vector of file options.
/// - `read-only`
pub(crate) fn new(ex: &Executor, filename: &str, options: Vec<&str>) -> anyhow::Result<Self> {
let read_only = options.contains(&"read-only");
let sparse = false;
let block_size = 512;
let f = OpenOptions::new()
.read(true)
.write(!read_only)
.create(false)
.open(filename)
.context("Failed to open disk file")?;
let disk_image = create_async_disk_file(f).context("Failed to create async file")?;
let base_features = base_features(ProtectionType::Unprotected);
pub fn new_from_async_disk(
ex: &Executor,
async_image: Box<dyn AsyncDisk>,
base_features: u64,
read_only: bool,
sparse: bool,
block_size: u32,
) -> anyhow::Result<BlockBackend> {
if block_size % SECTOR_SIZE as u32 != 0 {
bail!(
"Block size {} is not a multiple of {}.",
@ -73,7 +57,7 @@ impl BlockBackend {
SECTOR_SIZE,
);
}
let disk_size = disk_image.get_len()?;
let disk_size = async_image.get_len()?;
if disk_size % block_size as u64 != 0 {
warn!(
"Disk size {} is not a multiple of block size {}; \
@ -87,8 +71,6 @@ impl BlockBackend {
let seg_max = get_seg_max(QUEUE_SIZE);
let async_image = disk_image.to_async_disk(ex)?;
let disk_size = Arc::new(AtomicU64::new(disk_size));
let disk_state = Rc::new(AsyncMutex::new(DiskState::new(
@ -115,7 +97,10 @@ impl BlockBackend {
let flush_timer_read = timer
.try_clone()
.context("Failed to clone flush_timer")
.and_then(|t| TimerAsync::new(t, ex).context("Failed to create an async timer"))?;
.and_then(|t| {
// TODO(b/228645507): Update code below to match B* once B* Timer is upstreamed.
TimerAsync::new(t, ex).context("Failed to create an async timer")
})?;
let flush_timer_armed = Rc::new(RefCell::new(false));
ex.spawn_local(flush_disk(
Rc::clone(&disk_state),
@ -243,60 +228,8 @@ impl VhostUserBackend for BlockBackend {
}
}
#[derive(FromArgs)]
#[argh(description = "")]
struct Options {
#[argh(
option,
description = "path and options of the disk file.",
arg_name = "PATH<:read-only>"
)]
file: String,
#[argh(option, description = "path to a vhost-user socket", arg_name = "PATH")]
socket: Option<String>,
#[argh(
option,
description = "VFIO-PCI device name (e.g. '0000:00:07.0')",
arg_name = "STRING"
)]
vfio: Option<String>,
}
/// Starts a vhost-user block device.
/// Returns an error if the given `args` is invalid or the device fails to run.
pub fn run_block_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
let opts = match Options::from_args(&[program_name], args) {
Ok(opts) => opts,
Err(e) => {
if e.status.is_err() {
bail!(e.output);
} else {
println!("{}", e.output);
}
return Ok(());
}
};
if !(opts.socket.is_some() ^ opts.vfio.is_some()) {
bail!("Exactly one of `--socket` or `--vfio` is required");
}
let ex = Executor::new().context("failed to create executor")?;
let mut fileopts = opts.file.split(":").collect::<Vec<_>>();
let filename = fileopts.remove(0);
let block = BlockBackend::new(&ex, filename, fileopts)?;
let handler = DeviceRequestHandler::new(block);
match (opts.socket, opts.vfio) {
(Some(socket), None) => {
// run_until() returns an Result<Result<..>> which the ? operator lets us flatten.
ex.run_until(handler.run(socket, &ex))?
}
(None, Some(device_name)) => {
let device = VvuPciDevice::new(device_name.as_str(), BlockBackend::MAX_QUEUE_NUM)?;
ex.run_until(handler.run_vvu(device, &ex))?
}
_ => unreachable!("Must be checked above"),
}
return start_device(program_name, args);
}

View file

@ -0,0 +1,15 @@
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cfg_if::cfg_if! {
if #[cfg(unix)] {
mod unix;
use unix as platform;
} else if #[cfg(windows)] {
mod windows;
use windows as platform;
}
}
pub(super) use platform::start_device;

View file

@ -0,0 +1,112 @@
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::fs::OpenOptions;
use anyhow::{bail, Context};
use argh::FromArgs;
use cros_async::Executor;
use disk::create_async_disk_file;
use hypervisor::ProtectionType;
use crate::virtio::base_features;
use crate::virtio::vhost::user::device::block::{BlockBackend, VhostUserBackend};
use crate::virtio::vhost::user::device::handler::DeviceRequestHandler;
use crate::virtio::vhost::user::device::vvu::pci::VvuPciDevice;
impl BlockBackend {
/// Creates a new block backend.
///
/// * `ex`: executor used to run this device task.
/// * `filename`: Name of the disk image file.
/// * `options`: Vector of file options.
/// - `read-only`
pub(in crate::virtio::vhost::user::device::block) fn new(
ex: &Executor,
filename: &str,
options: Vec<&str>,
) -> anyhow::Result<Self> {
let read_only = options.contains(&"read-only");
let sparse = false;
let block_size = 512;
let f = OpenOptions::new()
.read(true)
.write(!read_only)
.create(false)
.open(filename)
.context("Failed to open disk file")?;
let disk_image = create_async_disk_file(f).context("Failed to create async file")?;
let base_features = base_features(ProtectionType::Unprotected);
Self::new_from_async_disk(
ex,
disk_image.to_async_disk(ex)?,
base_features,
read_only,
sparse,
block_size,
)
}
}
#[derive(FromArgs)]
#[argh(description = "")]
struct Options {
#[argh(
option,
description = "path and options of the disk file.",
arg_name = "PATH<:read-only>"
)]
file: String,
#[argh(option, description = "path to a vhost-user socket", arg_name = "PATH")]
socket: Option<String>,
#[argh(
option,
description = "VFIO-PCI device name (e.g. '0000:00:07.0')",
arg_name = "STRING"
)]
vfio: Option<String>,
}
/// Starts a vhost-user block device.
/// Returns an error if the given `args` is invalid or the device fails to run.
pub(in crate::virtio::vhost::user::device::block) fn start_device(
program_name: &str,
args: &[&str],
) -> anyhow::Result<()> {
let opts = match Options::from_args(&[program_name], args) {
Ok(opts) => opts,
Err(e) => {
if e.status.is_err() {
bail!(e.output);
} else {
println!("{}", e.output);
}
return Ok(());
}
};
if !(opts.socket.is_some() ^ opts.vfio.is_some()) {
bail!("Exactly one of `--socket` or `--vfio` is required");
}
let ex = Executor::new().context("failed to create executor")?;
let mut fileopts = opts.file.split(":").collect::<Vec<_>>();
let filename = fileopts.remove(0);
let block = BlockBackend::new(&ex, filename, fileopts)?;
let handler = DeviceRequestHandler::new(block);
match (opts.socket, opts.vfio) {
(Some(socket), None) => {
// run_until() returns an Result<Result<..>> which the ? operator lets us flatten.
ex.run_until(handler.run(socket, &ex))?
}
(None, Some(device_name)) => {
let device = VvuPciDevice::new(device_name.as_str(), BlockBackend::MAX_QUEUE_NUM)?;
ex.run_until(handler.run_vvu(device, &ex))?
}
_ => unreachable!("Must be checked above"),
}
}

View file

@ -0,0 +1,149 @@
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::fs::File;
use std::fs::OpenOptions;
use std::os::windows::fs::OpenOptionsExt;
use anyhow::{bail, Context};
use argh::FromArgs;
use base::{enable_high_res_timers, info, Event, RawDescriptor};
use broker_ipc::{common_child_setup, CommonChildStartupArgs};
use cros_async::Executor;
use disk::{async_ok, AsyncDisk, SingleFileDisk};
use hypervisor::ProtectionType;
use tracing;
use tube_transporter::TubeToken;
use winapi::um::winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE};
use crate::virtio::base_features;
use crate::virtio::block::block::DiskOption;
use crate::virtio::vhost::user::device::block::BlockBackend;
use crate::virtio::vhost::user::device::handler::read_from_tube_transporter;
use crate::virtio::vhost::user::device::handler::DeviceRequestHandler;
impl BlockBackend {
pub(in crate::virtio::vhost::user::device::block) fn new_from_files(
ex: &Executor,
files: Vec<File>,
base_features: u64,
read_only: bool,
sparse: bool,
block_size: u32,
) -> anyhow::Result<BlockBackend> {
// Safe because the executor is initialized in main() below.
let disk = Box::new(SingleFileDisk::new_from_files(files, ex)?) as Box<dyn AsyncDisk>;
Self::new_from_async_disk(ex, disk, base_features, read_only, sparse, block_size)
}
}
/// Opens a disk image for use by the backend.
fn open_disk_file(disk_option: &DiskOption, take_write_lock: bool) -> anyhow::Result<File> {
let share_flags = if take_write_lock {
FILE_SHARE_READ
} else {
FILE_SHARE_READ | FILE_SHARE_WRITE
};
OpenOptions::new()
.read(true)
.write(!disk_option.read_only)
.share_mode(share_flags)
.open(&disk_option.path)
.context("Failed to open disk file")
}
#[derive(FromArgs)]
#[argh(description = "")]
struct Options {
#[argh(
option,
description = "pipe handle end for Tube Transporter",
arg_name = "HANDLE"
)]
bootstrap: usize,
}
pub(in crate::virtio::vhost::user::device::block) fn start_device(
program_name: &str,
args: &[&str],
) -> anyhow::Result<()> {
let opts = match Options::from_args(&[program_name], args) {
Ok(opts) => opts,
Err(e) => {
if e.status.is_err() {
bail!(e.output);
} else {
println!("{}", e.output);
}
return Ok(());
}
};
tracing::init();
base::syslog::init().context("failed to initialize syslog")?;
let raw_transport_tube = opts.bootstrap as RawDescriptor;
let mut tubes = read_from_tube_transporter(raw_transport_tube)?;
let vhost_user_tube = tubes.get_tube(TubeToken::VhostUser)?;
let _control_tube = tubes.get_tube(TubeToken::Control)?;
let bootstrap_tube = tubes.get_tube(TubeToken::Bootstrap)?;
let startup_args: CommonChildStartupArgs = bootstrap_tube.recv::<CommonChildStartupArgs>()?;
common_child_setup(startup_args)?;
let disk_option: DiskOption = bootstrap_tube.recv::<DiskOption>()?;
let exit_event = bootstrap_tube.recv::<Event>()?;
// TODO(b/213146388): Replace below with `broker_ipc::common_child_setup`
// once `src` directory is upstreamed.
let _raise_timer_resolution =
enable_high_res_timers().context("failed to set timer resolution")?;
info!("using {} IO handles.", disk_option.io_concurrency.get());
// We can only take the write lock if a single handle is used (otherwise we can't open multiple
// handles).
let take_write_lock = disk_option.io_concurrency.get() == 1;
let mut files = Vec::new();
for _ in 0..disk_option.io_concurrency.get() {
files.push(open_disk_file(&disk_option, take_write_lock)?);
}
if !async_ok(&files[0])? {
bail!("found non-raw image; only raw images are supported.");
}
let base_features = base_features(ProtectionType::Unprotected);
let ex = Executor::new().context("failed to create executor")?;
let block = BlockBackend::new_from_files(
&ex,
files,
base_features,
disk_option.read_only,
disk_option.sparse,
disk_option.block_size,
)?;
if sandbox::is_sandbox_target() {
sandbox::TargetServices::get()
.expect("failed to get target services")
.unwrap()
.lower_token();
}
// This is basically the event loop.
let handler = DeviceRequestHandler::new(block);
if let Err(e) = ex.run_until(handler.run(vhost_user_tube, exit_event, &ex)) {
bail!("error occurred: {}", e);
}
Ok(())
}

View file

@ -2,24 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::io;
use std::os::unix::io::AsRawFd;
use std::os::unix::net::UnixListener;
use std::path::{Path, PathBuf};
mod sys;
use sys::start_device;
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{anyhow, bail, Context};
use argh::FromArgs;
use base::{
error, get_max_open_files, warn, AsRawDescriptors, Event, RawDescriptor, Tube,
UnlinkUnixListener,
};
use base::{error, warn, AsRawDescriptors, Event, RawDescriptor, Tube};
use cros_async::{EventAsync, Executor};
use data_model::{DataInit, Le32};
use fuse::Server;
use futures::future::{AbortHandle, Abortable};
use hypervisor::ProtectionType;
use minijail::{self, Minijail};
use sync::Mutex;
use vm_memory::GuestMemory;
use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
@ -28,10 +25,7 @@ use crate::virtio;
use crate::virtio::copy_config;
use crate::virtio::fs::passthrough::PassthroughFs;
use crate::virtio::fs::{process_fs_queue, virtio_fs_config, FS_MAX_TAG_LEN};
use crate::virtio::vhost::user::device::handler::{
DeviceRequestHandler, Doorbell, VhostUserBackend,
};
use crate::virtio::vhost::user::device::vvu::pci::VvuPciDevice;
use crate::virtio::vhost::user::device::handler::{Doorbell, VhostUserBackend};
async fn handle_fs_queue(
mut queue: virtio::Queue,
@ -56,66 +50,6 @@ async fn handle_fs_queue(
}
}
fn default_uidmap() -> String {
let euid = unsafe { libc::geteuid() };
format!("{} {} 1", euid, euid)
}
fn default_gidmap() -> String {
let egid = unsafe { libc::getegid() };
format!("{} {} 1", egid, egid)
}
fn jail_and_fork(
mut keep_rds: Vec<RawDescriptor>,
dir_path: PathBuf,
uid_map: Option<String>,
gid_map: Option<String>,
) -> anyhow::Result<i32> {
// Create new minijail sandbox
let mut j = Minijail::new()?;
j.namespace_pids();
j.namespace_user();
j.namespace_user_disable_setgroups();
j.uidmap(&uid_map.unwrap_or_else(default_uidmap))?;
j.gidmap(&gid_map.unwrap_or_else(default_gidmap))?;
j.run_as_init();
j.namespace_vfs();
j.namespace_net();
j.no_new_privs();
// Only pivot_root if we are not re-using the current root directory.
if dir_path != Path::new("/") {
// It's safe to call `namespace_vfs` multiple times.
j.namespace_vfs();
j.enter_pivot_root(&dir_path)?;
}
j.set_remount_mode(libc::MS_SLAVE);
let limit = get_max_open_files().context("failed to get max open files")?;
j.set_rlimit(libc::RLIMIT_NOFILE as i32, limit, limit)?;
// vvu locks around 512k memory. Just give 1M.
j.set_rlimit(libc::RLIMIT_MEMLOCK as i32, 1 << 20, 1 << 20)?;
// Make sure there are no duplicates in keep_rds
keep_rds.dedup();
// fork on the jail here
let pid = unsafe { j.fork(Some(&keep_rds))? };
if pid > 0 {
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) };
}
if pid < 0 {
bail!("Fork error! {}", std::io::Error::last_os_error());
}
Ok(pid)
}
struct FsBackend {
ex: Executor,
server: Arc<fuse::Server<PassthroughFs>>,
@ -288,75 +222,6 @@ struct Options {
gid_map: Option<String>,
}
/// Starts a vhost-user fs device.
/// Returns an error if the given `args` is invalid or the device fails to run.
pub fn run_fs_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
let opts = match Options::from_args(&[program_name], args) {
Ok(opts) => opts,
Err(e) => {
if e.status.is_err() {
bail!(e.output);
} else {
println!("{}", e.output);
}
return Ok(());
}
};
base::syslog::init().context("Failed to initialize syslog")?;
let ex = Executor::new().context("Failed to create executor")?;
let fs_device = FsBackend::new(&ex, &opts.tag)?;
let mut keep_rds = fs_device.keep_rds.clone();
let listener = match opts.socket {
None => None,
Some(socket) => {
// Create and bind unix socket
let l = UnixListener::bind(socket).map(UnlinkUnixListener)?;
keep_rds.push(l.as_raw_fd());
Some(l)
}
};
base::syslog::push_descriptors(&mut keep_rds);
let handler = DeviceRequestHandler::new(fs_device);
let pid = jail_and_fork(keep_rds, opts.shared_dir, opts.uid_map, opts.gid_map)?;
// Parent, nothing to do but wait and then exit
if pid != 0 {
unsafe { libc::waitpid(pid, std::ptr::null_mut(), 0) };
return Ok(());
}
// We need to set the no setuid fixup secure bit so that we don't drop capabilities when
// changing the thread uid/gid. Without this, creating new entries can fail in some corner
// cases.
const SECBIT_NO_SETUID_FIXUP: i32 = 1 << 2;
// TODO(crbug.com/1199487): Remove this once libc provides the wrapper for all targets.
#[cfg(target_os = "linux")]
{
// Safe because this doesn't modify any memory and we check the return value.
let mut securebits = unsafe { libc::prctl(libc::PR_GET_SECUREBITS) };
if securebits < 0 {
bail!(io::Error::last_os_error());
}
securebits |= SECBIT_NO_SETUID_FIXUP;
// Safe because this doesn't modify any memory and we check the return value.
let ret = unsafe { libc::prctl(libc::PR_SET_SECUREBITS, securebits) };
if ret < 0 {
bail!(io::Error::last_os_error());
}
}
let res = match (listener, opts.vfio) {
(Some(l), None) => ex.run_until(handler.run_with_listener(l, &ex))?,
(None, Some(vfio)) => {
let device = VvuPciDevice::new(&vfio, FsBackend::MAX_QUEUE_NUM)?;
ex.run_until(handler.run_vvu(device, &ex))?
}
_ => Err(anyhow!("exactly one of `--socket` or `--vfio` is required")),
};
res
start_device(program_name, args)
}

View file

@ -0,0 +1,15 @@
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cfg_if::cfg_if! {
if #[cfg(unix)] {
mod unix;
use unix as platform;
} else if #[cfg(windows)] {
mod windows;
use windows as platform;
}
}
pub(in crate::virtio::vhost::user::device::fs) use platform::start_device;

View file

@ -0,0 +1,152 @@
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::io;
use std::os::unix::io::AsRawFd;
use std::os::unix::net::UnixListener;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, bail, Context};
use argh::FromArgs;
use base::{get_max_open_files, RawDescriptor, UnlinkUnixListener};
use cros_async::Executor;
use minijail::{self, Minijail};
use crate::virtio::vhost::user::device::fs::{FsBackend, Options};
use crate::virtio::vhost::user::device::handler::{DeviceRequestHandler, VhostUserBackend};
use crate::virtio::vhost::user::device::vvu::pci::VvuPciDevice;
fn default_uidmap() -> String {
let euid = unsafe { libc::geteuid() };
format!("{} {} 1", euid, euid)
}
fn default_gidmap() -> String {
let egid = unsafe { libc::getegid() };
format!("{} {} 1", egid, egid)
}
fn jail_and_fork(
mut keep_rds: Vec<RawDescriptor>,
dir_path: PathBuf,
uid_map: Option<String>,
gid_map: Option<String>,
) -> anyhow::Result<i32> {
// Create new minijail sandbox
let mut j = Minijail::new()?;
j.namespace_pids();
j.namespace_user();
j.namespace_user_disable_setgroups();
j.uidmap(&uid_map.unwrap_or_else(default_uidmap))?;
j.gidmap(&gid_map.unwrap_or_else(default_gidmap))?;
j.run_as_init();
j.namespace_vfs();
j.namespace_net();
j.no_new_privs();
// Only pivot_root if we are not re-using the current root directory.
if dir_path != Path::new("/") {
// It's safe to call `namespace_vfs` multiple times.
j.namespace_vfs();
j.enter_pivot_root(&dir_path)?;
}
j.set_remount_mode(libc::MS_SLAVE);
let limit = get_max_open_files().context("failed to get max open files")?;
j.set_rlimit(libc::RLIMIT_NOFILE as i32, limit, limit)?;
// vvu locks around 512k memory. Just give 1M.
j.set_rlimit(libc::RLIMIT_MEMLOCK as i32, 1 << 20, 1 << 20)?;
// Make sure there are no duplicates in keep_rds
keep_rds.dedup();
// fork on the jail here
let pid = unsafe { j.fork(Some(&keep_rds))? };
if pid > 0 {
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) };
}
if pid < 0 {
bail!("Fork error! {}", std::io::Error::last_os_error());
}
Ok(pid)
}
/// Starts a vhost-user fs device.
/// Returns an error if the given `args` is invalid or the device fails to run.
pub fn start_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
let opts = match Options::from_args(&[program_name], args) {
Ok(opts) => opts,
Err(e) => {
if e.status.is_err() {
bail!(e.output);
} else {
println!("{}", e.output);
}
return Ok(());
}
};
base::syslog::init().context("Failed to initialize syslog")?;
let ex = Executor::new().context("Failed to create executor")?;
let fs_device = FsBackend::new(&ex, &opts.tag)?;
let mut keep_rds = fs_device.keep_rds.clone();
let listener = match opts.socket {
None => None,
Some(socket) => {
// Create and bind unix socket
let l = UnixListener::bind(socket).map(UnlinkUnixListener)?;
keep_rds.push(l.as_raw_fd());
Some(l)
}
};
base::syslog::push_descriptors(&mut keep_rds);
let handler = DeviceRequestHandler::new(fs_device);
let pid = jail_and_fork(keep_rds, opts.shared_dir, opts.uid_map, opts.gid_map)?;
// Parent, nothing to do but wait and then exit
if pid != 0 {
unsafe { libc::waitpid(pid, std::ptr::null_mut(), 0) };
return Ok(());
}
// We need to set the no setuid fixup secure bit so that we don't drop capabilities when
// changing the thread uid/gid. Without this, creating new entries can fail in some corner
// cases.
const SECBIT_NO_SETUID_FIXUP: i32 = 1 << 2;
// TODO(crbug.com/1199487): Remove this once libc provides the wrapper for all targets.
#[cfg(target_os = "linux")]
{
// Safe because this doesn't modify any memory and we check the return value.
let mut securebits = unsafe { libc::prctl(libc::PR_GET_SECUREBITS) };
if securebits < 0 {
bail!(io::Error::last_os_error());
}
securebits |= SECBIT_NO_SETUID_FIXUP;
// Safe because this doesn't modify any memory and we check the return value.
let ret = unsafe { libc::prctl(libc::PR_SET_SECUREBITS, securebits) };
if ret < 0 {
bail!(io::Error::last_os_error());
}
}
let res = match (listener, opts.vfio) {
(Some(l), None) => ex.run_until(handler.run_with_listener(l, &ex))?,
(None, Some(vfio)) => {
let device = VvuPciDevice::new(&vfio, FsBackend::MAX_QUEUE_NUM)?;
ex.run_until(handler.run_vvu(device, &ex))?
}
_ => Err(anyhow!("exactly one of `--socket` or `--vfio` is required")),
};
res
}

View file

@ -0,0 +1,7 @@
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
pub fn start_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
unimplemented!("run_fs_device not implemented on Windows yet.")
}

View file

@ -8,18 +8,6 @@ use std::os::unix::net::UnixListener;
use std::path::Path;
use std::sync::Arc;
use crate::vfio::{VfioDevice, VfioRegionAddr};
use crate::virtio::interrupt::SignalableInterrupt;
use crate::virtio::vhost::user::device::handler::{
DeviceRequestHandler, Doorbell, GuestAddress, HandlerType, MappingInfo, MemoryRegion,
VhostUserBackend,
};
use crate::virtio::vhost::user::device::vvu::{
device::VvuDevice,
doorbell::DoorbellRegion,
pci::{VvuPciCaps, VvuPciDevice},
};
use anyhow::{anyhow, bail};
use anyhow::{Context, Result};
use base::{
@ -37,6 +25,18 @@ use vmm_vhost::{
VhostUserSlaveReqHandler,
};
use crate::vfio::{VfioDevice, VfioRegionAddr};
use crate::virtio::interrupt::SignalableInterrupt;
use crate::virtio::vhost::user::device::handler::{
DeviceRequestHandler, Doorbell, GuestAddress, HandlerType, MappingInfo, MemoryRegion,
VhostUserBackend,
};
use crate::virtio::vhost::user::device::vvu::{
device::VvuDevice,
doorbell::DoorbellRegion,
pci::{VvuPciCaps, VvuPciDevice},
};
pub(crate) enum HandlerTypeSys {
Vvu {
vfio_dev: Arc<VfioDevice>,

View file

@ -2,9 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
mod sys;
use std::cell::RefCell;
use std::os::unix::net::UnixStream;
use std::path::Path;
use std::thread;
use std::u32;
@ -13,7 +13,7 @@ use virtio_sys::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
use vm_memory::GuestMemory;
use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
use crate::virtio::vhost::user::vmm::{handler::VhostUserHandler, worker::Worker, Error, Result};
use crate::virtio::vhost::user::vmm::{handler::VhostUserHandler, worker::Worker};
use crate::virtio::{block::common::virtio_blk_config, DeviceType, Interrupt, Queue, VirtioDevice};
const VIRTIO_BLK_F_SEG_MAX: u32 = 2;
@ -33,9 +33,7 @@ pub struct Block {
}
impl Block {
pub fn new<P: AsRef<Path>>(base_features: u64, socket_path: P) -> Result<Block> {
let socket = UnixStream::connect(&socket_path).map_err(Error::SocketConnect)?;
fn get_all_features(base_features: u64) -> (u64, u64, VhostUserProtocolFeatures) {
let allow_features = 1u64 << crate::virtio::VIRTIO_F_VERSION_1
| 1 << VIRTIO_BLK_F_SEG_MAX
| 1 << VIRTIO_BLK_F_RO
@ -46,25 +44,11 @@ impl Block {
| 1 << VIRTIO_RING_F_EVENT_IDX
| base_features
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
let allow_protocol_features = VhostUserProtocolFeatures::CONFIG;
let mut handler = VhostUserHandler::new_from_stream(
socket,
// TODO(b/181753022): Support multiple queues.
1, /* queues_num */
allow_features,
init_features,
allow_protocol_features,
)?;
let queue_sizes = handler.queue_sizes(QUEUE_SIZE, 1)?;
Ok(Block {
kill_evt: None,
worker_thread: None,
handler: RefCell::new(handler),
queue_sizes,
})
(allow_features, init_features, allow_protocol_features)
}
}

View file

@ -0,0 +1,11 @@
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cfg_if::cfg_if! {
if #[cfg(unix)] {
mod unix;
} else if #[cfg(windows)] {
mod windows;
}
}

View file

@ -0,0 +1,36 @@
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::cell::RefCell;
use std::os::unix::net::UnixStream;
use std::path::Path;
use crate::virtio::vhost::user::vmm::block::{Block, QUEUE_SIZE};
use crate::virtio::vhost::user::vmm::Error;
use crate::virtio::vhost::user::vmm::{handler::VhostUserHandler, Result};
impl Block {
pub fn new<P: AsRef<Path>>(base_features: u64, socket_path: P) -> Result<Block> {
let socket = UnixStream::connect(&socket_path).map_err(Error::SocketConnect)?;
let (allow_features, init_features, allow_protocol_features) =
Self::get_all_features(base_features);
let mut handler = VhostUserHandler::new_from_stream(
socket,
1, /* queues_num */
allow_features,
init_features,
allow_protocol_features,
)?;
let queue_sizes = handler.queue_sizes(QUEUE_SIZE, 1)?;
Ok(Block {
kill_evt: None,
worker_thread: None,
handler: RefCell::new(handler),
queue_sizes,
})
}
}

View file

@ -0,0 +1,34 @@
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::cell::RefCell;
use base::Tube;
use crate::virtio::vhost::user::vmm::block::{Block, QUEUE_SIZE};
use crate::virtio::vhost::user::vmm::{handler::VhostUserHandler, Result};
impl Block {
pub fn new(base_features: u64, tube: Tube) -> Result<Block> {
let (allow_features, init_features, allow_protocol_features) =
Self::get_all_features(base_features);
let mut handler = VhostUserHandler::new_from_tube(
tube,
/* max_queue_num= */ 1,
allow_features,
init_features,
allow_protocol_features,
)?;
let queue_sizes = handler.queue_sizes(QUEUE_SIZE, 1)?;
Ok(Block {
kill_evt: None,
worker_thread: None,
handler: RefCell::new(handler),
queue_sizes,
})
}
}

View file

@ -648,6 +648,19 @@ impl Vm for KvmVm {
Err(_) => Err(Error::new(EIO)),
}
}
fn handle_deflate(&mut self, guest_address: GuestAddress, size: u64) -> Result<()> {
match self.guest_mem.remove_range(guest_address, size) {
Ok(_) => Ok(()),
Err(vm_memory::Error::MemoryAccess(_, MmapError::SystemCallFailed(e))) => Err(e),
Err(_) => Err(Error::new(EIO)),
}
}
fn handle_inflate(&mut self, _guest_address: GuestAddress, _size: u64) -> Result<()> {
// No-op, when the guest attempts to access the pages again, Linux/KVM will provide them.
Ok(())
}
}
impl AsRawDescriptor for KvmVm {

View file

@ -159,6 +159,25 @@ pub trait Vm: Send {
/// Remove `size`-byte mapping starting at `offset`.
fn remove_mapping(&mut self, slot: u32, offset: usize, size: usize) -> Result<()>;
/// Frees the given segment of guest memory to be reclaimed by the host OS.
/// This is intended for use with virtio-balloon, where a guest driver determines
/// unused ranges and requests they be freed. Use without the guest's knowledge is sure
/// to break something. As per virtio-balloon spec, the given address and size
/// are intended to be page-aligned.
///
/// # Arguments
/// * `guest_address` - Address in the guest's "physical" memory to begin the unmapping
/// * `size` - The size of the region to unmap, in bytes
fn handle_deflate(&mut self, guest_address: GuestAddress, size: u64) -> Result<()>;
/// Reallocates memory and maps it to provide to the guest. This is intended to be used
/// exclusively in tandem with `handle_deflate`, and will return an `Err` Result otherwise.
///
/// # Arguments
/// * `guest_address` - Address in the guest's "physical" memory to begin the mapping
/// * `size` - The size of the region to map, in bytes
fn handle_inflate(&mut self, guest_address: GuestAddress, size: u64) -> Result<()>;
}
/// A unique fingerprint for a particular `VcpuRunHandle`, used in `Vcpu` impls to ensure the

View file

@ -21,7 +21,7 @@ pub enum MmioType {
}
/// Region of memory.
#[derive(Debug)]
#[derive(Copy, Clone, Debug)]
pub struct MemRegion {
pub base: u64,
pub size: u64,

View file

@ -280,7 +280,7 @@ impl Gfxstream {
let cookie: *mut VirglCookie = Box::into_raw(Box::new(VirglCookie {
fence_state: Rc::clone(&fence_state),
render_server_fd: None,
fence_handler: None,
fence_handler: Some(fence_handler.clone()),
use_async_fence_cb,
}));

View file

@ -792,6 +792,7 @@ impl RutabagaBuilder {
// If any component sets this to true, timer-based wakeup is activated. Async fence
// handling will continue to work but worker wakeups will otherwise be avoided if no
// components need the timer-based approach.
#[allow(unused_mut)]
let mut use_timer_based_fence_polling = false;
if self.default_component == RutabagaComponentType::Rutabaga2D {

View file

@ -39,6 +39,8 @@ use hypervisor::ProtectionType;
use libc::{getegid, geteuid};
#[cfg(feature = "gpu")]
use platform::GpuRenderServerParameters;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use resources::MemRegion;
use uuid::Uuid;
use vm_control::BatteryType;
@ -412,6 +414,8 @@ pub struct Config {
pub no_legacy: bool,
pub no_smt: bool,
pub params: Vec<String>,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub pcie_ecam: Option<MemRegion>,
#[cfg(feature = "direct")]
pub pcie_rp: Vec<HostPcieRootPortParameters>,
pub per_vm_core_scheduling: bool,
@ -479,129 +483,131 @@ pub struct Config {
impl Default for Config {
fn default() -> Config {
Config {
kvm_device_path: PathBuf::from(KVM_PATH),
vhost_vsock_device: None,
vhost_net_device_path: PathBuf::from(VHOST_NET_PATH),
vcpu_count: None,
vcpu_cgroup_path: None,
rt_cpus: Vec::new(),
vcpu_affinity: None,
cpu_clusters: Vec::new(),
cpu_capacity: BTreeMap::new(),
per_vm_core_scheduling: false,
#[cfg(feature = "audio")]
ac97_parameters: Vec::new(),
acpi_tables: Vec::new(),
android_fstab: None,
balloon: true,
balloon_bias: 0,
balloon_control: None,
battery_type: None,
cid: None,
coiommu_param: None,
#[cfg(feature = "audio_cras")]
cras_snds: Vec::new(),
cpu_capacity: BTreeMap::new(),
cpu_clusters: Vec::new(),
delay_rt: false,
no_smt: false,
memory: None,
swiotlb: None,
hugepages: false,
memory_file: None,
#[cfg(feature = "direct")]
direct_edge_irq: Vec::new(),
#[cfg(feature = "direct")]
direct_gpe: Vec::new(),
#[cfg(feature = "direct")]
direct_level_irq: Vec::new(),
#[cfg(feature = "direct")]
direct_mmio: None,
#[cfg(feature = "direct")]
direct_pmio: None,
#[cfg(feature = "direct")]
direct_wake_irq: Vec::new(),
disks: Vec::new(),
display_window_keyboard: false,
display_window_mouse: false,
dmi_path: None,
executable_path: None,
android_fstab: None,
file_backed_mappings: Vec::new(),
force_s2idle: false,
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
gdb: None,
#[cfg(feature = "gpu")]
gpu_parameters: None,
#[cfg(feature = "gpu")]
gpu_render_server_parameters: None,
host_cpu_topology: false,
host_ip: None,
hugepages: false,
init_memory: None,
initrd_path: None,
itmt: false,
// We initialize the jail configuration with a default value so jail-related options can
// apply irrespective of whether jail is enabled or not. `jail_config` will then be
// assigned `None` if it turns out that `jail_enabled` is `false` after we parse all the
// arguments.
jail_config: Some(Default::default()),
jail_enabled: !cfg!(feature = "default-no-sandbox"),
params: Vec::new(),
socket_path: None,
balloon_control: None,
plugin_root: None,
plugin_mounts: Vec::new(),
plugin_gid_maps: Vec::new(),
disks: Vec::new(),
pmem_devices: Vec::new(),
pstore: None,
host_ip: None,
netmask: None,
kvm_device_path: PathBuf::from(KVM_PATH),
mac_address: None,
memory: None,
memory_file: None,
mmio_address_ranges: Vec::new(),
net_vq_pairs: None,
vhost_net: false,
tap_fd: Vec::new(),
tap_name: Vec::new(),
cid: None,
#[cfg(feature = "gpu")]
gpu_parameters: None,
#[cfg(feature = "gpu")]
gpu_render_server_parameters: None,
software_tpm: false,
wayland_socket_paths: BTreeMap::new(),
x_display: None,
display_window_keyboard: false,
display_window_mouse: false,
netmask: None,
no_legacy: false,
no_smt: false,
params: Vec::new(),
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pcie_ecam: None,
#[cfg(feature = "direct")]
pcie_rp: Vec::new(),
per_vm_core_scheduling: false,
plugin_gid_maps: Vec::new(),
plugin_mounts: Vec::new(),
plugin_root: None,
pmem_devices: Vec::new(),
privileged_vm: false,
protected_vm: ProtectionType::Unprotected,
pstore: None,
pvm_fw: None,
rng: true,
rt_cpus: Vec::new(),
serial_parameters: BTreeMap::new(),
shared_dirs: Vec::new(),
#[cfg(feature = "audio")]
ac97_parameters: Vec::new(),
socket_path: None,
software_tpm: false,
#[cfg(feature = "audio")]
sound: None,
serial_parameters: BTreeMap::new(),
syslog_tag: None,
usb: true,
virtio_single_touch: Vec::new(),
virtio_multi_touch: Vec::new(),
virtio_trackpad: Vec::new(),
virtio_mice: Vec::new(),
virtio_keyboard: Vec::new(),
virtio_switches: Vec::new(),
virtio_input_evdevs: Vec::new(),
split_irqchip: false,
strict_balloon: false,
stub_pci_devices: Vec::new(),
swiotlb: None,
syslog_tag: None,
tap_fd: Vec::new(),
tap_name: Vec::new(),
#[cfg(target_os = "android")]
task_profiles: Vec::new(),
usb: true,
userspace_msr: BTreeMap::new(),
vcpu_affinity: None,
vcpu_cgroup_path: None,
vcpu_count: None,
vfio: Vec::new(),
#[cfg(feature = "video-decoder")]
video_dec: None,
#[cfg(feature = "video-encoder")]
video_enc: None,
acpi_tables: Vec::new(),
protected_vm: ProtectionType::Unprotected,
battery_type: None,
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
gdb: None,
balloon: true,
balloon_bias: 0,
vhost_net: false,
vhost_net_device_path: PathBuf::from(VHOST_NET_PATH),
vhost_user_blk: Vec::new(),
vhost_user_console: Vec::new(),
vhost_user_gpu: Vec::new(),
vhost_user_fs: Vec::new(),
vhost_user_gpu: Vec::new(),
vhost_user_mac80211_hwsim: None,
vhost_user_net: Vec::new(),
#[cfg(feature = "audio")]
vhost_user_snd: Vec::new(),
vhost_user_vsock: Vec::new(),
vhost_user_wl: Vec::new(),
vhost_vsock_device: None,
#[cfg(feature = "video-decoder")]
video_dec: None,
#[cfg(feature = "video-encoder")]
video_enc: None,
virtio_input_evdevs: Vec::new(),
virtio_keyboard: Vec::new(),
virtio_mice: Vec::new(),
virtio_multi_touch: Vec::new(),
virtio_single_touch: Vec::new(),
virtio_switches: Vec::new(),
virtio_trackpad: Vec::new(),
vvu_proxy: Vec::new(),
#[cfg(feature = "direct")]
direct_pmio: None,
#[cfg(feature = "direct")]
direct_mmio: None,
#[cfg(feature = "direct")]
direct_level_irq: Vec::new(),
#[cfg(feature = "direct")]
direct_edge_irq: Vec::new(),
#[cfg(feature = "direct")]
direct_wake_irq: Vec::new(),
#[cfg(feature = "direct")]
direct_gpe: Vec::new(),
dmi_path: None,
no_legacy: false,
host_cpu_topology: false,
privileged_vm: false,
stub_pci_devices: Vec::new(),
coiommu_param: None,
file_backed_mappings: Vec::new(),
init_memory: None,
#[cfg(feature = "direct")]
pcie_rp: Vec::new(),
rng: true,
force_s2idle: false,
strict_balloon: false,
mmio_address_ranges: Vec::new(),
userspace_msr: BTreeMap::new(),
itmt: false,
#[cfg(target_os = "android")]
task_profiles: Vec::new(),
pvm_fw: None,
wayland_socket_paths: BTreeMap::new(),
x_display: None,
}
}
}

View file

@ -910,6 +910,8 @@ fn setup_vm_components(cfg: &Config) -> Result<VmComponents> {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
force_s2idle: cfg.force_s2idle,
pvm_fw: pvm_fw_image,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pcie_ecam: cfg.pcie_ecam,
})
}

View file

@ -5,6 +5,7 @@
//! Runs a virtual machine
pub mod panic_hook;
pub mod sys;
use std::collections::{BTreeMap, BTreeSet};
use std::convert::TryFrom;
@ -24,35 +25,27 @@ use arch::{
set_default_serial_parameters, MsrAction, MsrConfig, MsrValueFrom, Pstore, VcpuAffinity,
};
use base::{debug, error, getpid, info, kill_process_group, pagesize, reap_child, syslog, warn};
#[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
use crosvm::platform::GpuRenderServerParameters;
#[cfg(feature = "direct")]
use crosvm::{argument::parse_hex_or_decimal, DirectIoOption, HostPcieRootPortParameters};
use crosvm::{
argument::{self, print_help, set_arguments, Argument},
platform, BindMount, Config, Executable, FileBackedMappingParameters, GidMap, SharedDir,
argument::{self, parse_hex_or_decimal, print_help, set_arguments, Argument},
platform, BindMount, Config, Executable, FileBackedMappingParameters, GidMap,
TouchDeviceOption, VfioCommand, VhostUserFsOption, VhostUserOption, VhostUserWlOption,
VvuOption,
};
#[cfg(feature = "direct")]
use crosvm::{DirectIoOption, HostPcieRootPortParameters};
use devices::serial_device::{SerialHardware, SerialParameters};
use devices::virtio::block::block::DiskOption;
#[cfg(feature = "gpu")]
use devices::virtio::gpu::{
GpuDisplayParameters, GpuMode, GpuParameters, DEFAULT_DISPLAY_HEIGHT, DEFAULT_DISPLAY_WIDTH,
};
#[cfg(feature = "audio_cras")]
use devices::virtio::snd::cras_backend::Error as CrasSndError;
#[cfg(feature = "audio_cras")]
use devices::virtio::vhost::user::device::run_cras_snd_device;
use devices::virtio::vhost::user::device::{
run_block_device, run_console_device, run_fs_device, run_net_device, run_vsock_device,
run_wl_device,
};
use devices::virtio::vhost::user::device::{run_block_device, run_net_device};
#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
use devices::virtio::VideoBackendType;
#[cfg(feature = "gpu")]
use devices::virtio::{
gpu::{
GpuDisplayParameters, GpuMode, GpuParameters, DEFAULT_DISPLAY_HEIGHT, DEFAULT_DISPLAY_WIDTH,
},
vhost::user::device::run_gpu_device,
};
#[cfg(feature = "direct")]
use devices::BusRange;
#[cfg(feature = "audio")]
@ -64,6 +57,8 @@ use disk::{
create_composite_disk, create_disk_file, create_zero_filler, ImagePartitionType, PartitionInfo,
};
use hypervisor::ProtectionType;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use resources::MemRegion;
use serde_keyvalue::from_key_values;
use uuid::Uuid;
use vm_control::{
@ -77,6 +72,11 @@ use vm_control::{
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use x86_64::set_itmt_msr_config;
const ONE_MB: u64 = 1 << 20;
const MB_ALIGNED: u64 = ONE_MB - 1;
// the max bus number is 256 and each bus occupy 1MB, so the max pcie cfg mmio size = 256M
const MAX_PCIE_ECAM_SIZE: u64 = ONE_MB * 256;
#[cfg(feature = "scudo")]
#[global_allocator]
static ALLOCATOR: scudo::GlobalScudoAllocator = scudo::GlobalScudoAllocator;
@ -567,56 +567,6 @@ fn parse_gpu_display_options(
Ok(())
}
#[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
fn parse_gpu_render_server_options(s: Option<&str>) -> argument::Result<GpuRenderServerParameters> {
let mut path: Option<PathBuf> = None;
let mut cache_path = None;
let mut cache_size = None;
if let Some(s) = s {
let opts = s
.split(',')
.map(|frag| frag.split('='))
.map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
for (k, v) in opts {
match k {
"path" => {
path =
Some(
PathBuf::from_str(v).map_err(|e| argument::Error::InvalidValue {
value: v.to_string(),
expected: e.to_string(),
})?,
)
}
"cache-path" => cache_path = Some(v.to_string()),
"cache-size" => cache_size = Some(v.to_string()),
"" => {}
_ => {
return Err(argument::Error::UnknownArgument(format!(
"gpu-render-server parameter {}",
k
)));
}
}
}
}
if let Some(p) = path {
Ok(GpuRenderServerParameters {
path: p,
cache_path,
cache_size,
})
} else {
Err(argument::Error::InvalidValue {
value: s.unwrap_or("").to_string(),
expected: String::from("gpu-render-server must include 'path'"),
})
}
}
#[cfg(feature = "audio")]
fn parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters> {
let mut ac97_params: Ac97Parameters = Default::default();
@ -641,61 +591,13 @@ fn parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters> {
argument::Error::Syntax(format!("invalid capture option: {}", e))
})?;
}
#[cfg(feature = "audio_cras")]
"client_type" => {
ac97_params
.set_client_type(v)
.map_err(|e| argument::Error::InvalidValue {
value: v.to_string(),
expected: e.to_string(),
})?;
}
#[cfg(feature = "audio_cras")]
"socket_type" => {
ac97_params
.set_socket_type(v)
.map_err(|e| argument::Error::InvalidValue {
value: v.to_string(),
expected: e.to_string(),
})?;
}
#[cfg(any(target_os = "linux", target_os = "android"))]
"server" => {
ac97_params.vios_server_path =
Some(
PathBuf::from_str(v).map_err(|e| argument::Error::InvalidValue {
value: v.to_string(),
expected: e.to_string(),
})?,
);
}
_ => {
return Err(argument::Error::UnknownArgument(format!(
"unknown ac97 parameter {}",
k
)));
sys::parse_ac97_options(&mut ac97_params, k, v)?;
}
}
}
// server is required for and exclusive to vios backend
#[cfg(any(target_os = "linux", target_os = "android"))]
match ac97_params.backend {
Ac97Backend::VIOS => {
if ac97_params.vios_server_path.is_none() {
return Err(argument::Error::ExpectedArgument(String::from(
"server argument is required for VIOS backend",
)));
}
}
_ => {
if ac97_params.vios_server_path.is_some() {
return Err(argument::Error::UnexpectedValue(String::from(
"server argument is exclusive to the VIOS backend",
)));
}
}
}
sys::check_ac97_backend(&ac97_params)?;
Ok(ac97_params)
}
@ -1094,39 +996,6 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
cfg.kvm_device_path = kvm_device_path;
}
"vhost-vsock-fd" => {
if cfg.vhost_vsock_device.is_some() {
return Err(argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: String::from("A vhost-vsock device was already specified"),
});
}
let fd: i32 = value
.unwrap()
.parse()
.map_err(|_| argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: String::from("this value for `vhost-vsock-fd` needs to be integer"),
})?;
cfg.vhost_vsock_device = Some(PathBuf::from(format!("/proc/self/fd/{}", fd)));
}
"vhost-vsock-device" => {
if cfg.vhost_vsock_device.is_some() {
return Err(argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: String::from("A vhost-vsock device was already specified"),
});
}
let vhost_vsock_device_path = PathBuf::from(value.unwrap());
if !vhost_vsock_device_path.exists() {
return Err(argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: String::from("this vhost-vsock device path does not exist"),
});
}
cfg.vhost_vsock_device = Some(vhost_vsock_device_path);
}
"vhost-net-device" => {
let vhost_net_device_path = PathBuf::from(value.unwrap());
if !vhost_net_device_path.exists() {
@ -1204,15 +1073,6 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
cfg.vcpu_cgroup_path = Some(vcpu_cgroup_path);
}
#[cfg(feature = "audio_cras")]
"cras-snd" => {
cfg.cras_snds.push(
value
.unwrap()
.parse()
.map_err(|e: CrasSndError| argument::Error::Syntax(e.to_string()))?,
);
}
"no-smt" => {
cfg.no_smt = true;
}
@ -1440,23 +1300,6 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
},
});
}
"host_ip" => {
if cfg.host_ip.is_some() {
return Err(argument::Error::TooManyArguments(
"`host_ip` already given".to_owned(),
));
}
cfg.host_ip =
Some(
value
.unwrap()
.parse()
.map_err(|_| argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: String::from("`host_ip` needs to be in the form \"x.x.x.x\""),
})?,
)
}
"netmask" => {
if cfg.netmask.is_some() {
return Err(argument::Error::TooManyArguments(
@ -1622,150 +1465,6 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
})?,
);
}
"shared-dir" => {
// This is formatted as multiple fields, each separated by ":". The first 2 fields are
// fixed (src:tag). The rest may appear in any order:
//
// * type=TYPE - must be one of "p9" or "fs" (default: p9)
// * uidmap=UIDMAP - a uid map in the format "inner outer count[,inner outer count]"
// (default: "0 <current euid> 1")
// * gidmap=GIDMAP - a gid map in the same format as uidmap
// (default: "0 <current egid> 1")
// * privileged_quota_uids=UIDS - Space-separated list of privileged uid values. When
// performing quota-related operations, these UIDs are treated as if they have
// CAP_FOWNER.
// * timeout=TIMEOUT - a timeout value in seconds, which indicates how long attributes
// and directory contents should be considered valid (default: 5)
// * cache=CACHE - one of "never", "always", or "auto" (default: auto)
// * writeback=BOOL - indicates whether writeback caching should be enabled (default: false)
let param = value.unwrap();
let mut components = param.split(':');
let src =
PathBuf::from(
components
.next()
.ok_or_else(|| argument::Error::InvalidValue {
value: param.to_owned(),
expected: String::from("missing source path for `shared-dir`"),
})?,
);
let tag = components
.next()
.ok_or_else(|| argument::Error::InvalidValue {
value: param.to_owned(),
expected: String::from("missing tag for `shared-dir`"),
})?
.to_owned();
if !src.is_dir() {
return Err(argument::Error::InvalidValue {
value: param.to_owned(),
expected: String::from("source path for `shared-dir` must be a directory"),
});
}
let mut shared_dir = SharedDir {
src,
tag,
..Default::default()
};
for opt in components {
let mut o = opt.splitn(2, '=');
let kind = o.next().ok_or_else(|| argument::Error::InvalidValue {
value: opt.to_owned(),
expected: String::from("`shared-dir` options must not be empty"),
})?;
let value = o.next().ok_or_else(|| argument::Error::InvalidValue {
value: opt.to_owned(),
expected: String::from("`shared-dir` options must be of the form `kind=value`"),
})?;
match kind {
"type" => {
shared_dir.kind =
value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("`type` must be one of `fs` or `9p`"),
})?
}
"uidmap" => shared_dir.uid_map = value.into(),
"gidmap" => shared_dir.gid_map = value.into(),
#[cfg(feature = "chromeos")]
"privileged_quota_uids" => {
shared_dir.fs_cfg.privileged_quota_uids =
value.split(' ').map(|s| s.parse().unwrap()).collect();
}
"timeout" => {
let seconds = value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("`timeout` must be an integer"),
})?;
let dur = Duration::from_secs(seconds);
shared_dir.fs_cfg.entry_timeout = dur;
shared_dir.fs_cfg.attr_timeout = dur;
}
"cache" => {
let policy = value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from(
"`cache` must be one of `never`, `always`, or `auto`",
),
})?;
shared_dir.fs_cfg.cache_policy = policy;
}
"writeback" => {
let writeback =
value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("`writeback` must be a boolean"),
})?;
shared_dir.fs_cfg.writeback = writeback;
}
"rewrite-security-xattrs" => {
let rewrite_security_xattrs =
value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from(
"`rewrite-security-xattrs` must be a boolean",
),
})?;
shared_dir.fs_cfg.rewrite_security_xattrs = rewrite_security_xattrs;
}
"ascii_casefold" => {
let ascii_casefold =
value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("`ascii_casefold` must be a boolean"),
})?;
shared_dir.fs_cfg.ascii_casefold = ascii_casefold;
shared_dir.p9_cfg.ascii_casefold = ascii_casefold;
}
"dax" => {
let use_dax = value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("`dax` must be a boolean"),
})?;
shared_dir.fs_cfg.use_dax = use_dax;
}
"posix_acl" => {
let posix_acl =
value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("`posix_acl` must be a boolean"),
})?;
shared_dir.fs_cfg.posix_acl = posix_acl;
}
_ => {
return Err(argument::Error::InvalidValue {
value: kind.to_owned(),
expected: String::from("unrecognized option for `shared-dir`"),
})
}
}
}
cfg.shared_dirs.push(shared_dir);
}
"seccomp-policy-dir" => {
if let Some(jail_config) = &mut cfg.jail_config {
// `value` is Some because we are in this match so it's safe to unwrap.
@ -1852,22 +1551,6 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
}
}
"vhost-net" => cfg.vhost_net = true,
"tap-fd" => {
cfg.tap_fd.push(
value
.unwrap()
.parse()
.map_err(|_| argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: String::from(
"this value for `tap-fd` must be an unsigned integer",
),
})?,
);
}
"tap-name" => {
cfg.tap_name.push(value.unwrap().to_owned());
}
#[cfg(feature = "gpu")]
"gpu" => {
let gpu_parameters = cfg.gpu_parameters.get_or_insert_with(Default::default);
@ -1878,10 +1561,6 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
let gpu_parameters = cfg.gpu_parameters.get_or_insert_with(Default::default);
parse_gpu_display_options(value, gpu_parameters)?;
}
#[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
"gpu-render-server" => {
cfg.gpu_render_server_parameters = Some(parse_gpu_render_server_options(value)?);
}
"software-tpm" => {
cfg.software_tpm = true;
}
@ -2301,63 +1980,6 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
cfg.vvu_proxy.push(vvu_opt);
}
"coiommu" => {
let mut params: devices::CoIommuParameters = Default::default();
if let Some(v) = value {
let opts = v
.split(',')
.map(|frag| frag.splitn(2, '='))
.map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
for (k, v) in opts {
match k {
"unpin_policy" => {
params.unpin_policy = v
.parse::<devices::CoIommuUnpinPolicy>()
.map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
}
"unpin_interval" => {
params.unpin_interval =
Duration::from_secs(v.parse::<u64>().map_err(|e| {
argument::Error::UnknownArgument(format!("{}", e))
})?)
}
"unpin_limit" => {
let limit = v
.parse::<u64>()
.map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?;
if limit == 0 {
return Err(argument::Error::InvalidValue {
value: v.to_owned(),
expected: String::from("Please use non-zero unpin_limit value"),
});
}
params.unpin_limit = Some(limit)
}
"unpin_gen_threshold" => {
params.unpin_gen_threshold = v
.parse::<u64>()
.map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
}
_ => {
return Err(argument::Error::UnknownArgument(format!(
"coiommu parameter {}",
k
)));
}
}
}
}
if cfg.coiommu_param.is_some() {
return Err(argument::Error::TooManyArguments(
"coiommu param already given".to_owned(),
));
}
cfg.coiommu_param = Some(params);
}
"file-backed-mapping" => {
cfg.file_backed_mappings
.push(parse_file_backed_mapping(value)?);
@ -2474,8 +2096,60 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
cfg.task_profiles.push(name.to_owned());
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
"pcie-ecam" => {
let value = value.unwrap();
let paras: Vec<&str> = value.split(',').collect();
if paras.len() != 2 {
return Err(argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from(
"pcie-ecam must have exactly 2 parameters: ecam_base,ecam_size",
),
});
}
let base =
parse_hex_or_decimal(paras[0]).map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("pcie-ecam, the first parameter base should be integer"),
})?;
let mut len =
parse_hex_or_decimal(paras[1]).map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from(
"pcie-ecam, the second parameter size should be integer",
),
})?;
if (base & MB_ALIGNED != 0) || (len & MB_ALIGNED != 0) {
return Err(argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("pcie-ecam, the base and len should be aligned to 1MB"),
});
}
if len > MAX_PCIE_ECAM_SIZE {
len = MAX_PCIE_ECAM_SIZE;
}
if base + len >= 0x1_0000_0000 {
return Err(argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("pcie-ecam, the end address couldn't beyond 4G"),
});
}
if base % len != 0 {
return Err(argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("pcie-ecam, base should be multiple of len"),
});
}
cfg.pcie_ecam = Some(MemRegion { base, size: len });
}
"help" => return Err(argument::Error::PrintHelp),
_ => unreachable!(),
_ => sys::set_arguments(cfg, name, value)?,
}
Ok(())
}
@ -2656,10 +2330,9 @@ enum CommandStatus {
}
fn run_vm(args: std::env::Args) -> std::result::Result<CommandStatus, ()> {
let arguments =
&[Argument::positional("KERNEL", "bzImage of kernel to run"),
let mut arguments =
vec![Argument::positional("KERNEL", "bzImage of kernel to run"),
Argument::value("kvm-device", "PATH", "Path to the KVM device. (default /dev/kvm)"),
Argument::value("vhost-vsock-fd", "FD", "Open FD to the vhost-vsock device, mutually exclusive with vhost-vsock-device."),
Argument::value("vhost-vsock-device", "PATH", "Path to the vhost-vsock device. (default /dev/vhost-vsock)"),
Argument::value("vhost-net-device", "PATH", "Path to the vhost-net device. (default /dev/vhost-net)"),
Argument::value("android-fstab", "PATH", "Path to Android fstab"),
@ -2677,20 +2350,6 @@ fn run_vm(args: std::env::Args) -> std::result::Result<CommandStatus, ()> {
making all vCPU threads share same cookie for core scheduling.
This option is no-op on devices that have neither MDS nor L1TF vulnerability."),
Argument::value("vcpu-cgroup-path", "PATH", "Move all vCPU threads to this CGroup (default: nothing moves)."),
#[cfg(feature = "audio_cras")]
Argument::value("cras-snd",
"[capture=true,client=crosvm,socket=unified,num_output_streams=1,num_input_streams=1]",
"Comma separated key=value pairs for setting up cras snd devices.
Possible key values:
capture - Enable audio capture. Default to false.
client_type - Set specific client type for cras backend.
num_output_streams - Set number of output PCM streams
num_input_streams - Set number of input PCM streams"),
Argument::flag("no-smt", "Don't use SMT in the guest"),
Argument::value("rt-cpus", "CPUSET", "Comma-separated list of CPUs or CPU ranges to run VCPUs on. (e.g. 0,1-3,5) (default: none)"),
Argument::flag("delay-rt", "Don't set VCPUs real-time until make-rt command is run"),
@ -2726,9 +2385,6 @@ fn run_vm(args: std::env::Args) -> std::result::Result<CommandStatus, ()> {
Argument::value("rw-pmem-device", "PATH", "Path to a writable disk image."),
Argument::value("pmem-device", "PATH", "Path to a disk image."),
Argument::value("pstore", "path=PATH,size=SIZE", "Path to pstore buffer backend file followed by size."),
Argument::value("host_ip",
"IP",
"IP address to assign to host tap interface."),
Argument::value("netmask", "NETMASK", "Netmask for VM subnet."),
Argument::value("mac", "MAC", "MAC address for VM."),
Argument::value("net-vq-pairs", "N", "virtio net virtual queue paris. (default: 1)"),
@ -2828,12 +2484,6 @@ There is a cost of slightly increased latency the first time the file is accesse
#[cfg(feature = "plugin")]
Argument::value("plugin-gid-map-file", "PATH", "Path to the file listing supplemental GIDs that should be mapped in plugin jail. Can be given more than once."),
Argument::flag("vhost-net", "Use vhost for networking."),
Argument::value("tap-name",
"NAME",
"Name of a configured persistent TAP interface to use for networking. A different virtual network card will be added each time this argument is given."),
Argument::value("tap-fd",
"fd",
"File descriptor for configured tap device. A different virtual network card will be added each time this argument is given."),
#[cfg(feature = "gpu")]
Argument::flag_or_value("gpu",
"[width=INT,height=INT]",
@ -2872,18 +2522,6 @@ There is a cost of slightly increased latency the first time the file is accesse
width=INT - The width of the virtual display connected to the virtio-gpu.
height=INT - The height of the virtual display connected to the virtio-gpu."),
#[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
Argument::flag_or_value("gpu-render-server",
"[path=PATH]",
"(EXPERIMENTAL) Comma separated key=value pairs for setting up a render server for the virtio-gpu device
Possible key values:
path=PATH - The path to the render server executable.
cache-path=PATH - The path to the render server shader cache.
cache-size=SIZE - The maximum size of the shader cache."),
#[cfg(feature = "tpm")]
Argument::flag("software-tpm", "enable a software emulated trusted platform module device"),
Argument::value("evdev", "PATH", "Path to an event device node. The device will be grabbed (unusable from the host) and made available to the guest with the same configuration it shows on the host"),
@ -2986,19 +2624,6 @@ iommu=on|off - indicates whether to enable virtio IOMMU for this device"),
subsystem_device=NUM - PCI subsystem device ID
revision=NUM - revision"),
Argument::flag_or_value("coiommu",
"unpin_policy=POLICY,unpin_interval=NUM,unpin_limit=NUM,unpin_gen_threshold=NUM ",
"Comma separated key=value pairs for setting up coiommu devices.
Possible key values:
unpin_policy=lru - LRU unpin policy.
unpin_interval=NUM - Unpin interval time in seconds.
unpin_limit=NUM - Unpin limit for each unpin cycle, in unit of page count. 0 is invalid.
unpin_gen_threshold=NUM - Number of unpin intervals a pinned page must be busy for to be aged into the older which is less frequently checked generation."),
Argument::value("file-backed-mapping", "addr=NUM,size=SIZE,path=PATH[,offset=NUM][,ro][,rw][,sync]", "Map the given file into guest memory at the specified address.
Parameters (addr, size, path are required):
@ -3030,8 +2655,12 @@ iommu=on|off - indicates whether to enable virtio IOMMU for this device"),
incompatible with the default ranges calculated by crosvm."),
#[cfg(target_os = "android")]
Argument::value("task-profiles", "NAME[,...]", "Comma-separated names of the task profiles to apply to all threads in crosvm including the vCPU threads."),
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
Argument::value("pcie-ecam", "mmio_base,mmio_length",
"Base and length for PCIE Enhanced Configuration Access Mechanism"),
Argument::short_flag('h', "help", "Print help message.")];
arguments.append(&mut sys::get_arguments());
let mut cfg = Config::default();
let match_res = set_arguments(args, &arguments[..], |name, value| {
set_argument(&mut cfg, name, value)
@ -3481,20 +3110,8 @@ fn start_device(mut args: std::env::Args) -> std::result::Result<(), ()> {
let result = match device.as_str() {
"block" => run_block_device(&program_name, args),
"console" => run_console_device(&program_name, args),
#[cfg(feature = "audio_cras")]
"cras-snd" => run_cras_snd_device(&program_name, args),
"fs" => run_fs_device(&program_name, args),
#[cfg(feature = "gpu")]
"gpu" => run_gpu_device(&program_name, args),
"net" => run_net_device(&program_name, args),
"vsock" => run_vsock_device(&program_name, args),
"wl" => run_wl_device(&program_name, args),
_ => {
println!("Unknown device name: {}", device);
print_usage();
return Err(());
}
_ => sys::start_device(&program_name, device.as_str(), args),
};
result.map_err(|e| {
@ -4449,8 +4066,10 @@ mod tests {
#[cfg(feature = "direct")]
#[test]
fn parse_direct_io_options_valid() {
let params = parse_direct_io_options(Some("/dev/mem@1,100-110")).unwrap();
assert_eq!(params.path.to_str(), Some("/dev/mem"));
// Use /dev/zero here which is usually available on any systems,
// /dev/mem may not.
let params = parse_direct_io_options(Some("/dev/zero@1,100-110")).unwrap();
assert_eq!(params.path.to_str(), Some("/dev/zero"));
assert_eq!(params.ranges[0], BusRange { base: 1, len: 1 });
assert_eq!(params.ranges[1], BusRange { base: 100, len: 11 });
}
@ -4458,8 +4077,10 @@ mod tests {
#[cfg(feature = "direct")]
#[test]
fn parse_direct_io_options_hex() {
let params = parse_direct_io_options(Some("/dev/mem@1,0x10,100-110,0x10-0x20")).unwrap();
assert_eq!(params.path.to_str(), Some("/dev/mem"));
// Use /dev/zero here which is usually available on any systems,
// /dev/mem may not.
let params = parse_direct_io_options(Some("/dev/zero@1,0x10,100-110,0x10-0x20")).unwrap();
assert_eq!(params.path.to_str(), Some("/dev/zero"));
assert_eq!(params.ranges[0], BusRange { base: 1, len: 1 });
assert_eq!(params.ranges[1], BusRange { base: 0x10, len: 1 });
assert_eq!(params.ranges[2], BusRange { base: 100, len: 11 });
@ -4475,12 +4096,14 @@ mod tests {
#[cfg(feature = "direct")]
#[test]
fn parse_direct_io_options_invalid() {
assert!(parse_direct_io_options(Some("/dev/mem@0y10"))
// Use /dev/zero here which is usually available on any systems,
// /dev/mem may not.
assert!(parse_direct_io_options(Some("/dev/zero@0y10"))
.unwrap_err()
.to_string()
.contains("invalid base range value"));
assert!(parse_direct_io_options(Some("/dev/mem@"))
assert!(parse_direct_io_options(Some("/dev/zero@"))
.unwrap_err()
.to_string()
.contains("invalid base range value"));

17
src/sys.rs Normal file
View file

@ -0,0 +1,17 @@
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cfg_if::cfg_if! {
if #[cfg(unix)] {
pub(crate) mod unix;
use unix as platform;
} else {
compile_error!("Unsupported platform");
}
}
pub(crate) use platform::main::{get_arguments, set_arguments, start_device};
#[cfg(feature = "audio")]
pub(crate) use platform::main::{check_ac97_backend, parse_ac97_options};

5
src/sys/unix.rs Normal file
View file

@ -0,0 +1,5 @@
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
pub(crate) mod main;

506
src/sys/unix/main.rs Normal file
View file

@ -0,0 +1,506 @@
// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#[cfg(any(
all(feature = "gpu", feature = "virgl_renderer_next"),
feature = "audio"
))]
use std::str::FromStr;
use std::{path::PathBuf, time::Duration};
use anyhow::{anyhow, Result};
#[cfg(feature = "gpu")]
use devices::virtio::vhost::user::device::run_gpu_device;
use devices::virtio::vhost::user::device::{
run_console_device, run_fs_device, run_vsock_device, run_wl_device,
};
#[cfg(feature = "audio_cras")]
use devices::virtio::{
snd::cras_backend::Error as CrasSndError, vhost::user::device::run_cras_snd_device,
};
#[cfg(target_os = "android")]
use devices::Ac97Backend;
#[cfg(feature = "audio")]
use devices::Ac97Parameters;
use crate::argument::{self, Argument};
#[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
use crate::platform::GpuRenderServerParameters;
use crosvm::{Config, SharedDir};
#[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
fn parse_gpu_render_server_options(s: Option<&str>) -> argument::Result<GpuRenderServerParameters> {
let mut path: Option<PathBuf> = None;
let mut cache_path = None;
let mut cache_size = None;
if let Some(s) = s {
let opts = s
.split(',')
.map(|frag| frag.split('='))
.map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
for (k, v) in opts {
match k {
"path" => {
path =
Some(
PathBuf::from_str(v).map_err(|e| argument::Error::InvalidValue {
value: v.to_string(),
expected: e.to_string(),
})?,
)
}
"cache-path" => cache_path = Some(v.to_string()),
"cache-size" => cache_size = Some(v.to_string()),
"" => {}
_ => {
return Err(argument::Error::UnknownArgument(format!(
"gpu-render-server parameter {}",
k
)));
}
}
}
}
if let Some(p) = path {
Ok(GpuRenderServerParameters {
path: p,
cache_path,
cache_size,
})
} else {
Err(argument::Error::InvalidValue {
value: s.unwrap_or("").to_string(),
expected: String::from("gpu-render-server must include 'path'"),
})
}
}
pub fn get_arguments() -> Vec<Argument> {
vec![
#[cfg(feature = "audio_cras")]
Argument::value("cras-snd",
"[capture=true,client=crosvm,socket=unified,num_output_streams=1,num_input_streams=1]",
"Comma separated key=value pairs for setting up cras snd devices.
Possible key values:
capture - Enable audio capture. Default to false.
client_type - Set specific client type for cras backend.
num_output_streams - Set number of output PCM streams
num_input_streams - Set number of input PCM streams"),
Argument::value("host_ip",
"IP",
"IP address to assign to host tap interface."),
Argument::value("tap-name",
"NAME",
"Name of a configured persistent TAP interface to use for networking. A different virtual network card will be added each time this argument is given."),
Argument::value("tap-fd",
"fd",
"File descriptor for configured tap device. A different virtual network card will be added each time this argument is given."),
Argument::value("vhost-vsock-fd", "FD", "Open FD to the vhost-vsock device, mutually exclusive with vhost-vsock-device."),
#[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
Argument::flag_or_value("gpu-render-server",
"[path=PATH]",
"(EXPERIMENTAL) Comma separated key=value pairs for setting up a render server for the virtio-gpu device
Possible key values:
path=PATH - The path to the render server executable.
cache-path=PATH - The path to the render server shader cache.
cache-size=SIZE - The maximum size of the shader cache."),
Argument::flag_or_value("coiommu",
"unpin_policy=POLICY,unpin_interval=NUM,unpin_limit=NUM,unpin_gen_threshold=NUM ",
"Comma separated key=value pairs for setting up coiommu devices.
Possible key values:
unpin_policy=lru - LRU unpin policy.
unpin_interval=NUM - Unpin interval time in seconds.
unpin_limit=NUM - Unpin limit for each unpin cycle, in unit of page count. 0 is invalid.
unpin_gen_threshold=NUM - Number of unpin intervals a pinned page must be busy for to be aged into the older which is less frequently checked generation."),
]
}
pub fn set_arguments(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::Result<()> {
match name {
#[cfg(feature = "audio_cras")]
"cras-snd" => {
cfg.cras_snds.push(
value
.unwrap()
.parse()
.map_err(|e: CrasSndError| argument::Error::Syntax(e.to_string()))?,
);
}
"vhost-vsock-fd" => {
if cfg.vhost_vsock_device.is_some() {
return Err(argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: String::from("A vhost-vsock device was already specified"),
});
}
let fd: i32 = value
.unwrap()
.parse()
.map_err(|_| argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: String::from("this value for `vhost-vsock-fd` needs to be integer"),
})?;
cfg.vhost_vsock_device = Some(PathBuf::from(format!("/proc/self/fd/{}", fd)));
}
"vhost-vsock-device" => {
if cfg.vhost_vsock_device.is_some() {
return Err(argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: String::from("A vhost-vsock device was already specified"),
});
}
let vhost_vsock_device_path = PathBuf::from(value.unwrap());
if !vhost_vsock_device_path.exists() {
return Err(argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: String::from("this vhost-vsock device path does not exist"),
});
}
cfg.vhost_vsock_device = Some(vhost_vsock_device_path);
}
"shared-dir" => {
// This is formatted as multiple fields, each separated by ":". The first 2 fields are
// fixed (src:tag). The rest may appear in any order:
//
// * type=TYPE - must be one of "p9" or "fs" (default: p9)
// * uidmap=UIDMAP - a uid map in the format "inner outer count[,inner outer count]"
// (default: "0 <current euid> 1")
// * gidmap=GIDMAP - a gid map in the same format as uidmap
// (default: "0 <current egid> 1")
// * privileged_quota_uids=UIDS - Space-separated list of privileged uid values. When
// performing quota-related operations, these UIDs are treated as if they have
// CAP_FOWNER.
// * timeout=TIMEOUT - a timeout value in seconds, which indicates how long attributes
// and directory contents should be considered valid (default: 5)
// * cache=CACHE - one of "never", "always", or "auto" (default: auto)
// * writeback=BOOL - indicates whether writeback caching should be enabled (default: false)
let param = value.unwrap();
let mut components = param.split(':');
let src =
PathBuf::from(
components
.next()
.ok_or_else(|| argument::Error::InvalidValue {
value: param.to_owned(),
expected: String::from("missing source path for `shared-dir`"),
})?,
);
let tag = components
.next()
.ok_or_else(|| argument::Error::InvalidValue {
value: param.to_owned(),
expected: String::from("missing tag for `shared-dir`"),
})?
.to_owned();
if !src.is_dir() {
return Err(argument::Error::InvalidValue {
value: param.to_owned(),
expected: String::from("source path for `shared-dir` must be a directory"),
});
}
let mut shared_dir = SharedDir {
src,
tag,
..Default::default()
};
for opt in components {
let mut o = opt.splitn(2, '=');
let kind = o.next().ok_or_else(|| argument::Error::InvalidValue {
value: opt.to_owned(),
expected: String::from("`shared-dir` options must not be empty"),
})?;
let value = o.next().ok_or_else(|| argument::Error::InvalidValue {
value: opt.to_owned(),
expected: String::from("`shared-dir` options must be of the form `kind=value`"),
})?;
match kind {
"type" => {
shared_dir.kind =
value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("`type` must be one of `fs` or `9p`"),
})?
}
"uidmap" => shared_dir.uid_map = value.into(),
"gidmap" => shared_dir.gid_map = value.into(),
#[cfg(feature = "chromeos")]
"privileged_quota_uids" => {
shared_dir.fs_cfg.privileged_quota_uids =
value.split(' ').map(|s| s.parse().unwrap()).collect();
}
"timeout" => {
let seconds = value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("`timeout` must be an integer"),
})?;
let dur = Duration::from_secs(seconds);
shared_dir.fs_cfg.entry_timeout = dur;
shared_dir.fs_cfg.attr_timeout = dur;
}
"cache" => {
let policy = value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from(
"`cache` must be one of `never`, `always`, or `auto`",
),
})?;
shared_dir.fs_cfg.cache_policy = policy;
}
"writeback" => {
let writeback =
value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("`writeback` must be a boolean"),
})?;
shared_dir.fs_cfg.writeback = writeback;
}
"rewrite-security-xattrs" => {
let rewrite_security_xattrs =
value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from(
"`rewrite-security-xattrs` must be a boolean",
),
})?;
shared_dir.fs_cfg.rewrite_security_xattrs = rewrite_security_xattrs;
}
"ascii_casefold" => {
let ascii_casefold =
value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("`ascii_casefold` must be a boolean"),
})?;
shared_dir.fs_cfg.ascii_casefold = ascii_casefold;
shared_dir.p9_cfg.ascii_casefold = ascii_casefold;
}
"dax" => {
let use_dax = value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("`dax` must be a boolean"),
})?;
shared_dir.fs_cfg.use_dax = use_dax;
}
"posix_acl" => {
let posix_acl =
value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
expected: String::from("`posix_acl` must be a boolean"),
})?;
shared_dir.fs_cfg.posix_acl = posix_acl;
}
_ => {
return Err(argument::Error::InvalidValue {
value: kind.to_owned(),
expected: String::from("unrecognized option for `shared-dir`"),
})
}
}
}
cfg.shared_dirs.push(shared_dir);
}
"host_ip" => {
if cfg.host_ip.is_some() {
return Err(argument::Error::TooManyArguments(
"`host_ip` already given".to_owned(),
));
}
cfg.host_ip =
Some(
value
.unwrap()
.parse()
.map_err(|_| argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: String::from("`host_ip` needs to be in the form \"x.x.x.x\""),
})?,
)
}
"tap-name" => {
cfg.tap_name.push(value.unwrap().to_owned());
}
"tap-fd" => {
cfg.tap_fd.push(
value
.unwrap()
.parse()
.map_err(|_| argument::Error::InvalidValue {
value: value.unwrap().to_owned(),
expected: String::from(
"this value for `tap-fd` must be an unsigned integer",
),
})?,
);
}
"coiommu" => {
let mut params: devices::CoIommuParameters = Default::default();
if let Some(v) = value {
let opts = v
.split(',')
.map(|frag| frag.splitn(2, '='))
.map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
for (k, v) in opts {
match k {
"unpin_policy" => {
params.unpin_policy = v
.parse::<devices::CoIommuUnpinPolicy>()
.map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
}
"unpin_interval" => {
params.unpin_interval =
Duration::from_secs(v.parse::<u64>().map_err(|e| {
argument::Error::UnknownArgument(format!("{}", e))
})?)
}
"unpin_limit" => {
let limit = v
.parse::<u64>()
.map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?;
if limit == 0 {
return Err(argument::Error::InvalidValue {
value: v.to_owned(),
expected: String::from("Please use non-zero unpin_limit value"),
});
}
params.unpin_limit = Some(limit)
}
"unpin_gen_threshold" => {
params.unpin_gen_threshold = v
.parse::<u64>()
.map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
}
_ => {
return Err(argument::Error::UnknownArgument(format!(
"coiommu parameter {}",
k
)));
}
}
}
}
if cfg.coiommu_param.is_some() {
return Err(argument::Error::TooManyArguments(
"coiommu param already given".to_owned(),
));
}
cfg.coiommu_param = Some(params);
}
#[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
"gpu-render-server" => {
cfg.gpu_render_server_parameters = Some(parse_gpu_render_server_options(value)?);
}
_ => unreachable!(),
}
Ok(())
}
pub(crate) fn start_device(program_name: &str, device_name: &str, args: &[&str]) -> Result<()> {
match device_name {
"console" => run_console_device(program_name, args),
#[cfg(feature = "audio_cras")]
"cras-snd" => run_cras_snd_device(program_name, args),
"fs" => run_fs_device(program_name, args),
#[cfg(feature = "gpu")]
"gpu" => run_gpu_device(program_name, args),
"vsock" => run_vsock_device(program_name, args),
"wl" => run_wl_device(program_name, args),
_ => Err(anyhow!("unknown device name: {}", device_name)),
}
}
#[cfg(feature = "audio")]
pub(crate) fn check_ac97_backend(
#[allow(unused_variables)] ac97_params: &Ac97Parameters,
) -> argument::Result<()> {
// server is required for and exclusive to vios backend
#[cfg(target_os = "android")]
match ac97_params.backend {
Ac97Backend::VIOS => {
if ac97_params.vios_server_path.is_none() {
return Err(argument::Error::ExpectedArgument(String::from(
"server argument is required for VIOS backend",
)));
}
}
_ => {
if ac97_params.vios_server_path.is_some() {
return Err(argument::Error::UnexpectedValue(String::from(
"server argument is exclusive to the VIOS backend",
)));
}
}
}
Ok(())
}
#[cfg(feature = "audio")]
pub fn parse_ac97_options(
ac97_params: &mut Ac97Parameters,
key: &str,
#[allow(unused_variables)] value: &str,
) -> argument::Result<()> {
match key {
#[cfg(feature = "audio_cras")]
"client_type" => {
ac97_params
.set_client_type(value)
.map_err(|e| argument::Error::InvalidValue {
value: value.to_string(),
expected: e.to_string(),
})?;
}
#[cfg(feature = "audio_cras")]
"socket_type" => {
ac97_params
.set_socket_type(value)
.map_err(|e| argument::Error::InvalidValue {
value: value.to_string(),
expected: e.to_string(),
})?;
}
#[cfg(any(target_os = "linux", target_os = "android"))]
"server" => {
ac97_params.vios_server_path =
Some(
PathBuf::from_str(value).map_err(|e| argument::Error::InvalidValue {
value: value.to_string(),
expected: e.to_string(),
})?,
);
}
_ => {
return Err(argument::Error::UnknownArgument(format!(
"unknown ac97 parameter {}",
key
)));
}
};
Ok(())
}

View file

@ -251,7 +251,7 @@ struct LowMemoryLayout {
static LOW_MEMORY_LAYOUT: OnceCell<LowMemoryLayout> = OnceCell::new();
fn init_low_memory_layout() {
fn init_low_memory_layout(pcie_ecam: Option<MemRegion>) {
LOW_MEMORY_LAYOUT.get_or_init(|| {
// Make sure it align to 256MB for MTRR convenient
const MEM_32BIT_GAP_SIZE: u64 = if cfg!(feature = "direct") {
@ -270,11 +270,23 @@ fn init_low_memory_layout() {
// Reserve 64MB for pcie enhanced configuration
const PCIE_CFG_MMIO_SIZE: u64 = 0x400_0000;
let (pcie_cfg_mmio_start, pcie_cfg_mmio_size) = if let Some(pcie_mem) = pcie_ecam {
(pcie_mem.base, pcie_mem.size)
} else {
(
(FIRST_ADDR_PAST_32BITS - RESERVED_MEM_SIZE - PCIE_CFG_MMIO_SIZE),
PCIE_CFG_MMIO_SIZE,
)
};
let pci_start = pcie_cfg_mmio_start.min(FIRST_ADDR_PAST_32BITS - MEM_32BIT_GAP_SIZE);
let pci_size = FIRST_ADDR_PAST_32BITS - pci_start - RESERVED_MEM_SIZE;
LowMemoryLayout {
pci_start: FIRST_ADDR_PAST_32BITS - MEM_32BIT_GAP_SIZE,
pci_size: MEM_32BIT_GAP_SIZE - RESERVED_MEM_SIZE,
pcie_cfg_mmio_start: FIRST_ADDR_PAST_32BITS - RESERVED_MEM_SIZE - PCIE_CFG_MMIO_SIZE,
pcie_cfg_mmio_size: PCIE_CFG_MMIO_SIZE,
pci_start,
pci_size,
pcie_cfg_mmio_start,
pcie_cfg_mmio_size,
}
});
}
@ -447,12 +459,13 @@ impl arch::LinuxArch for X8664arch {
fn guest_memory_layout(
components: &VmComponents,
) -> std::result::Result<Vec<(GuestAddress, u64)>, Self::Error> {
init_low_memory_layout();
init_low_memory_layout(components.pcie_ecam);
let bios_size = match &components.vm_image {
VmImage::Bios(bios_file) => Some(bios_file.metadata().map_err(Error::LoadBios)?.len()),
VmImage::Kernel(_) => None,
};
Ok(arch_memory_regions(components.memory_size, bios_size))
}
@ -604,7 +617,6 @@ impl arch::LinuxArch for X8664arch {
// each bus occupy 1MB mmio for pcie enhanced configuration
let max_bus = ((read_pcie_cfg_mmio_size() / 0x100000) - 1) as u8;
let (acpi_dev_resource, bat_control) = Self::setup_acpi_devices(
&mem,
&io_bus,
@ -1282,13 +1294,6 @@ impl X8664arch {
reset_evt: Event,
mem_size: u64,
) -> Result<()> {
struct NoDevice;
impl devices::BusDevice for NoDevice {
fn debug_label(&self) -> String {
"no device".to_owned()
}
}
let mem_regions = arch_memory_regions(mem_size, None);
let mem_below_4g = mem_regions
@ -1311,7 +1316,6 @@ impl X8664arch {
)
.unwrap();
let nul_device = Arc::new(Mutex::new(NoDevice));
let i8042 = Arc::new(Mutex::new(devices::I8042Device::new(
reset_evt.try_clone().map_err(Error::CloneEvent)?,
)));
@ -1322,9 +1326,6 @@ impl X8664arch {
io_bus.insert(i8042, 0x061, 0x4).unwrap();
}
io_bus.insert(nul_device.clone(), 0x0ed, 0x1).unwrap(); // most likely this one does nothing
io_bus.insert(nul_device, 0x0f0, 0x2).unwrap(); // ignore fpu
Ok(())
}
@ -1676,7 +1677,11 @@ mod tests {
const TEST_MEMORY_SIZE: u64 = 2 * GB;
fn setup() {
init_low_memory_layout();
let pcie_ecam = Some(MemRegion {
base: 3 * GB,
size: 256 * MB,
});
init_low_memory_layout(pcie_ecam);
}
#[test]
@ -1759,6 +1764,15 @@ mod tests {
assert_eq!(bios_len, regions[1].1);
}
#[test]
fn check_pci_mmio_layout() {
setup();
assert_eq!(read_pci_start_before_32bit(), 3 * GB);
assert_eq!(read_pcie_cfg_mmio_start(), 3 * GB);
assert_eq!(read_pcie_cfg_mmio_size(), 256 * MB);
}
#[test]
#[cfg(feature = "direct")]
fn end_addr_before_32bits() {

View file

@ -9,7 +9,7 @@ use devices::IrqChipX86_64;
use hypervisor::{
HypervisorX86_64, IoOperation, IoParams, ProtectionType, VcpuExit, VcpuX86_64, VmX86_64,
};
use resources::SystemAllocator;
use resources::{MemRegion, SystemAllocator};
use vm_memory::{GuestAddress, GuestMemory};
use super::cpuid::setup_cpuid;
@ -103,7 +103,10 @@ where
// write to 4th page
let write_addr = GuestAddress(0x4000);
init_low_memory_layout();
init_low_memory_layout(Some(MemRegion {
base: 0xC000_0000,
size: 0x1000_0000,
}));
// guest mem is 400 pages
let arch_mem_regions = arch_memory_regions(memory_size, None);
let guest_mem = GuestMemory::new(&arch_mem_regions).unwrap();