mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-24 12:34:31 +00:00
Merge with main 2022-05-16
8958e112
crosvm: Sort struct Config default initializers.90f281c1
crosvm: Use /dev/zero instead of /dev/memcc3fcb8e
crosvm: carve-out unix specific parts of parse_ac97_options4281630b
crosvm: carve-out unix specific parts of start_devicee3cae727
crosvm: drop a few useless legacy I/O ports5b3cfb91
crosvm: carve-out unix specific set and get argumentsa6945f4a
aarch64: Add support for loading kernel image formatted as ELF646de85e
gfxstream: fix fence_handler not stored in fencing cookieeb0cf097
devices: vhost_user: Split up system specific code in fs devicebb30f23c
hypervisor: Upstream traits changes for Vm1b27aef2
x86_64: Add parameter to speicify pcie cfg mmio base and sizef0e09415
rutabaga_gfx: silence unused_mut warning for non-virgl builds96e50eb7
base: return SmallVec directly from platform wait impls65b3c0fe
devices: Upstream Windows vhost-user block vmm code0e5ee848
devices: Upstream Windows vhost-user block devicec3c170f780..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:
commit
f421479729
34 changed files with 1424 additions and 976 deletions
|
@ -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 = "*"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, *};
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
15
devices/src/virtio/vhost/user/device/block/sys.rs
Normal file
15
devices/src/virtio/vhost/user/device/block/sys.rs
Normal 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;
|
112
devices/src/virtio/vhost/user/device/block/sys/unix.rs
Normal file
112
devices/src/virtio/vhost/user/device/block/sys/unix.rs
Normal 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"),
|
||||
}
|
||||
}
|
149
devices/src/virtio/vhost/user/device/block/sys/windows.rs
Normal file
149
devices/src/virtio/vhost/user/device/block/sys/windows.rs
Normal 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(())
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
15
devices/src/virtio/vhost/user/device/fs/sys.rs
Normal file
15
devices/src/virtio/vhost/user/device/fs/sys.rs
Normal 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;
|
152
devices/src/virtio/vhost/user/device/fs/sys/unix.rs
Normal file
152
devices/src/virtio/vhost/user/device/fs/sys/unix.rs
Normal 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
|
||||
}
|
7
devices/src/virtio/vhost/user/device/fs/sys/windows.rs
Normal file
7
devices/src/virtio/vhost/user/device/fs/sys/windows.rs
Normal 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.")
|
||||
}
|
|
@ -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>,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
11
devices/src/virtio/vhost/user/vmm/block/sys.rs
Normal file
11
devices/src/virtio/vhost/user/vmm/block/sys.rs
Normal 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;
|
||||
}
|
||||
}
|
36
devices/src/virtio/vhost/user/vmm/block/sys/unix.rs
Normal file
36
devices/src/virtio/vhost/user/vmm/block/sys/unix.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
34
devices/src/virtio/vhost/user/vmm/block/sys/windows.rs
Normal file
34
devices/src/virtio/vhost/user/vmm/block/sys/windows.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}));
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
196
src/crosvm.rs
196
src/crosvm.rs
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
559
src/main.rs
559
src/main.rs
|
@ -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
17
src/sys.rs
Normal 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
5
src/sys/unix.rs
Normal 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
506
src/sys/unix/main.rs
Normal 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(())
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue