blob: 1e70b7916613b901b7ca1d1ad04dad84d1846076 [file] [log] [blame]
// Copyright 2017 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.
//! Handles IPC for controlling the main VM process.
//!
//! The VM Control IPC protocol is synchronous, meaning that each `VmRequest` sent over a connection
//! will receive a `VmResponse` for that request next time data is received over that connection.
//!
//! The wire message format is a little-endian C-struct of fixed size, along with a file descriptor
//! if the request type expects one.
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
pub mod gdb;
pub mod client;
use std::fmt::{self, Display};
use std::fs::File;
use std::os::raw::c_int;
use std::result::Result as StdResult;
use std::str::FromStr;
use std::sync::{mpsc, Arc};
use std::thread::JoinHandle;
use libc::{EINVAL, EIO, ENODEV};
use serde::{Deserialize, Serialize};
use base::{
error, with_as_descriptor, AsRawDescriptor, Error as SysError, Event, ExternalMapping, Fd,
FromRawDescriptor, IntoRawDescriptor, Killable, MappedRegion, MemoryMappingArena,
MemoryMappingBuilder, MemoryMappingBuilderUnix, MmapError, Protection, Result, SafeDescriptor,
SharedMemory, Tube, SIGRTMIN,
};
use hypervisor::{IrqRoute, IrqSource, Vm};
use resources::{Alloc, MmioType, SystemAllocator};
use rutabaga_gfx::{
DrmFormat, ImageAllocationInfo, RutabagaGralloc, RutabagaGrallocFlags, RutabagaHandle,
VulkanInfo,
};
use sync::Mutex;
use vm_memory::GuestAddress;
/// Struct that describes the offset and stride of a plane located in GPU memory.
#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize)]
pub struct GpuMemoryPlaneDesc {
pub stride: u32,
pub offset: u32,
}
/// Struct that describes a GPU memory allocation that consists of up to 3 planes.
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
pub struct GpuMemoryDesc {
pub planes: [GpuMemoryPlaneDesc; 3],
}
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
pub use crate::gdb::*;
pub use hypervisor::MemSlot;
/// Control the state of a particular VM CPU.
#[derive(Clone, Debug)]
pub enum VcpuControl {
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
Debug(VcpuDebug),
RunState(VmRunMode),
MakeRT,
}
/// Mode of execution for the VM.
#[derive(Debug, Clone, PartialEq)]
pub enum VmRunMode {
/// The default run mode indicating the VCPUs are running.
Running,
/// Indicates that the VCPUs are suspending execution until the `Running` mode is set.
Suspending,
/// Indicates that the VM is exiting all processes.
Exiting,
/// Indicates that the VM is in a breakpoint waiting for the debugger to do continue.
Breakpoint,
}
impl Display for VmRunMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::VmRunMode::*;
match self {
Running => write!(f, "running"),
Suspending => write!(f, "suspending"),
Exiting => write!(f, "exiting"),
Breakpoint => write!(f, "breakpoint"),
}
}
}
impl Default for VmRunMode {
fn default() -> Self {
VmRunMode::Running
}
}
/// The maximum number of devices that can be listed in one `UsbControlCommand`.
///
/// This value was set to be equal to `xhci_regs::MAX_PORTS` for convenience, but it is not
/// necessary for correctness. Importing that value directly would be overkill because it would
/// require adding a big dependency for a single const.
pub const USB_CONTROL_MAX_PORTS: usize = 16;
// Balloon commands that are sent on the crosvm control socket.
#[derive(Serialize, Deserialize, Debug)]
pub enum BalloonControlCommand {
/// Set the size of the VM's balloon.
Adjust {
num_bytes: u64,
},
Stats,
}
// Balloon commands that are send on the balloon command tube.
//
// This is the same as BalloonControlCommand above, but includes an ID for the
// Stats request so that we can discard stale stats if any previous stats
// request failed or timed out.
#[derive(Serialize, Deserialize, Debug)]
pub enum BalloonTubeCommand {
Adjust { num_bytes: u64 },
Stats { id: u64 },
}
// BalloonStats holds stats returned from the stats_queue.
#[derive(Default, Serialize, Deserialize, Debug)]
pub struct BalloonStats {
pub swap_in: Option<u64>,
pub swap_out: Option<u64>,
pub major_faults: Option<u64>,
pub minor_faults: Option<u64>,
pub free_memory: Option<u64>,
pub total_memory: Option<u64>,
pub available_memory: Option<u64>,
pub disk_caches: Option<u64>,
pub hugetlb_allocations: Option<u64>,
pub hugetlb_failures: Option<u64>,
pub shared_memory: Option<u64>,
pub unevictable_memory: Option<u64>,
}
// BalloonControlResult holds results for BalloonControlCommand defined above.
#[derive(Serialize, Deserialize, Debug)]
pub enum BalloonControlResult {
Stats {
stats: BalloonStats,
balloon_actual: u64,
},
}
// BalloonTubeResult are results to BalloonTubeCommand defined above. This is
// the same as BalloonControlResult, but with an added ID so that we can detect
// stale balloon stats values queued to the balloon command tube.
#[derive(Serialize, Deserialize, Debug)]
pub enum BalloonTubeResult {
Stats {
stats: BalloonStats,
balloon_actual: u64,
id: u64,
},
}
#[derive(Serialize, Deserialize, Debug)]
pub enum DiskControlCommand {
/// Resize a disk to `new_size` in bytes.
Resize { new_size: u64 },
}
impl Display for DiskControlCommand {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::DiskControlCommand::*;
match self {
Resize { new_size } => write!(f, "disk_resize {}", new_size),
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub enum DiskControlResult {
Ok,
Err(SysError),
}
#[derive(Serialize, Deserialize, Debug)]
pub enum UsbControlCommand {
AttachDevice {
bus: u8,
addr: u8,
vid: u16,
pid: u16,
#[serde(with = "with_as_descriptor")]
file: File,
},
DetachDevice {
port: u8,
},
ListDevice {
ports: [u8; USB_CONTROL_MAX_PORTS],
},
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Default)]
pub struct UsbControlAttachedDevice {
pub port: u8,
pub vendor_id: u16,
pub product_id: u16,
}
impl UsbControlAttachedDevice {
pub fn valid(self) -> bool {
self.port != 0
}
}
#[derive(Serialize, Deserialize, Debug)]
pub enum UsbControlResult {
Ok { port: u8 },
NoAvailablePort,
NoSuchDevice,
NoSuchPort,
FailedToOpenDevice,
Devices([UsbControlAttachedDevice; USB_CONTROL_MAX_PORTS]),
FailedToInitHostDevice,
}
impl Display for UsbControlResult {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::UsbControlResult::*;
match self {
UsbControlResult::Ok { port } => write!(f, "ok {}", port),
NoAvailablePort => write!(f, "no_available_port"),
NoSuchDevice => write!(f, "no_such_device"),
NoSuchPort => write!(f, "no_such_port"),
FailedToOpenDevice => write!(f, "failed_to_open_device"),
Devices(devices) => {
write!(f, "devices")?;
for d in devices.iter().filter(|d| d.valid()) {
write!(f, " {} {:04x} {:04x}", d.port, d.vendor_id, d.product_id)?;
}
std::result::Result::Ok(())
}
FailedToInitHostDevice => write!(f, "failed_to_init_host_device"),
}
}
}
#[derive(Serialize, Deserialize)]
pub enum VmMemoryRequest {
/// Register shared memory represented by the given descriptor into guest address space.
/// The response variant is `VmResponse::RegisterMemory`.
RegisterMemory(SharedMemory),
/// Similiar to `VmMemoryRequest::RegisterMemory`, but doesn't allocate new address space.
/// Useful for cases where the address space is already allocated (PCI regions).
RegisterFdAtPciBarOffset(Alloc, SafeDescriptor, usize, u64),
/// Similar to RegisterFdAtPciBarOffset, but is for buffers in the current address space.
RegisterHostPointerAtPciBarOffset(Alloc, u64),
/// Similiar to `RegisterFdAtPciBarOffset`, but uses Vulkano to map the resource instead of
/// the mmap system call.
RegisterVulkanMemoryAtPciBarOffset {
alloc: Alloc,
descriptor: SafeDescriptor,
handle_type: u32,
memory_idx: u32,
physical_device_idx: u32,
offset: u64,
size: u64,
},
/// Unregister the given memory slot that was previously registered with `RegisterMemory*`.
UnregisterMemory(MemSlot),
/// Allocate GPU buffer of a given size/format and register the memory into guest address space.
/// The response variant is `VmResponse::AllocateAndRegisterGpuMemory`
AllocateAndRegisterGpuMemory {
width: u32,
height: u32,
format: u32,
},
/// Register mmaped memory into the hypervisor's EPT.
RegisterMmapMemory {
descriptor: SafeDescriptor,
size: usize,
offset: u64,
gpa: u64,
},
}
impl VmMemoryRequest {
/// Executes this request on the given Vm.
///
/// # Arguments
/// * `vm` - The `Vm` to perform the request on.
/// * `allocator` - Used to allocate addresses.
///
/// This does not return a result, instead encapsulating the success or failure in a
/// `VmMemoryResponse` with the intended purpose of sending the response back over the socket
/// that received this `VmMemoryResponse`.
pub fn execute(
self,
vm: &mut impl Vm,
sys_allocator: &mut SystemAllocator,
map_request: Arc<Mutex<Option<ExternalMapping>>>,
gralloc: &mut RutabagaGralloc,
) -> VmMemoryResponse {
use self::VmMemoryRequest::*;
match self {
RegisterMemory(ref shm) => {
match register_memory(vm, sys_allocator, shm, shm.size() as usize, None) {
Ok((pfn, slot)) => VmMemoryResponse::RegisterMemory { pfn, slot },
Err(e) => VmMemoryResponse::Err(e),
}
}
RegisterFdAtPciBarOffset(alloc, ref descriptor, size, offset) => {
match register_memory(vm, sys_allocator, descriptor, size, Some((alloc, offset))) {
Ok((pfn, slot)) => VmMemoryResponse::RegisterMemory { pfn, slot },
Err(e) => VmMemoryResponse::Err(e),
}
}
UnregisterMemory(slot) => match vm.remove_memory_region(slot) {
Ok(_) => VmMemoryResponse::Ok,
Err(e) => VmMemoryResponse::Err(e),
},
RegisterHostPointerAtPciBarOffset(alloc, offset) => {
let mem = map_request
.lock()
.take()
.ok_or_else(|| VmMemoryResponse::Err(SysError::new(EINVAL)))
.unwrap();
match register_host_pointer(vm, sys_allocator, Box::new(mem), (alloc, offset)) {
Ok((pfn, slot)) => VmMemoryResponse::RegisterMemory { pfn, slot },
Err(e) => VmMemoryResponse::Err(e),
}
}
RegisterVulkanMemoryAtPciBarOffset {
alloc,
descriptor,
handle_type,
memory_idx,
physical_device_idx,
offset,
size,
} => {
let mapped_region = match gralloc.import_and_map(
RutabagaHandle {
os_handle: descriptor,
handle_type,
},
VulkanInfo {
memory_idx,
physical_device_idx,
},
size,
) {
Ok(mapped_region) => mapped_region,
Err(e) => {
error!("gralloc failed to import and map: {}", e);
return VmMemoryResponse::Err(SysError::new(EINVAL));
}
};
match register_host_pointer(vm, sys_allocator, mapped_region, (alloc, offset)) {
Ok((pfn, slot)) => VmMemoryResponse::RegisterMemory { pfn, slot },
Err(e) => VmMemoryResponse::Err(e),
}
}
AllocateAndRegisterGpuMemory {
width,
height,
format,
} => {
let img = ImageAllocationInfo {
width,
height,
drm_format: DrmFormat::from(format),
// Linear layout is a requirement as virtio wayland guest expects
// this for CPU access to the buffer. Scanout and texturing are
// optional as the consumer (wayland compositor) is expected to
// fall-back to a less efficient meachnisms for presentation if
// neccesary. In practice, linear buffers for commonly used formats
// will also support scanout and texturing.
flags: RutabagaGrallocFlags::empty().use_linear(true),
};
let reqs = match gralloc.get_image_memory_requirements(img) {
Ok(reqs) => reqs,
Err(e) => {
error!("gralloc failed to get image requirements: {}", e);
return VmMemoryResponse::Err(SysError::new(EINVAL));
}
};
let handle = match gralloc.allocate_memory(reqs) {
Ok(handle) => handle,
Err(e) => {
error!("gralloc failed to allocate memory: {}", e);
return VmMemoryResponse::Err(SysError::new(EINVAL));
}
};
let mut desc = GpuMemoryDesc::default();
for i in 0..3 {
desc.planes[i] = GpuMemoryPlaneDesc {
stride: reqs.strides[i],
offset: reqs.offsets[i],
}
}
match register_memory(
vm,
sys_allocator,
&handle.os_handle,
reqs.size as usize,
None,
) {
Ok((pfn, slot)) => VmMemoryResponse::AllocateAndRegisterGpuMemory {
// Safe because ownership is transferred to SafeDescriptor via
// into_raw_descriptor
descriptor: unsafe {
SafeDescriptor::from_raw_descriptor(
handle.os_handle.into_raw_descriptor(),
)
},
pfn,
slot,
desc,
},
Err(e) => VmMemoryResponse::Err(e),
}
}
RegisterMmapMemory {
ref descriptor,
size,
offset,
gpa,
} => {
let mmap = match MemoryMappingBuilder::new(size)
.from_descriptor(descriptor)
.offset(offset as u64)
.build()
{
Ok(v) => v,
Err(_e) => return VmMemoryResponse::Err(SysError::new(EINVAL)),
};
match vm.add_memory_region(GuestAddress(gpa), Box::new(mmap), false, false) {
Ok(slot) => VmMemoryResponse::RegisterMemory { pfn: 0, slot },
Err(e) => VmMemoryResponse::Err(e),
}
}
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub enum VmMemoryResponse {
/// The request to register memory into guest address space was successfully done at page frame
/// number `pfn` and memory slot number `slot`.
RegisterMemory {
pfn: u64,
slot: MemSlot,
},
/// The request to allocate and register GPU memory into guest address space was successfully
/// done at page frame number `pfn` and memory slot number `slot` for buffer with `desc`.
AllocateAndRegisterGpuMemory {
descriptor: SafeDescriptor,
pfn: u64,
slot: MemSlot,
desc: GpuMemoryDesc,
},
Ok,
Err(SysError),
}
#[derive(Serialize, Deserialize, Debug)]
pub enum VmIrqRequest {
/// Allocate one gsi, and associate gsi to irqfd with register_irqfd()
AllocateOneMsi { irqfd: Event },
/// Add one msi route entry into the IRQ chip.
AddMsiRoute {
gsi: u32,
msi_address: u64,
msi_data: u32,
},
}
/// Data to set up an IRQ event or IRQ route on the IRQ chip.
/// VmIrqRequest::execute can't take an `IrqChip` argument, because of a dependency cycle between
/// devices and vm_control, so it takes a Fn that processes an `IrqSetup`.
pub enum IrqSetup<'a> {
Event(u32, &'a Event),
Route(IrqRoute),
}
impl VmIrqRequest {
/// Executes this request on the given Vm.
///
/// # Arguments
/// * `set_up_irq` - A function that applies an `IrqSetup` to an IRQ chip.
///
/// This does not return a result, instead encapsulating the success or failure in a
/// `VmIrqResponse` with the intended purpose of sending the response back over the socket
/// that received this `VmIrqResponse`.
pub fn execute<F>(&self, set_up_irq: F, sys_allocator: &mut SystemAllocator) -> VmIrqResponse
where
F: FnOnce(IrqSetup) -> Result<()>,
{
use self::VmIrqRequest::*;
match *self {
AllocateOneMsi { ref irqfd } => {
if let Some(irq_num) = sys_allocator.allocate_irq() {
match set_up_irq(IrqSetup::Event(irq_num, irqfd)) {
Ok(_) => VmIrqResponse::AllocateOneMsi { gsi: irq_num },
Err(e) => VmIrqResponse::Err(e),
}
} else {
VmIrqResponse::Err(SysError::new(EINVAL))
}
}
AddMsiRoute {
gsi,
msi_address,
msi_data,
} => {
let route = IrqRoute {
gsi,
source: IrqSource::Msi {
address: msi_address,
data: msi_data,
},
};
match set_up_irq(IrqSetup::Route(route)) {
Ok(_) => VmIrqResponse::Ok,
Err(e) => VmIrqResponse::Err(e),
}
}
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub enum VmIrqResponse {
AllocateOneMsi { gsi: u32 },
Ok,
Err(SysError),
}
#[derive(Serialize, Deserialize, Debug)]
pub enum VmMsyncRequest {
/// Flush the content of a memory mapping to its backing file.
/// `slot` selects the arena (as returned by `Vm::add_mmap_arena`).
/// `offset` is the offset of the mapping to sync within the arena.
/// `size` is the size of the mapping to sync within the arena.
MsyncArena {
slot: MemSlot,
offset: usize,
size: usize,
},
}
#[derive(Serialize, Deserialize, Debug)]
pub enum VmMsyncResponse {
Ok,
Err(SysError),
}
impl VmMsyncRequest {
/// Executes this request on the given Vm.
///
/// # Arguments
/// * `vm` - The `Vm` to perform the request on.
///
/// This does not return a result, instead encapsulating the success or failure in a
/// `VmMsyncResponse` with the intended purpose of sending the response back over the socket
/// that received this `VmMsyncResponse`.
pub fn execute(&self, vm: &mut impl Vm) -> VmMsyncResponse {
use self::VmMsyncRequest::*;
match *self {
MsyncArena { slot, offset, size } => match vm.msync_memory_region(slot, offset, size) {
Ok(()) => VmMsyncResponse::Ok,
Err(e) => VmMsyncResponse::Err(e),
},
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub enum BatControlResult {
Ok,
NoBatDevice,
NoSuchHealth,
NoSuchProperty,
NoSuchStatus,
NoSuchBatType,
StringParseIntErr,
}
impl Display for BatControlResult {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::BatControlResult::*;
match self {
Ok => write!(f, "Setting battery property successfully"),
NoBatDevice => write!(f, "No battery device created"),
NoSuchHealth => write!(f, "Invalid Battery health setting. Only support: unknown/good/overheat/dead/overvoltage/unexpectedfailure/cold/watchdogtimerexpire/safetytimerexpire/overcurrent"),
NoSuchProperty => write!(f, "Battery doesn't have such property. Only support: status/health/present/capacity/aconline"),
NoSuchStatus => write!(f, "Invalid Battery status setting. Only support: unknown/charging/discharging/notcharging/full"),
NoSuchBatType => write!(f, "Invalid Battery type setting. Only support: goldfish"),
StringParseIntErr => write!(f, "Battery property target ParseInt error"),
}
}
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
pub enum BatteryType {
Goldfish,
}
impl Default for BatteryType {
fn default() -> Self {
BatteryType::Goldfish
}
}
impl FromStr for BatteryType {
type Err = BatControlResult;
fn from_str(s: &str) -> StdResult<Self, Self::Err> {
match s {
"goldfish" => Ok(BatteryType::Goldfish),
_ => Err(BatControlResult::NoSuchBatType),
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub enum BatProperty {
Status,
Health,
Present,
Capacity,
ACOnline,
}
impl FromStr for BatProperty {
type Err = BatControlResult;
fn from_str(s: &str) -> StdResult<Self, Self::Err> {
match s {
"status" => Ok(BatProperty::Status),
"health" => Ok(BatProperty::Health),
"present" => Ok(BatProperty::Present),
"capacity" => Ok(BatProperty::Capacity),
"aconline" => Ok(BatProperty::ACOnline),
_ => Err(BatControlResult::NoSuchProperty),
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub enum BatStatus {
Unknown,
Charging,
DisCharging,
NotCharging,
Full,
}
impl BatStatus {
pub fn new(status: String) -> std::result::Result<Self, BatControlResult> {
match status.as_str() {
"unknown" => Ok(BatStatus::Unknown),
"charging" => Ok(BatStatus::Charging),
"discharging" => Ok(BatStatus::DisCharging),
"notcharging" => Ok(BatStatus::NotCharging),
"full" => Ok(BatStatus::Full),
_ => Err(BatControlResult::NoSuchStatus),
}
}
}
impl FromStr for BatStatus {
type Err = BatControlResult;
fn from_str(s: &str) -> StdResult<Self, Self::Err> {
match s {
"unknown" => Ok(BatStatus::Unknown),
"charging" => Ok(BatStatus::Charging),
"discharging" => Ok(BatStatus::DisCharging),
"notcharging" => Ok(BatStatus::NotCharging),
"full" => Ok(BatStatus::Full),
_ => Err(BatControlResult::NoSuchStatus),
}
}
}
impl From<BatStatus> for u32 {
fn from(status: BatStatus) -> Self {
status as u32
}
}
#[derive(Serialize, Deserialize, Debug)]
pub enum BatHealth {
Unknown,
Good,
Overheat,
Dead,
OverVoltage,
UnexpectedFailure,
Cold,
WatchdogTimerExpire,
SafetyTimerExpire,
OverCurrent,
}
impl FromStr for BatHealth {
type Err = BatControlResult;
fn from_str(s: &str) -> StdResult<Self, Self::Err> {
match s {
"unknown" => Ok(BatHealth::Unknown),
"good" => Ok(BatHealth::Good),
"overheat" => Ok(BatHealth::Overheat),
"dead" => Ok(BatHealth::Dead),
"overvoltage" => Ok(BatHealth::OverVoltage),
"unexpectedfailure" => Ok(BatHealth::UnexpectedFailure),
"cold" => Ok(BatHealth::Cold),
"watchdogtimerexpire" => Ok(BatHealth::WatchdogTimerExpire),
"safetytimerexpire" => Ok(BatHealth::SafetyTimerExpire),
"overcurrent" => Ok(BatHealth::OverCurrent),
_ => Err(BatControlResult::NoSuchHealth),
}
}
}
impl From<BatHealth> for u32 {
fn from(status: BatHealth) -> Self {
status as u32
}
}
#[derive(Serialize, Deserialize, Debug)]
pub enum BatControlCommand {
SetStatus(BatStatus),
SetHealth(BatHealth),
SetPresent(u32),
SetCapacity(u32),
SetACOnline(u32),
}
impl BatControlCommand {
pub fn new(property: String, target: String) -> std::result::Result<Self, BatControlResult> {
let cmd = property.parse::<BatProperty>()?;
match cmd {
BatProperty::Status => Ok(BatControlCommand::SetStatus(target.parse::<BatStatus>()?)),
BatProperty::Health => Ok(BatControlCommand::SetHealth(target.parse::<BatHealth>()?)),
BatProperty::Present => Ok(BatControlCommand::SetPresent(
target
.parse::<u32>()
.map_err(|_| BatControlResult::StringParseIntErr)?,
)),
BatProperty::Capacity => Ok(BatControlCommand::SetCapacity(
target
.parse::<u32>()
.map_err(|_| BatControlResult::StringParseIntErr)?,
)),
BatProperty::ACOnline => Ok(BatControlCommand::SetACOnline(
target
.parse::<u32>()
.map_err(|_| BatControlResult::StringParseIntErr)?,
)),
}
}
}
/// Used for VM to control battery properties.
pub struct BatControl {
pub type_: BatteryType,
pub control_tube: Tube,
}
#[derive(Serialize, Deserialize, Debug)]
pub enum FsMappingRequest {
/// Create an anonymous memory mapping that spans the entire region described by `Alloc`.
AllocateSharedMemoryRegion(Alloc),
/// Create a memory mapping.
CreateMemoryMapping {
/// The slot for a MemoryMappingArena, previously returned by a response to an
/// `AllocateSharedMemoryRegion` request.
slot: u32,
/// The file descriptor that should be mapped.
fd: SafeDescriptor,
/// The size of the mapping.
size: usize,
/// The offset into the file from where the mapping should start.
file_offset: u64,
/// The memory protection to be used for the mapping. Protections other than readable and
/// writable will be silently dropped.
prot: u32,
/// The offset into the shared memory region where the mapping should be placed.
mem_offset: usize,
},
/// Remove a memory mapping.
RemoveMemoryMapping {
/// The slot for a MemoryMappingArena.
slot: u32,
/// The offset into the shared memory region.
offset: usize,
/// The size of the mapping.
size: usize,
},
}
impl FsMappingRequest {
pub fn execute(&self, vm: &mut dyn Vm, allocator: &mut SystemAllocator) -> VmResponse {
use self::FsMappingRequest::*;
match *self {
AllocateSharedMemoryRegion(Alloc::PciBar {
bus,
dev,
func,
bar,
}) => {
match allocator
.mmio_allocator(MmioType::High)
.get(&Alloc::PciBar {
bus,
dev,
func,
bar,
}) {
Some((addr, length, _)) => {
let arena = match MemoryMappingArena::new(*length as usize) {
Ok(a) => a,
Err(MmapError::SystemCallFailed(e)) => return VmResponse::Err(e),
_ => return VmResponse::Err(SysError::new(EINVAL)),
};
match vm.add_memory_region(
GuestAddress(*addr),
Box::new(arena),
false,
false,
) {
Ok(slot) => VmResponse::RegisterMemory {
pfn: addr >> 12,
slot,
},
Err(e) => VmResponse::Err(e),
}
}
None => VmResponse::Err(SysError::new(EINVAL)),
}
}
CreateMemoryMapping {
slot,
ref fd,
size,
file_offset,
prot,
mem_offset,
} => {
let raw_fd: Fd = Fd(fd.as_raw_descriptor());
match vm.add_fd_mapping(
slot,
mem_offset,
size,
&raw_fd,
file_offset,
Protection::from(prot as c_int & (libc::PROT_READ | libc::PROT_WRITE)),
) {
Ok(()) => VmResponse::Ok,
Err(e) => VmResponse::Err(e),
}
}
RemoveMemoryMapping { slot, offset, size } => {
match vm.remove_mapping(slot, offset, size) {
Ok(()) => VmResponse::Ok,
Err(e) => VmResponse::Err(e),
}
}
_ => VmResponse::Err(SysError::new(EINVAL)),
}
}
}
/// A request to the main process to perform some operation on the VM.
///
/// Unless otherwise noted, each request should expect a `VmResponse::Ok` to be received on success.
#[derive(Serialize, Deserialize, Debug)]
pub enum VmRequest {
/// Break the VM's run loop and exit.
Exit,
/// Suspend the VM's VCPUs until resume.
Suspend,
/// Resume the VM's VCPUs that were previously suspended.
Resume,
/// Make the VM's RT VCPU real-time.
MakeRT,
/// Command for balloon driver.
BalloonCommand(BalloonControlCommand),
/// Send a command to a disk chosen by `disk_index`.
/// `disk_index` is a 0-based count of `--disk`, `--rwdisk`, and `-r` command-line options.
DiskCommand {
disk_index: usize,
command: DiskControlCommand,
},
/// Command to use controller.
UsbCommand(UsbControlCommand),
/// Command to set battery.
BatCommand(BatteryType, BatControlCommand),
}
fn register_memory(
vm: &mut impl Vm,
allocator: &mut SystemAllocator,
descriptor: &dyn AsRawDescriptor,
size: usize,
pci_allocation: Option<(Alloc, u64)>,
) -> Result<(u64, MemSlot)> {
let mmap = match MemoryMappingBuilder::new(size)
.from_descriptor(descriptor)
.build()
{
Ok(v) => v,
Err(MmapError::SystemCallFailed(e)) => return Err(e),
_ => return Err(SysError::new(EINVAL)),
};
let addr = match pci_allocation {
Some(pci_allocation) => allocator
.mmio_allocator(MmioType::High)
.address_from_pci_offset(pci_allocation.0, pci_allocation.1, size as u64)
.map_err(|_e| SysError::new(EINVAL))?,
None => {
let alloc = allocator.get_anon_alloc();
allocator
.mmio_allocator(MmioType::High)
.allocate(size as u64, alloc, "vmcontrol_register_memory".to_string())
.map_err(|_e| SysError::new(EINVAL))?
}
};
let slot = vm.add_memory_region(GuestAddress(addr), Box::new(mmap), false, false)?;
Ok((addr >> 12, slot))
}
fn register_host_pointer(
vm: &mut impl Vm,
allocator: &mut SystemAllocator,
mem: Box<dyn MappedRegion>,
pci_allocation: (Alloc, u64),
) -> Result<(u64, MemSlot)> {
let addr = allocator
.mmio_allocator(MmioType::High)
.address_from_pci_offset(pci_allocation.0, pci_allocation.1, mem.size() as u64)
.map_err(|_e| SysError::new(EINVAL))?;
let slot = vm.add_memory_region(GuestAddress(addr), mem, false, false)?;
Ok((addr >> 12, slot))
}
impl VmRequest {
/// Executes this request on the given Vm and other mutable state.
///
/// This does not return a result, instead encapsulating the success or failure in a
/// `VmResponse` with the intended purpose of sending the response back over the socket that
/// received this `VmRequest`.
pub fn execute(
&self,
run_mode: &mut Option<VmRunMode>,
balloon_host_tube: &Tube,
balloon_stats_id: &mut u64,
disk_host_tubes: &[Tube],
usb_control_tube: Option<&Tube>,
bat_control: &mut Option<BatControl>,
vcpu_handles: &[(JoinHandle<()>, mpsc::Sender<VcpuControl>)],
) -> VmResponse {
match *self {
VmRequest::Exit => {
*run_mode = Some(VmRunMode::Exiting);
VmResponse::Ok
}
VmRequest::Suspend => {
*run_mode = Some(VmRunMode::Suspending);
VmResponse::Ok
}
VmRequest::Resume => {
*run_mode = Some(VmRunMode::Running);
VmResponse::Ok
}
VmRequest::MakeRT => {
for (handle, channel) in vcpu_handles {
if let Err(e) = channel.send(VcpuControl::MakeRT) {
error!("failed to send MakeRT: {}", e);
}
let _ = handle.kill(SIGRTMIN() + 0);
}
VmResponse::Ok
}
VmRequest::BalloonCommand(BalloonControlCommand::Adjust { num_bytes }) => {
match balloon_host_tube.send(&BalloonTubeCommand::Adjust { num_bytes }) {
Ok(_) => VmResponse::Ok,
Err(_) => VmResponse::Err(SysError::last()),
}
}
VmRequest::BalloonCommand(BalloonControlCommand::Stats) => {
// NB: There are a few reasons stale balloon stats could be left
// in balloon_host_tube:
// - the send succeeds, but the recv fails because the device
// is not ready yet. So when the device is ready, there are
// extra stats requests queued.
// - the send succeed, but the recv times out. When the device
// does return the stats, there will be no consumer.
//
// To guard against this, add an `id` to the stats request. If
// the id returned to us doesn't match, we keep trying to read
// until it does.
*balloon_stats_id = (*balloon_stats_id).wrapping_add(1);
let sent_id = *balloon_stats_id;
match balloon_host_tube.send(&BalloonTubeCommand::Stats { id: sent_id }) {
Ok(_) => {
loop {
match balloon_host_tube.recv() {
Ok(BalloonTubeResult::Stats {
stats,
balloon_actual,
id,
}) => {
if sent_id != id {
// Keep trying to get the fresh stats.
continue;
}
break VmResponse::BalloonStats {
stats,
balloon_actual,
};
}
Err(e) => {
error!("balloon socket recv failed: {}", e);
break VmResponse::Err(SysError::last());
}
}
}
}
Err(_) => VmResponse::Err(SysError::last()),
}
}
VmRequest::DiskCommand {
disk_index,
ref command,
} => {
// Forward the request to the block device process via its control socket.
if let Some(sock) = disk_host_tubes.get(disk_index) {
if let Err(e) = sock.send(command) {
error!("disk socket send failed: {}", e);
VmResponse::Err(SysError::new(EINVAL))
} else {
match sock.recv() {
Ok(DiskControlResult::Ok) => VmResponse::Ok,
Ok(DiskControlResult::Err(e)) => VmResponse::Err(e),
Err(e) => {
error!("disk socket recv failed: {}", e);
VmResponse::Err(SysError::new(EINVAL))
}
}
}
} else {
VmResponse::Err(SysError::new(ENODEV))
}
}
VmRequest::UsbCommand(ref cmd) => {
let usb_control_tube = match usb_control_tube {
Some(t) => t,
None => {
error!("attempted to execute USB request without control tube");
return VmResponse::Err(SysError::new(ENODEV));
}
};
let res = usb_control_tube.send(cmd);
if let Err(e) = res {
error!("fail to send command to usb control socket: {}", e);
return VmResponse::Err(SysError::new(EIO));
}
match usb_control_tube.recv() {
Ok(response) => VmResponse::UsbResponse(response),
Err(e) => {
error!("fail to recv command from usb control socket: {}", e);
VmResponse::Err(SysError::new(EIO))
}
}
}
VmRequest::BatCommand(type_, ref cmd) => {
match bat_control {
Some(battery) => {
if battery.type_ != type_ {
error!("ignored battery command due to battery type: expected {:?}, got {:?}", battery.type_, type_);
return VmResponse::Err(SysError::new(EINVAL));
}
let res = battery.control_tube.send(cmd);
if let Err(e) = res {
error!("fail to send command to bat control socket: {}", e);
return VmResponse::Err(SysError::new(EIO));
}
match battery.control_tube.recv() {
Ok(response) => VmResponse::BatResponse(response),
Err(e) => {
error!("fail to recv command from bat control socket: {}", e);
VmResponse::Err(SysError::new(EIO))
}
}
}
None => VmResponse::BatResponse(BatControlResult::NoBatDevice),
}
}
}
}
}
/// Indication of success or failure of a `VmRequest`.
///
/// Success is usually indicated `VmResponse::Ok` unless there is data associated with the response.
#[derive(Serialize, Deserialize, Debug)]
pub enum VmResponse {
/// Indicates the request was executed successfully.
Ok,
/// Indicates the request encountered some error during execution.
Err(SysError),
/// The request to register memory into guest address space was successfully done at page frame
/// number `pfn` and memory slot number `slot`.
RegisterMemory { pfn: u64, slot: u32 },
/// The request to allocate and register GPU memory into guest address space was successfully
/// done at page frame number `pfn` and memory slot number `slot` for buffer with `desc`.
AllocateAndRegisterGpuMemory {
descriptor: SafeDescriptor,
pfn: u64,
slot: u32,
desc: GpuMemoryDesc,
},
/// Results of balloon control commands.
BalloonStats {
stats: BalloonStats,
balloon_actual: u64,
},
/// Results of usb control commands.
UsbResponse(UsbControlResult),
/// Results of battery control commands.
BatResponse(BatControlResult),
}
impl Display for VmResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::VmResponse::*;
match self {
Ok => write!(f, "ok"),
Err(e) => write!(f, "error: {}", e),
RegisterMemory { pfn, slot } => write!(
f,
"memory registered to page frame number {:#x} and memory slot {}",
pfn, slot
),
AllocateAndRegisterGpuMemory { pfn, slot, .. } => write!(
f,
"gpu memory allocated and registered to page frame number {:#x} and memory slot {}",
pfn, slot
),
VmResponse::BalloonStats {
stats,
balloon_actual,
} => {
write!(
f,
"stats: {}\nballoon_actual: {}",
serde_json::to_string_pretty(&stats)
.unwrap_or_else(|_| "invalid_response".to_string()),
balloon_actual
)
}
UsbResponse(result) => write!(f, "usb control request get result {:?}", result),
BatResponse(result) => write!(f, "{}", result),
}
}
}