summary refs log blame commit diff
path: root/vm_control/src/lib.rs
blob: ee24b61ab382e587dbbd03f80823eab2a4986aac (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12











                                                                                                    
                       

                  
                        
                       
            
                      
 
                              
                  
                              
                                                   
 
                                
 
                                             
                                         
                                                                            
                                                


                                                                                                   

                                                       
                









                                                                                          

                                                    



         


















                                                                                          










                                                                                          











                                                          





                            











































                                                                                              


                                                                                                    
                             
                    

                                                  

                                         



                                                             








                                                                                                 

                                                                                                    




                                  


                                                                                               

                                  

 





                                    




                                                             



                                                                       



                                                                                   
 
                          

 
                



                                                                      
                                                   
                                                                                   



                                                                                                 



                                            
                                         
                                            
                                                               
                     

                                








                                                        

                              
                                                                     
                                                                                       



                                                 
                                                                                         
                                        
                                             
              
                                                        
                                                                    

                                                                                

                 
                                                                                      


                                             
                                                    

                                                                      
                               
                                                         
                              



                                                                
             
                                                     



                       
                                                                                 



                                                                                                

                                                                          





                                                                                              
                                                                              



                                                                                 

                             


                                                 






                                                                                          
                                                                 




                                                              
                                                                         







                                                                      



                                               






                                                                                                    
                             







                                                                                                   
                                                                                                 
                                                                                             





                                  

                                        
 

















                                                                                                    
                                                                                            


         
// 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.

extern crate byteorder;
extern crate kvm;
extern crate libc;
extern crate msg_socket;
extern crate resources;
#[macro_use]
extern crate sys_util;

use std::fmt::{self, Display};
use std::fs::File;
use std::io::{Seek, SeekFrom};
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};

use libc::{EINVAL, EIO, ENODEV};

use byteorder::{LittleEndian, WriteBytesExt};
use kvm::{Datamatch, IoeventAddress, Vm};
use msg_socket::{MsgOnSocket, MsgReceiver, MsgResult, MsgSender, MsgSocket};
use resources::{GpuMemoryDesc, SystemAllocator};
use sys_util::{
    net::UnixSeqpacket, Error as SysError, EventFd, GuestAddress, MemoryMapping, MmapError, Result,
};

/// A file descriptor either borrowed or owned by this.
#[derive(Debug)]
pub enum MaybeOwnedFd {
    /// Owned by this enum variant, and will be destructed automatically if not moved out.
    Owned(File),
    /// A file descriptor borrwed by this enum.
    Borrowed(RawFd),
}

impl AsRawFd for MaybeOwnedFd {
    fn as_raw_fd(&self) -> RawFd {
        match self {
            MaybeOwnedFd::Owned(f) => f.as_raw_fd(),
            MaybeOwnedFd::Borrowed(fd) => *fd,
        }
    }
}

// When sent, it could be owned or borrowed. On the receiver end, it always owned.
impl MsgOnSocket for MaybeOwnedFd {
    fn msg_size() -> usize {
        0usize
    }
    fn max_fd_count() -> usize {
        1usize
    }
    unsafe fn read_from_buffer(buffer: &[u8], fds: &[RawFd]) -> MsgResult<(Self, usize)> {
        let (fd, size) = RawFd::read_from_buffer(buffer, fds)?;
        let file = File::from_raw_fd(fd);
        Ok((MaybeOwnedFd::Owned(file), size))
    }
    fn write_to_buffer(&self, buffer: &mut [u8], fds: &mut [RawFd]) -> MsgResult<usize> {
        let fd = self.as_raw_fd();
        fd.write_to_buffer(buffer, fds)
    }
}

/// Mode of execution for the VM.
#[derive(Debug)]
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,
}

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"),
        }
    }
}

impl Default for VmRunMode {
    fn default() -> Self {
        VmRunMode::Running
    }
}

#[derive(MsgOnSocket, Debug)]
pub enum UsbControlCommand {
    AttachDevice {
        bus: u8,
        addr: u8,
        vid: u16,
        pid: u16,
        fd: Option<MaybeOwnedFd>,
    },
    DetachDevice {
        port: u8,
    },
    ListDevice {
        port: u8,
    },
}

#[derive(MsgOnSocket, Debug)]
pub enum UsbControlResult {
    Ok { port: u8 },
    NoAvailablePort,
    NoSuchDevice,
    NoSuchPort,
    FailedToOpenDevice,
    Device { port: u8, vid: u16, pid: u16 },
}

impl Display for UsbControlResult {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::UsbControlResult::*;

        match self {
            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"),
            Device { port, vid, pid } => write!(f, "device {} {:04x} {:04x}", port, vid, pid),
        }
    }
}

pub type UsbControlSocket = MsgSocket<UsbControlCommand, UsbControlResult>;

/// 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(MsgOnSocket, Debug)]
pub enum VmRequest {
    /// Set the size of the VM's balloon in bytes.
    BalloonAdjust(u64),
    /// 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,
    /// Register the given ioevent address along with given datamatch to trigger the `EventFd`.
    RegisterIoevent(EventFd, IoeventAddress, u32),
    /// Register the given IRQ number to be triggered when the `EventFd` is triggered.
    RegisterIrqfd(EventFd, u32),
    /// Register shared memory represented by the given fd into guest address space. The response
    /// variant is `VmResponse::RegisterMemory`.
    RegisterMemory(MaybeOwnedFd, usize),
    /// Unregister the given memory slot that was previously registereed with `RegisterMemory`.
    UnregisterMemory(u32),
    /// 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,
    },
    /// Resize a disk chosen by `disk_index` to `new_size` in bytes.
    /// `disk_index` is a 0-based count of `--disk`, `--rwdisk`, and `-r` command-line options.
    DiskResize { disk_index: usize, new_size: u64 },
    /// Command to use controller.
    UsbCommand(UsbControlCommand),
}

fn register_memory(
    vm: &mut Vm,
    allocator: &mut SystemAllocator,
    fd: &AsRawFd,
    size: usize,
) -> Result<(u64, u32)> {
    let mmap = match MemoryMapping::from_fd(fd, size) {
        Ok(v) => v,
        Err(MmapError::SystemCallFailed(e)) => return Err(e),
        _ => return Err(SysError::new(EINVAL)),
    };
    let addr = match allocator.allocate_device_addresses(size as u64) {
        Some(a) => a,
        None => return Err(SysError::new(EINVAL)),
    };
    let slot = match vm.add_device_memory(GuestAddress(addr), mmap, false, false) {
        Ok(v) => v,
        Err(e) => return Err(e),
    };

    Ok((addr >> 12, slot))
}

impl VmRequest {
    /// Executes this request on the given Vm and other mutable state.
    ///
    /// # Arguments
    /// * `vm` - The `Vm` to perform the request on.
    /// * `allocator` - Used to allocate addresses.
    /// * `run_mode` - Out argument that is set to a run mode if one was requested.
    ///
    /// 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,
        vm: &mut Vm,
        sys_allocator: &mut SystemAllocator,
        run_mode: &mut Option<VmRunMode>,
        balloon_host_socket: &UnixSeqpacket,
        disk_host_sockets: &[MsgSocket<VmRequest, VmResponse>],
    ) -> 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::RegisterIoevent(ref evt, addr, datamatch) => {
                match vm.register_ioevent(evt, addr, Datamatch::U32(Some(datamatch))) {
                    Ok(_) => VmResponse::Ok,
                    Err(e) => VmResponse::Err(e),
                }
            }
            VmRequest::RegisterIrqfd(ref evt, irq) => match vm.register_irqfd(evt, irq) {
                Ok(_) => VmResponse::Ok,
                Err(e) => VmResponse::Err(e),
            },
            VmRequest::RegisterMemory(ref fd, size) => {
                match register_memory(vm, sys_allocator, fd, size) {
                    Ok((pfn, slot)) => VmResponse::RegisterMemory { pfn, slot },
                    Err(e) => VmResponse::Err(e),
                }
            }
            VmRequest::UnregisterMemory(slot) => match vm.remove_device_memory(slot) {
                Ok(_) => VmResponse::Ok,
                Err(e) => VmResponse::Err(e),
            },
            VmRequest::BalloonAdjust(num_pages) => {
                let mut buf = [0u8; 8];
                // write_u64 can't fail as the buffer is 8 bytes long.
                (&mut buf[0..])
                    .write_u64::<LittleEndian>(num_pages)
                    .unwrap();
                match balloon_host_socket.send(&buf) {
                    Ok(_) => VmResponse::Ok,
                    Err(_) => VmResponse::Err(SysError::last()),
                }
            }
            VmRequest::AllocateAndRegisterGpuMemory {
                width,
                height,
                format,
            } => {
                let (mut fd, desc) = match sys_allocator.gpu_memory_allocator() {
                    Some(gpu_allocator) => match gpu_allocator.allocate(width, height, format) {
                        Ok(v) => v,
                        Err(e) => return VmResponse::Err(e),
                    },
                    None => return VmResponse::Err(SysError::new(ENODEV)),
                };
                // Determine size of buffer using 0 byte seek from end. This is preferred over
                // `stride * height` as it's not limited to packed pixel formats.
                let size = match fd.seek(SeekFrom::End(0)) {
                    Ok(v) => v,
                    Err(e) => return VmResponse::Err(SysError::from(e)),
                };
                match register_memory(vm, sys_allocator, &fd, size as usize) {
                    Ok((pfn, slot)) => VmResponse::AllocateAndRegisterGpuMemory {
                        fd: MaybeOwnedFd::Owned(fd),
                        pfn,
                        slot,
                        desc,
                    },
                    Err(e) => VmResponse::Err(e),
                }
            }
            VmRequest::DiskResize {
                disk_index,
                new_size: _,
            } => {
                // Forward the request to the block device process via its control socket.
                if let Some(sock) = disk_host_sockets.get(disk_index) {
                    if let Err(e) = sock.send(self) {
                        error!("disk socket send failed: {}", e);
                        VmResponse::Err(SysError::new(EINVAL))
                    } else {
                        match sock.recv() {
                            Ok(result) => result,
                            Err(e) => {
                                error!("disk socket recv failed: {}", e);
                                VmResponse::Err(SysError::new(EINVAL))
                            }
                        }
                    }
                } else {
                    VmResponse::Err(SysError::new(ENODEV))
                }
            }
            VmRequest::UsbCommand(ref cmd) => {
                error!("not implemented yet");
                VmResponse::Ok
            }
        }
    }
}

/// Indication of success or failure of a `VmRequest`.
///
/// Success is usually indicated `VmResponse::Ok` unless there is data associated with the response.
#[derive(MsgOnSocket, 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 {
        fd: MaybeOwnedFd,
        pfn: u64,
        slot: u32,
        desc: GpuMemoryDesc,
    },
    /// Results of usb control commands.
    UsbResponse(UsbControlResult),
}

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
            ),
            UsbResponse(result) => write!(f, "usb control request get result {:?}", result),
        }
    }
}