2019-05-08 10:22:53 +00:00
|
|
|
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
|
|
//
|
|
|
|
// Portions 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-BSD-3-Clause file.
|
|
|
|
//
|
2019-02-28 13:16:58 +00:00
|
|
|
// Copyright © 2019 Intel Corporation
|
|
|
|
//
|
2019-05-08 10:22:53 +00:00
|
|
|
// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
|
|
|
|
//
|
2019-02-28 13:16:58 +00:00
|
|
|
|
|
|
|
extern crate arch;
|
2019-03-07 13:56:43 +00:00
|
|
|
extern crate devices;
|
2019-03-18 20:59:50 +00:00
|
|
|
extern crate epoll;
|
2019-02-28 13:16:58 +00:00
|
|
|
extern crate kvm_ioctls;
|
2019-02-28 14:26:30 +00:00
|
|
|
extern crate libc;
|
2019-02-28 13:16:58 +00:00
|
|
|
extern crate linux_loader;
|
2019-05-09 15:01:42 +00:00
|
|
|
extern crate net_util;
|
2019-05-06 17:27:40 +00:00
|
|
|
extern crate vm_allocator;
|
2019-02-28 13:16:58 +00:00
|
|
|
extern crate vm_memory;
|
2019-05-06 17:27:40 +00:00
|
|
|
extern crate vm_virtio;
|
2019-02-28 14:26:30 +00:00
|
|
|
extern crate vmm_sys_util;
|
2019-02-28 13:16:58 +00:00
|
|
|
|
2019-03-06 11:04:14 +00:00
|
|
|
use kvm_bindings::{kvm_pit_config, kvm_userspace_memory_region, KVM_PIT_SPEAKER_DUMMY};
|
2019-02-28 13:16:58 +00:00
|
|
|
use kvm_ioctls::*;
|
2019-03-07 13:56:43 +00:00
|
|
|
use libc::{c_void, siginfo_t, EFD_NONBLOCK};
|
2019-02-28 13:16:58 +00:00
|
|
|
use linux_loader::cmdline;
|
|
|
|
use linux_loader::loader::KernelLoader;
|
2019-05-09 15:01:42 +00:00
|
|
|
use net_util::{MacAddr, Tap};
|
2019-05-06 17:27:40 +00:00
|
|
|
use pci::{PciConfigIo, PciDevice, PciInterruptPin, PciRoot};
|
2019-05-10 07:27:56 +00:00
|
|
|
use qcow::{self, ImageType, QcowFile};
|
2019-02-28 13:16:58 +00:00
|
|
|
use std::ffi::CString;
|
2019-05-06 17:27:40 +00:00
|
|
|
use std::fs::{File, OpenOptions};
|
2019-03-07 13:56:43 +00:00
|
|
|
use std::io::{self, stdout};
|
vmm: Add support for letting the VMM create the TAP interface
Until now, the only way to get some networking with cloud-hypervisor
was to let the user create a TAP interface first, and then to provide
the name of this interface to the VMM.
This patch extend the previous behavior by adding the support for the
creation of a brand new TAP interface from the VMM itself. In case no
interface name is provided through "tap=<if_name>", we will assume
the user wants the VMM to create and set the interface on its behalf,
no matter the value of other parameters (ip, mask, and mac).
In this same scenario, because the user expects the VMM to create the
TAP interface, he can also provide the associated IP address and subnet
mask associated with it. In case those values are not provided, some
default ones will be picked.
No matter the value of "tap", the MAC address will always be set, and
if no value is provided, the VMM will come up with a default value for
it.
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-05-09 18:21:15 +00:00
|
|
|
use std::net::Ipv4Addr;
|
2019-03-18 20:59:50 +00:00
|
|
|
use std::os::unix::io::{AsRawFd, RawFd};
|
2019-05-22 13:56:22 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
2019-03-07 13:56:43 +00:00
|
|
|
use std::sync::{Arc, Barrier, Mutex};
|
|
|
|
use std::{result, str, thread};
|
2019-05-06 17:27:40 +00:00
|
|
|
use vm_allocator::SystemAllocator;
|
2019-02-28 13:16:58 +00:00
|
|
|
use vm_memory::{
|
|
|
|
Address, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion, GuestUsize,
|
|
|
|
MmapError,
|
|
|
|
};
|
2019-05-06 17:27:40 +00:00
|
|
|
use vm_virtio::transport::VirtioPciDevice;
|
2019-02-28 14:26:30 +00:00
|
|
|
use vmm_sys_util::signal::register_signal_handler;
|
2019-03-18 20:59:50 +00:00
|
|
|
use vmm_sys_util::terminal::Terminal;
|
2019-03-07 13:56:43 +00:00
|
|
|
use vmm_sys_util::EventFd;
|
2019-02-28 13:16:58 +00:00
|
|
|
|
2019-02-28 14:26:30 +00:00
|
|
|
const VCPU_RTSIG_OFFSET: i32 = 0;
|
2019-03-07 13:56:43 +00:00
|
|
|
pub const DEFAULT_VCPUS: u8 = 1;
|
2019-03-11 16:48:35 +00:00
|
|
|
pub const DEFAULT_MEMORY: GuestUsize = 512;
|
2019-02-28 13:16:58 +00:00
|
|
|
const CMDLINE_OFFSET: GuestAddress = GuestAddress(0x20000);
|
2019-05-06 17:27:40 +00:00
|
|
|
const X86_64_IRQ_BASE: u32 = 5;
|
2019-02-28 13:16:58 +00:00
|
|
|
|
2019-04-25 17:10:42 +00:00
|
|
|
// CPUID feature bits
|
|
|
|
const ECX_HYPERVISOR_SHIFT: u32 = 31; // Hypervisor bit.
|
|
|
|
|
2019-05-10 08:21:53 +00:00
|
|
|
/// Errors associated with VM management
|
2019-02-28 13:16:58 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum Error {
|
|
|
|
/// Cannot open the VM file descriptor.
|
|
|
|
VmFd(io::Error),
|
|
|
|
|
|
|
|
/// Cannot create the KVM instance
|
|
|
|
VmCreate(io::Error),
|
|
|
|
|
|
|
|
/// Cannot set the VM up
|
|
|
|
VmSetup(io::Error),
|
|
|
|
|
|
|
|
/// Cannot open the kernel image
|
|
|
|
KernelFile(io::Error),
|
|
|
|
|
|
|
|
/// Mmap backed guest memory error
|
|
|
|
GuestMemory(MmapError),
|
|
|
|
|
|
|
|
/// Cannot load the kernel in memory
|
|
|
|
KernelLoad(linux_loader::loader::Error),
|
|
|
|
|
|
|
|
/// Cannot load the command line in memory
|
|
|
|
CmdLine,
|
2019-02-28 14:26:30 +00:00
|
|
|
|
|
|
|
/// Cannot open the VCPU file descriptor.
|
|
|
|
VcpuFd(io::Error),
|
|
|
|
|
|
|
|
/// Cannot run the VCPUs.
|
|
|
|
VcpuRun(io::Error),
|
|
|
|
|
|
|
|
/// Cannot spawn a new vCPU thread.
|
|
|
|
VcpuSpawn(io::Error),
|
|
|
|
|
|
|
|
#[cfg(target_arch = "x86_64")]
|
|
|
|
/// Cannot set the local interruption due to bad configuration.
|
|
|
|
LocalIntConfiguration(arch::x86_64::interrupts::Error),
|
|
|
|
|
|
|
|
#[cfg(target_arch = "x86_64")]
|
|
|
|
/// Error configuring the MSR registers
|
|
|
|
MSRSConfiguration(arch::x86_64::regs::Error),
|
|
|
|
|
|
|
|
#[cfg(target_arch = "x86_64")]
|
|
|
|
/// Error configuring the general purpose registers
|
|
|
|
REGSConfiguration(arch::x86_64::regs::Error),
|
|
|
|
|
|
|
|
#[cfg(target_arch = "x86_64")]
|
|
|
|
/// Error configuring the special registers
|
|
|
|
SREGSConfiguration(arch::x86_64::regs::Error),
|
|
|
|
|
|
|
|
#[cfg(target_arch = "x86_64")]
|
|
|
|
/// Error configuring the floating point related registers
|
|
|
|
FPUConfiguration(arch::x86_64::regs::Error),
|
2019-03-01 15:22:26 +00:00
|
|
|
|
|
|
|
/// The call to KVM_SET_CPUID2 failed.
|
|
|
|
SetSupportedCpusFailed(io::Error),
|
2019-03-07 13:56:43 +00:00
|
|
|
|
2019-05-14 01:12:40 +00:00
|
|
|
/// Cannot create a device manager.
|
|
|
|
DeviceManager(DeviceManagerError),
|
|
|
|
|
2019-03-07 13:56:43 +00:00
|
|
|
/// Cannot create EventFd.
|
|
|
|
EventFd(io::Error),
|
|
|
|
|
|
|
|
/// Cannot add legacy device to Bus.
|
|
|
|
BusError(devices::BusError),
|
2019-03-18 20:59:50 +00:00
|
|
|
|
|
|
|
/// Cannot create epoll context.
|
|
|
|
EpollError(io::Error),
|
|
|
|
|
|
|
|
/// Write to the serial console failed.
|
|
|
|
Serial(vmm_sys_util::Error),
|
2019-03-18 21:03:00 +00:00
|
|
|
|
2019-05-07 18:34:03 +00:00
|
|
|
/// Cannot setup terminal in raw mode.
|
|
|
|
SetTerminalRaw(vmm_sys_util::Error),
|
|
|
|
|
|
|
|
/// Cannot setup terminal in canonical mode.
|
|
|
|
SetTerminalCanon(vmm_sys_util::Error),
|
|
|
|
|
2019-03-18 21:03:00 +00:00
|
|
|
/// Cannot configure the IRQ.
|
|
|
|
Irq(io::Error),
|
2019-05-06 17:27:40 +00:00
|
|
|
|
2019-05-14 01:12:40 +00:00
|
|
|
/// Cannot create the system allocator
|
|
|
|
CreateSystemAllocator,
|
2019-05-06 17:27:40 +00:00
|
|
|
|
2019-05-14 01:12:40 +00:00
|
|
|
/// Failed parsing network parameters
|
|
|
|
ParseNetworkParameters,
|
2019-05-19 02:24:47 +00:00
|
|
|
|
|
|
|
/// Unexpected KVM_RUN exit reason
|
|
|
|
VcpuUnhandledKvmExit,
|
2019-05-14 01:12:40 +00:00
|
|
|
}
|
|
|
|
pub type Result<T> = result::Result<T, Error>;
|
|
|
|
|
|
|
|
/// Errors associated with device manager
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum DeviceManagerError {
|
|
|
|
/// Cannot create EventFd.
|
|
|
|
EventFd(io::Error),
|
2019-05-06 17:27:40 +00:00
|
|
|
|
|
|
|
/// Cannot open disk path
|
|
|
|
Disk(io::Error),
|
|
|
|
|
|
|
|
/// Cannot create virtio-blk device
|
|
|
|
CreateVirtioBlock(io::Error),
|
|
|
|
|
2019-05-09 15:01:42 +00:00
|
|
|
/// Cannot create virtio-net device
|
|
|
|
CreateVirtioNet(vm_virtio::net::Error),
|
|
|
|
|
2019-05-09 05:01:48 +00:00
|
|
|
/// Cannot create virtio-rng device
|
|
|
|
CreateVirtioRng(io::Error),
|
|
|
|
|
2019-05-14 01:12:40 +00:00
|
|
|
/// Failed parsing disk image format
|
|
|
|
DetectImageType(qcow::Error),
|
2019-05-09 15:01:42 +00:00
|
|
|
|
2019-05-14 01:12:40 +00:00
|
|
|
/// Cannot open qcow disk path
|
|
|
|
QcowDeviceCreate(qcow::Error),
|
vmm: Add support for letting the VMM create the TAP interface
Until now, the only way to get some networking with cloud-hypervisor
was to let the user create a TAP interface first, and then to provide
the name of this interface to the VMM.
This patch extend the previous behavior by adding the support for the
creation of a brand new TAP interface from the VMM itself. In case no
interface name is provided through "tap=<if_name>", we will assume
the user wants the VMM to create and set the interface on its behalf,
no matter the value of other parameters (ip, mask, and mac).
In this same scenario, because the user expects the VMM to create the
TAP interface, he can also provide the associated IP address and subnet
mask associated with it. In case those values are not provided, some
default ones will be picked.
No matter the value of "tap", the MAC address will always be set, and
if no value is provided, the VMM will come up with a default value for
it.
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-05-09 18:21:15 +00:00
|
|
|
|
|
|
|
/// Cannot open tap interface
|
|
|
|
OpenTap(net_util::TapError),
|
2019-05-10 07:27:56 +00:00
|
|
|
|
2019-05-14 01:12:40 +00:00
|
|
|
/// Cannot allocate IRQ.
|
|
|
|
AllocateIrq,
|
2019-05-10 07:27:56 +00:00
|
|
|
|
2019-05-14 01:12:40 +00:00
|
|
|
/// Cannot configure the IRQ.
|
|
|
|
Irq(io::Error),
|
|
|
|
|
|
|
|
/// Cannot allocate PCI BARs
|
|
|
|
AllocateBars(pci::PciDeviceError),
|
|
|
|
|
|
|
|
/// Cannot register ioevent.
|
|
|
|
RegisterIoevent(io::Error),
|
|
|
|
|
|
|
|
/// Cannot create virtio device
|
|
|
|
VirtioDevice(vmm_sys_util::Error),
|
|
|
|
|
|
|
|
/// Cannot add PCI device
|
|
|
|
AddPciDevice(pci::PciRootError),
|
2019-02-28 13:16:58 +00:00
|
|
|
}
|
2019-05-14 01:12:40 +00:00
|
|
|
pub type DeviceManagerResult<T> = result::Result<T, DeviceManagerError>;
|
2019-02-28 13:16:58 +00:00
|
|
|
|
2019-02-28 14:26:30 +00:00
|
|
|
/// A wrapper around creating and using a kvm-based VCPU.
|
|
|
|
pub struct Vcpu {
|
|
|
|
fd: VcpuFd,
|
|
|
|
id: u8,
|
2019-05-19 02:24:47 +00:00
|
|
|
io_bus: devices::Bus,
|
|
|
|
mmio_bus: devices::Bus,
|
2019-02-28 14:26:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Vcpu {
|
|
|
|
/// Constructs a new VCPU for `vm`.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `id` - Represents the CPU number between [0, max vcpus).
|
|
|
|
/// * `vm` - The virtual machine this vcpu will get attached to.
|
2019-05-19 02:24:47 +00:00
|
|
|
pub fn new(id: u8, vm: &Vm, io_bus: devices::Bus, mmio_bus: devices::Bus) -> Result<Self> {
|
2019-02-28 14:26:30 +00:00
|
|
|
let kvm_vcpu = vm.fd.create_vcpu(id).map_err(Error::VcpuFd)?;
|
|
|
|
// Initially the cpuid per vCPU is the one supported by this VM.
|
2019-05-19 02:24:47 +00:00
|
|
|
Ok(Vcpu {
|
|
|
|
fd: kvm_vcpu,
|
|
|
|
id,
|
|
|
|
io_bus,
|
|
|
|
mmio_bus,
|
|
|
|
})
|
2019-02-28 14:26:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Configures a x86_64 specific vcpu and should be called once per vcpu from the vcpu's thread.
|
|
|
|
///
|
|
|
|
/// # Arguments
|
|
|
|
///
|
|
|
|
/// * `machine_config` - Specifies necessary info used for the CPUID configuration.
|
|
|
|
/// * `kernel_start_addr` - Offset from `guest_mem` at which the kernel starts.
|
|
|
|
/// * `vm` - The virtual machine this vcpu will get attached to.
|
|
|
|
pub fn configure(&mut self, kernel_start_addr: GuestAddress, vm: &Vm) -> Result<()> {
|
2019-03-01 15:22:26 +00:00
|
|
|
self.fd
|
|
|
|
.set_cpuid2(&vm.cpuid)
|
|
|
|
.map_err(Error::SetSupportedCpusFailed)?;
|
|
|
|
|
2019-02-28 14:26:30 +00:00
|
|
|
arch::x86_64::regs::setup_msrs(&self.fd).map_err(Error::MSRSConfiguration)?;
|
|
|
|
// Safe to unwrap because this method is called after the VM is configured
|
|
|
|
let vm_memory = vm.get_memory();
|
|
|
|
arch::x86_64::regs::setup_regs(
|
|
|
|
&self.fd,
|
|
|
|
kernel_start_addr.raw_value(),
|
|
|
|
arch::x86_64::layout::BOOT_STACK_POINTER.raw_value(),
|
|
|
|
arch::x86_64::layout::ZERO_PAGE_START.raw_value(),
|
|
|
|
)
|
|
|
|
.map_err(Error::REGSConfiguration)?;
|
|
|
|
arch::x86_64::regs::setup_fpu(&self.fd).map_err(Error::FPUConfiguration)?;
|
|
|
|
arch::x86_64::regs::setup_sregs(vm_memory, &self.fd).map_err(Error::SREGSConfiguration)?;
|
|
|
|
arch::x86_64::interrupts::set_lint(&self.fd).map_err(Error::LocalIntConfiguration)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Runs the VCPU until it exits, returning the reason.
|
|
|
|
///
|
|
|
|
/// Note that the state of the VCPU and associated VM must be setup first for this to do
|
|
|
|
/// anything useful.
|
2019-05-19 02:24:47 +00:00
|
|
|
pub fn run(&self) -> Result<()> {
|
|
|
|
match self.fd.run() {
|
|
|
|
Ok(run) => match run {
|
|
|
|
VcpuExit::IoIn(addr, data) => {
|
|
|
|
self.io_bus.read(u64::from(addr), data);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
VcpuExit::IoOut(addr, data) => {
|
|
|
|
self.io_bus.write(u64::from(addr), data);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
VcpuExit::MmioRead(addr, data) => {
|
|
|
|
self.mmio_bus.read(addr as u64, data);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
VcpuExit::MmioWrite(addr, data) => {
|
|
|
|
self.mmio_bus.write(addr as u64, data);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
r => {
|
|
|
|
error!("Unexpected exit reason on vcpu run: {:?}", r);
|
|
|
|
Err(Error::VcpuUnhandledKvmExit)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
Err(ref e) => match e.raw_os_error().unwrap() {
|
|
|
|
libc::EAGAIN | libc::EINTR => Ok(()),
|
|
|
|
_ => {
|
|
|
|
error!("VCPU {:?} error {:?}", self.id, e);
|
|
|
|
Err(Error::VcpuUnhandledKvmExit)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
2019-02-28 14:26:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-07 13:56:43 +00:00
|
|
|
pub struct VmConfig<'a> {
|
2019-02-28 13:16:58 +00:00
|
|
|
kernel_path: &'a Path,
|
2019-05-22 13:56:22 +00:00
|
|
|
disk_paths: Vec<PathBuf>,
|
2019-05-09 05:01:48 +00:00
|
|
|
rng_path: Option<String>,
|
2019-05-06 20:24:57 +00:00
|
|
|
cmdline: cmdline::Cmdline,
|
2019-02-28 13:16:58 +00:00
|
|
|
cmdline_addr: GuestAddress,
|
2019-05-09 15:01:42 +00:00
|
|
|
net_params: Option<String>,
|
2019-02-28 13:16:58 +00:00
|
|
|
memory_size: GuestUsize,
|
|
|
|
vcpu_count: u8,
|
|
|
|
}
|
|
|
|
|
2019-03-07 13:56:43 +00:00
|
|
|
impl<'a> VmConfig<'a> {
|
2019-05-06 19:15:44 +00:00
|
|
|
pub fn new(
|
|
|
|
kernel_path: &'a Path,
|
2019-05-22 13:56:22 +00:00
|
|
|
disk_paths: Vec<PathBuf>,
|
2019-05-09 05:01:48 +00:00
|
|
|
rng_path: Option<String>,
|
2019-05-06 20:24:57 +00:00
|
|
|
cmdline_str: String,
|
2019-05-09 15:01:42 +00:00
|
|
|
net_params: Option<String>,
|
2019-05-06 19:15:44 +00:00
|
|
|
vcpus: u8,
|
|
|
|
memory_size: GuestUsize,
|
|
|
|
) -> Result<Self> {
|
2019-05-06 20:24:57 +00:00
|
|
|
let mut cmdline = cmdline::Cmdline::new(arch::CMDLINE_MAX_SIZE);
|
|
|
|
cmdline.insert_str(cmdline_str).unwrap();
|
|
|
|
|
2019-03-07 13:56:43 +00:00
|
|
|
Ok(VmConfig {
|
|
|
|
kernel_path,
|
2019-05-22 13:56:22 +00:00
|
|
|
disk_paths,
|
2019-05-09 05:01:48 +00:00
|
|
|
rng_path,
|
2019-05-06 20:24:57 +00:00
|
|
|
cmdline,
|
|
|
|
cmdline_addr: CMDLINE_OFFSET,
|
2019-05-09 15:01:42 +00:00
|
|
|
net_params,
|
2019-03-11 16:48:35 +00:00
|
|
|
memory_size,
|
2019-03-11 16:36:27 +00:00
|
|
|
vcpu_count: vcpus,
|
2019-03-07 13:56:43 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
vmm: Add support for letting the VMM create the TAP interface
Until now, the only way to get some networking with cloud-hypervisor
was to let the user create a TAP interface first, and then to provide
the name of this interface to the VMM.
This patch extend the previous behavior by adding the support for the
creation of a brand new TAP interface from the VMM itself. In case no
interface name is provided through "tap=<if_name>", we will assume
the user wants the VMM to create and set the interface on its behalf,
no matter the value of other parameters (ip, mask, and mac).
In this same scenario, because the user expects the VMM to create the
TAP interface, he can also provide the associated IP address and subnet
mask associated with it. In case those values are not provided, some
default ones will be picked.
No matter the value of "tap", the MAC address will always be set, and
if no value is provided, the VMM will come up with a default value for
it.
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-05-09 18:21:15 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
struct NetParams<'a> {
|
|
|
|
tap_if_name: Option<&'a str>,
|
|
|
|
ip_addr: Ipv4Addr,
|
|
|
|
net_mask: Ipv4Addr,
|
|
|
|
mac_addr: MacAddr,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn parse_net_params(net_params: &str) -> Result<NetParams> {
|
2019-05-09 15:01:42 +00:00
|
|
|
// Split the parameters based on the comma delimiter
|
|
|
|
let params_list: Vec<&str> = net_params.split(',').collect();
|
|
|
|
|
vmm: Add support for letting the VMM create the TAP interface
Until now, the only way to get some networking with cloud-hypervisor
was to let the user create a TAP interface first, and then to provide
the name of this interface to the VMM.
This patch extend the previous behavior by adding the support for the
creation of a brand new TAP interface from the VMM itself. In case no
interface name is provided through "tap=<if_name>", we will assume
the user wants the VMM to create and set the interface on its behalf,
no matter the value of other parameters (ip, mask, and mac).
In this same scenario, because the user expects the VMM to create the
TAP interface, he can also provide the associated IP address and subnet
mask associated with it. In case those values are not provided, some
default ones will be picked.
No matter the value of "tap", the MAC address will always be set, and
if no value is provided, the VMM will come up with a default value for
it.
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-05-09 18:21:15 +00:00
|
|
|
let mut tap: &str = "";
|
|
|
|
let mut ip: &str = "";
|
|
|
|
let mut mask: &str = "";
|
|
|
|
let mut mac: &str = "";
|
2019-05-09 15:01:42 +00:00
|
|
|
|
|
|
|
for param in params_list.iter() {
|
|
|
|
if param.starts_with("tap=") {
|
vmm: Add support for letting the VMM create the TAP interface
Until now, the only way to get some networking with cloud-hypervisor
was to let the user create a TAP interface first, and then to provide
the name of this interface to the VMM.
This patch extend the previous behavior by adding the support for the
creation of a brand new TAP interface from the VMM itself. In case no
interface name is provided through "tap=<if_name>", we will assume
the user wants the VMM to create and set the interface on its behalf,
no matter the value of other parameters (ip, mask, and mac).
In this same scenario, because the user expects the VMM to create the
TAP interface, he can also provide the associated IP address and subnet
mask associated with it. In case those values are not provided, some
default ones will be picked.
No matter the value of "tap", the MAC address will always be set, and
if no value is provided, the VMM will come up with a default value for
it.
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-05-09 18:21:15 +00:00
|
|
|
tap = ¶m[4..];
|
|
|
|
} else if param.starts_with("ip=") {
|
|
|
|
ip = ¶m[3..];
|
|
|
|
} else if param.starts_with("mask=") {
|
|
|
|
mask = ¶m[5..];
|
2019-05-09 15:01:42 +00:00
|
|
|
} else if param.starts_with("mac=") {
|
vmm: Add support for letting the VMM create the TAP interface
Until now, the only way to get some networking with cloud-hypervisor
was to let the user create a TAP interface first, and then to provide
the name of this interface to the VMM.
This patch extend the previous behavior by adding the support for the
creation of a brand new TAP interface from the VMM itself. In case no
interface name is provided through "tap=<if_name>", we will assume
the user wants the VMM to create and set the interface on its behalf,
no matter the value of other parameters (ip, mask, and mac).
In this same scenario, because the user expects the VMM to create the
TAP interface, he can also provide the associated IP address and subnet
mask associated with it. In case those values are not provided, some
default ones will be picked.
No matter the value of "tap", the MAC address will always be set, and
if no value is provided, the VMM will come up with a default value for
it.
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-05-09 18:21:15 +00:00
|
|
|
mac = ¶m[4..];
|
2019-05-09 15:01:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
vmm: Add support for letting the VMM create the TAP interface
Until now, the only way to get some networking with cloud-hypervisor
was to let the user create a TAP interface first, and then to provide
the name of this interface to the VMM.
This patch extend the previous behavior by adding the support for the
creation of a brand new TAP interface from the VMM itself. In case no
interface name is provided through "tap=<if_name>", we will assume
the user wants the VMM to create and set the interface on its behalf,
no matter the value of other parameters (ip, mask, and mac).
In this same scenario, because the user expects the VMM to create the
TAP interface, he can also provide the associated IP address and subnet
mask associated with it. In case those values are not provided, some
default ones will be picked.
No matter the value of "tap", the MAC address will always be set, and
if no value is provided, the VMM will come up with a default value for
it.
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-05-09 18:21:15 +00:00
|
|
|
let mut tap_if_name: Option<&str> = None;
|
|
|
|
let mut ip_addr: Ipv4Addr = "192.168.249.1".parse().unwrap();
|
|
|
|
let mut net_mask: Ipv4Addr = "255.255.255.0".parse().unwrap();
|
2019-05-10 05:22:11 +00:00
|
|
|
let mut mac_addr: MacAddr = MacAddr::local_random();
|
vmm: Add support for letting the VMM create the TAP interface
Until now, the only way to get some networking with cloud-hypervisor
was to let the user create a TAP interface first, and then to provide
the name of this interface to the VMM.
This patch extend the previous behavior by adding the support for the
creation of a brand new TAP interface from the VMM itself. In case no
interface name is provided through "tap=<if_name>", we will assume
the user wants the VMM to create and set the interface on its behalf,
no matter the value of other parameters (ip, mask, and mac).
In this same scenario, because the user expects the VMM to create the
TAP interface, he can also provide the associated IP address and subnet
mask associated with it. In case those values are not provided, some
default ones will be picked.
No matter the value of "tap", the MAC address will always be set, and
if no value is provided, the VMM will come up with a default value for
it.
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-05-09 18:21:15 +00:00
|
|
|
|
|
|
|
if !tap.is_empty() {
|
|
|
|
tap_if_name = Some(tap);
|
|
|
|
}
|
|
|
|
if !ip.is_empty() {
|
|
|
|
ip_addr = ip.parse().unwrap();
|
|
|
|
}
|
|
|
|
if !mask.is_empty() {
|
|
|
|
net_mask = mask.parse().unwrap();
|
|
|
|
}
|
|
|
|
if !mac.is_empty() {
|
|
|
|
mac_addr = MacAddr::parse_str(mac).unwrap();
|
2019-05-09 15:01:42 +00:00
|
|
|
}
|
|
|
|
|
vmm: Add support for letting the VMM create the TAP interface
Until now, the only way to get some networking with cloud-hypervisor
was to let the user create a TAP interface first, and then to provide
the name of this interface to the VMM.
This patch extend the previous behavior by adding the support for the
creation of a brand new TAP interface from the VMM itself. In case no
interface name is provided through "tap=<if_name>", we will assume
the user wants the VMM to create and set the interface on its behalf,
no matter the value of other parameters (ip, mask, and mac).
In this same scenario, because the user expects the VMM to create the
TAP interface, he can also provide the associated IP address and subnet
mask associated with it. In case those values are not provided, some
default ones will be picked.
No matter the value of "tap", the MAC address will always be set, and
if no value is provided, the VMM will come up with a default value for
it.
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-05-09 18:21:15 +00:00
|
|
|
Ok(NetParams {
|
|
|
|
tap_if_name,
|
|
|
|
ip_addr,
|
|
|
|
net_mask,
|
|
|
|
mac_addr,
|
|
|
|
})
|
2019-05-09 15:01:42 +00:00
|
|
|
}
|
|
|
|
|
2019-03-07 13:56:43 +00:00
|
|
|
struct DeviceManager {
|
|
|
|
io_bus: devices::Bus,
|
2019-05-06 17:27:40 +00:00
|
|
|
mmio_bus: devices::Bus,
|
2019-03-07 13:56:43 +00:00
|
|
|
|
|
|
|
// Serial port on 0x3f8
|
|
|
|
serial: Arc<Mutex<devices::legacy::Serial>>,
|
|
|
|
serial_evt: EventFd,
|
2019-04-15 18:57:30 +00:00
|
|
|
|
|
|
|
// i8042 device for exit
|
|
|
|
i8042: Arc<Mutex<devices::legacy::I8042Device>>,
|
2019-04-15 17:59:38 +00:00
|
|
|
exit_evt: EventFd,
|
2019-04-18 10:26:09 +00:00
|
|
|
|
|
|
|
// PCI root
|
|
|
|
pci: Arc<Mutex<PciConfigIo>>,
|
2019-03-07 13:56:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl DeviceManager {
|
2019-05-06 19:15:44 +00:00
|
|
|
fn new(
|
|
|
|
memory: GuestMemoryMmap,
|
|
|
|
allocator: &mut SystemAllocator,
|
|
|
|
vm_fd: &VmFd,
|
|
|
|
vm_cfg: &VmConfig,
|
2019-05-14 01:12:40 +00:00
|
|
|
) -> DeviceManagerResult<Self> {
|
2019-03-07 13:56:43 +00:00
|
|
|
let io_bus = devices::Bus::new();
|
2019-05-06 17:27:40 +00:00
|
|
|
let mut mmio_bus = devices::Bus::new();
|
2019-05-14 01:12:40 +00:00
|
|
|
let serial_evt = EventFd::new(EFD_NONBLOCK).map_err(DeviceManagerError::EventFd)?;
|
2019-03-07 13:56:43 +00:00
|
|
|
let serial = Arc::new(Mutex::new(devices::legacy::Serial::new_out(
|
2019-05-14 01:12:40 +00:00
|
|
|
serial_evt
|
|
|
|
.try_clone()
|
|
|
|
.map_err(DeviceManagerError::EventFd)?,
|
2019-03-07 13:56:43 +00:00
|
|
|
Box::new(stdout()),
|
|
|
|
)));
|
|
|
|
|
2019-05-14 01:12:40 +00:00
|
|
|
let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(DeviceManagerError::EventFd)?;
|
2019-04-15 18:57:30 +00:00
|
|
|
let i8042 = Arc::new(Mutex::new(devices::legacy::I8042Device::new(
|
2019-05-14 01:12:40 +00:00
|
|
|
exit_evt.try_clone().map_err(DeviceManagerError::EventFd)?,
|
2019-04-15 18:57:30 +00:00
|
|
|
)));
|
2019-04-15 17:59:38 +00:00
|
|
|
|
2019-05-06 17:27:40 +00:00
|
|
|
let mut pci_root = PciRoot::new(None);
|
|
|
|
|
2019-05-22 13:56:22 +00:00
|
|
|
for disk_path in &vm_cfg.disk_paths {
|
|
|
|
// Open block device path
|
|
|
|
let raw_img: File = OpenOptions::new()
|
|
|
|
.read(true)
|
|
|
|
.write(true)
|
|
|
|
.open(disk_path.as_path())
|
|
|
|
.map_err(DeviceManagerError::Disk)?;
|
|
|
|
|
|
|
|
// Add virtio-blk
|
|
|
|
let image_type =
|
|
|
|
qcow::detect_image_type(&raw_img).map_err(DeviceManagerError::DetectImageType)?;
|
|
|
|
let block = match image_type {
|
|
|
|
ImageType::Raw => {
|
|
|
|
let raw_img = vm_virtio::RawFile::new(raw_img);
|
|
|
|
let dev = vm_virtio::Block::new(raw_img, disk_path.to_path_buf(), false)
|
|
|
|
.map_err(DeviceManagerError::CreateVirtioBlock)?;
|
|
|
|
Box::new(dev) as Box<vm_virtio::VirtioDevice>
|
|
|
|
}
|
|
|
|
ImageType::Qcow2 => {
|
|
|
|
let qcow_img =
|
|
|
|
QcowFile::from(raw_img).map_err(DeviceManagerError::QcowDeviceCreate)?;
|
|
|
|
let dev = vm_virtio::Block::new(qcow_img, disk_path.to_path_buf(), false)
|
|
|
|
.map_err(DeviceManagerError::CreateVirtioBlock)?;
|
|
|
|
Box::new(dev) as Box<vm_virtio::VirtioDevice>
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
DeviceManager::add_virtio_pci_device(
|
|
|
|
block,
|
|
|
|
memory.clone(),
|
|
|
|
allocator,
|
|
|
|
vm_fd,
|
|
|
|
&mut pci_root,
|
|
|
|
&mut mmio_bus,
|
|
|
|
)?;
|
|
|
|
}
|
2019-05-08 10:41:24 +00:00
|
|
|
|
|
|
|
// Add virtio-net if required
|
|
|
|
if let Some(net_params) = &vm_cfg.net_params {
|
vmm: Add support for letting the VMM create the TAP interface
Until now, the only way to get some networking with cloud-hypervisor
was to let the user create a TAP interface first, and then to provide
the name of this interface to the VMM.
This patch extend the previous behavior by adding the support for the
creation of a brand new TAP interface from the VMM itself. In case no
interface name is provided through "tap=<if_name>", we will assume
the user wants the VMM to create and set the interface on its behalf,
no matter the value of other parameters (ip, mask, and mac).
In this same scenario, because the user expects the VMM to create the
TAP interface, he can also provide the associated IP address and subnet
mask associated with it. In case those values are not provided, some
default ones will be picked.
No matter the value of "tap", the MAC address will always be set, and
if no value is provided, the VMM will come up with a default value for
it.
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-05-09 18:21:15 +00:00
|
|
|
if let Ok(net_params) = parse_net_params(net_params) {
|
|
|
|
let mut virtio_net_device: vm_virtio::Net;
|
|
|
|
|
|
|
|
if let Some(tap_if_name) = net_params.tap_if_name {
|
2019-05-14 01:12:40 +00:00
|
|
|
let tap = Tap::open_named(tap_if_name).map_err(DeviceManagerError::OpenTap)?;
|
vmm: Add support for letting the VMM create the TAP interface
Until now, the only way to get some networking with cloud-hypervisor
was to let the user create a TAP interface first, and then to provide
the name of this interface to the VMM.
This patch extend the previous behavior by adding the support for the
creation of a brand new TAP interface from the VMM itself. In case no
interface name is provided through "tap=<if_name>", we will assume
the user wants the VMM to create and set the interface on its behalf,
no matter the value of other parameters (ip, mask, and mac).
In this same scenario, because the user expects the VMM to create the
TAP interface, he can also provide the associated IP address and subnet
mask associated with it. In case those values are not provided, some
default ones will be picked.
No matter the value of "tap", the MAC address will always be set, and
if no value is provided, the VMM will come up with a default value for
it.
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-05-09 18:21:15 +00:00
|
|
|
virtio_net_device =
|
|
|
|
vm_virtio::Net::new_with_tap(tap, Some(&net_params.mac_addr))
|
2019-05-14 01:12:40 +00:00
|
|
|
.map_err(DeviceManagerError::CreateVirtioNet)?;
|
vmm: Add support for letting the VMM create the TAP interface
Until now, the only way to get some networking with cloud-hypervisor
was to let the user create a TAP interface first, and then to provide
the name of this interface to the VMM.
This patch extend the previous behavior by adding the support for the
creation of a brand new TAP interface from the VMM itself. In case no
interface name is provided through "tap=<if_name>", we will assume
the user wants the VMM to create and set the interface on its behalf,
no matter the value of other parameters (ip, mask, and mac).
In this same scenario, because the user expects the VMM to create the
TAP interface, he can also provide the associated IP address and subnet
mask associated with it. In case those values are not provided, some
default ones will be picked.
No matter the value of "tap", the MAC address will always be set, and
if no value is provided, the VMM will come up with a default value for
it.
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-05-09 18:21:15 +00:00
|
|
|
} else {
|
|
|
|
virtio_net_device = vm_virtio::Net::new(
|
|
|
|
net_params.ip_addr,
|
|
|
|
net_params.net_mask,
|
|
|
|
Some(&net_params.mac_addr),
|
|
|
|
)
|
2019-05-14 01:12:40 +00:00
|
|
|
.map_err(DeviceManagerError::CreateVirtioNet)?;
|
vmm: Add support for letting the VMM create the TAP interface
Until now, the only way to get some networking with cloud-hypervisor
was to let the user create a TAP interface first, and then to provide
the name of this interface to the VMM.
This patch extend the previous behavior by adding the support for the
creation of a brand new TAP interface from the VMM itself. In case no
interface name is provided through "tap=<if_name>", we will assume
the user wants the VMM to create and set the interface on its behalf,
no matter the value of other parameters (ip, mask, and mac).
In this same scenario, because the user expects the VMM to create the
TAP interface, he can also provide the associated IP address and subnet
mask associated with it. In case those values are not provided, some
default ones will be picked.
No matter the value of "tap", the MAC address will always be set, and
if no value is provided, the VMM will come up with a default value for
it.
Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
2019-05-09 18:21:15 +00:00
|
|
|
}
|
2019-05-08 10:41:24 +00:00
|
|
|
|
|
|
|
DeviceManager::add_virtio_pci_device(
|
|
|
|
Box::new(virtio_net_device),
|
|
|
|
memory.clone(),
|
|
|
|
allocator,
|
|
|
|
vm_fd,
|
|
|
|
&mut pci_root,
|
|
|
|
&mut mmio_bus,
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-09 05:01:48 +00:00
|
|
|
// Add virtio-rng if required
|
|
|
|
if let Some(rng_path) = &vm_cfg.rng_path {
|
|
|
|
let virtio_rng_device =
|
2019-05-14 01:12:40 +00:00
|
|
|
vm_virtio::Rng::new(rng_path).map_err(DeviceManagerError::CreateVirtioRng)?;
|
2019-05-09 05:01:48 +00:00
|
|
|
|
|
|
|
DeviceManager::add_virtio_pci_device(
|
|
|
|
Box::new(virtio_rng_device),
|
|
|
|
memory.clone(),
|
|
|
|
allocator,
|
|
|
|
vm_fd,
|
|
|
|
&mut pci_root,
|
|
|
|
&mut mmio_bus,
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
2019-05-08 10:41:24 +00:00
|
|
|
let pci = Arc::new(Mutex::new(PciConfigIo::new(pci_root)));
|
|
|
|
|
|
|
|
Ok(DeviceManager {
|
|
|
|
io_bus,
|
|
|
|
mmio_bus,
|
|
|
|
serial,
|
|
|
|
serial_evt,
|
|
|
|
i8042,
|
|
|
|
exit_evt,
|
|
|
|
pci,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn add_virtio_pci_device(
|
|
|
|
virtio_device: Box<vm_virtio::VirtioDevice>,
|
|
|
|
memory: GuestMemoryMmap,
|
|
|
|
allocator: &mut SystemAllocator,
|
|
|
|
vm_fd: &VmFd,
|
|
|
|
pci_root: &mut PciRoot,
|
|
|
|
mmio_bus: &mut devices::Bus,
|
2019-05-14 01:12:40 +00:00
|
|
|
) -> DeviceManagerResult<()> {
|
|
|
|
let mut virtio_pci_device = VirtioPciDevice::new(memory, virtio_device)
|
|
|
|
.map_err(DeviceManagerError::VirtioDevice)?;
|
2019-05-06 17:27:40 +00:00
|
|
|
let bars = virtio_pci_device
|
|
|
|
.allocate_bars(allocator)
|
2019-05-14 01:12:40 +00:00
|
|
|
.map_err(DeviceManagerError::AllocateBars)?;
|
2019-05-06 17:27:40 +00:00
|
|
|
|
|
|
|
for (event, addr, _) in virtio_pci_device.ioeventfds() {
|
|
|
|
let io_addr = IoEventAddress::Mmio(addr);
|
|
|
|
vm_fd
|
|
|
|
.register_ioevent(event.as_raw_fd(), &io_addr, NoDatamatch)
|
2019-05-14 01:12:40 +00:00
|
|
|
.map_err(DeviceManagerError::RegisterIoevent)?;
|
2019-05-06 17:27:40 +00:00
|
|
|
}
|
|
|
|
|
2019-05-08 10:41:24 +00:00
|
|
|
// Assign IRQ to the virtio-pci device
|
2019-05-14 01:12:40 +00:00
|
|
|
let irqfd = EventFd::new(EFD_NONBLOCK).map_err(DeviceManagerError::EventFd)?;
|
|
|
|
let irq_num = allocator
|
|
|
|
.allocate_irq()
|
|
|
|
.ok_or(DeviceManagerError::AllocateIrq)?;
|
2019-05-06 17:27:40 +00:00
|
|
|
vm_fd
|
|
|
|
.register_irqfd(irqfd.as_raw_fd(), irq_num)
|
2019-05-14 01:12:40 +00:00
|
|
|
.map_err(DeviceManagerError::Irq)?;
|
2019-05-06 17:27:40 +00:00
|
|
|
// Let's use irq line INTA for now.
|
|
|
|
virtio_pci_device.assign_irq(irqfd, irq_num as u32, PciInterruptPin::IntA);
|
|
|
|
|
|
|
|
let virtio_pci_device = Arc::new(Mutex::new(virtio_pci_device));
|
|
|
|
|
|
|
|
pci_root
|
2019-05-08 10:41:24 +00:00
|
|
|
.add_device(virtio_pci_device.clone(), mmio_bus, bars)
|
2019-05-14 01:12:40 +00:00
|
|
|
.map_err(DeviceManagerError::AddPciDevice)?;
|
2019-05-06 17:27:40 +00:00
|
|
|
|
2019-05-08 10:41:24 +00:00
|
|
|
Ok(())
|
2019-03-07 13:56:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn register_devices(&mut self) -> Result<()> {
|
2019-04-15 18:57:30 +00:00
|
|
|
// Insert serial device
|
2019-03-07 13:56:43 +00:00
|
|
|
self.io_bus
|
|
|
|
.insert(self.serial.clone(), 0x3f8, 0x8)
|
|
|
|
.map_err(Error::BusError)?;
|
2019-04-15 18:57:30 +00:00
|
|
|
|
|
|
|
// Insert i8042 device
|
|
|
|
self.io_bus
|
|
|
|
.insert(self.i8042.clone(), 0x61, 0x4)
|
|
|
|
.map_err(Error::BusError)?;
|
|
|
|
|
2019-04-18 10:26:09 +00:00
|
|
|
// Insert the PCI root configuration space.
|
|
|
|
self.io_bus
|
|
|
|
.insert(self.pci.clone(), 0xcf8, 0x8)
|
|
|
|
.map_err(Error::BusError)?;
|
2019-03-07 13:56:43 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-15 17:14:31 +00:00
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
|
|
enum EpollDispatch {
|
2019-04-15 17:59:38 +00:00
|
|
|
Exit,
|
2019-04-15 17:14:31 +00:00
|
|
|
Stdin,
|
|
|
|
}
|
|
|
|
|
2019-03-18 20:59:50 +00:00
|
|
|
pub struct EpollContext {
|
|
|
|
raw_fd: RawFd,
|
2019-04-15 17:14:31 +00:00
|
|
|
dispatch_table: Vec<Option<EpollDispatch>>,
|
2019-03-18 20:59:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl EpollContext {
|
|
|
|
pub fn new() -> result::Result<EpollContext, io::Error> {
|
|
|
|
let raw_fd = epoll::create(true)?;
|
2019-04-15 17:14:31 +00:00
|
|
|
|
|
|
|
// Initial capacity needs to be large enough to hold:
|
2019-04-15 17:59:38 +00:00
|
|
|
// * 1 exit event
|
2019-04-15 17:14:31 +00:00
|
|
|
// * 1 stdin event
|
2019-04-15 17:59:38 +00:00
|
|
|
let mut dispatch_table = Vec::with_capacity(3);
|
2019-04-15 17:14:31 +00:00
|
|
|
dispatch_table.push(None);
|
|
|
|
|
|
|
|
Ok(EpollContext {
|
|
|
|
raw_fd,
|
|
|
|
dispatch_table,
|
|
|
|
})
|
2019-03-18 20:59:50 +00:00
|
|
|
}
|
|
|
|
|
2019-04-15 17:14:31 +00:00
|
|
|
pub fn add_stdin(&mut self) -> result::Result<(), io::Error> {
|
|
|
|
let dispatch_index = self.dispatch_table.len() as u64;
|
2019-03-18 20:59:50 +00:00
|
|
|
epoll::ctl(
|
|
|
|
self.raw_fd,
|
|
|
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
|
|
|
libc::STDIN_FILENO,
|
2019-04-15 17:14:31 +00:00
|
|
|
epoll::Event::new(epoll::Events::EPOLLIN, dispatch_index),
|
2019-03-18 20:59:50 +00:00
|
|
|
)?;
|
|
|
|
|
2019-04-15 17:14:31 +00:00
|
|
|
self.dispatch_table.push(Some(EpollDispatch::Stdin));
|
|
|
|
|
2019-03-18 20:59:50 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2019-04-15 17:59:38 +00:00
|
|
|
|
|
|
|
fn add_event<T>(&mut self, fd: &T, token: EpollDispatch) -> result::Result<(), io::Error>
|
|
|
|
where
|
|
|
|
T: AsRawFd,
|
|
|
|
{
|
|
|
|
let dispatch_index = self.dispatch_table.len() as u64;
|
|
|
|
epoll::ctl(
|
|
|
|
self.raw_fd,
|
|
|
|
epoll::ControlOptions::EPOLL_CTL_ADD,
|
|
|
|
fd.as_raw_fd(),
|
|
|
|
epoll::Event::new(epoll::Events::EPOLLIN, dispatch_index),
|
|
|
|
)?;
|
|
|
|
self.dispatch_table.push(Some(token));
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2019-03-18 20:59:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl AsRawFd for EpollContext {
|
|
|
|
fn as_raw_fd(&self) -> RawFd {
|
|
|
|
self.raw_fd
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-28 13:16:58 +00:00
|
|
|
pub struct Vm<'a> {
|
|
|
|
fd: VmFd,
|
|
|
|
kernel: File,
|
|
|
|
memory: GuestMemoryMmap,
|
2019-05-08 06:47:59 +00:00
|
|
|
vcpus: Vec<thread::JoinHandle<()>>,
|
2019-03-07 13:56:43 +00:00
|
|
|
devices: DeviceManager,
|
2019-03-01 15:22:26 +00:00
|
|
|
cpuid: CpuId,
|
2019-02-28 13:16:58 +00:00
|
|
|
config: VmConfig<'a>,
|
2019-03-18 20:59:50 +00:00
|
|
|
epoll: EpollContext,
|
2019-02-28 13:16:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Vm<'a> {
|
2019-03-07 13:56:43 +00:00
|
|
|
pub fn new(kvm: &Kvm, config: VmConfig<'a>) -> Result<Self> {
|
|
|
|
let kernel = File::open(&config.kernel_path).map_err(Error::KernelFile)?;
|
2019-02-28 13:16:58 +00:00
|
|
|
let fd = kvm.create_vm().map_err(Error::VmCreate)?;
|
|
|
|
|
|
|
|
// Init guest memory
|
2019-03-07 13:56:43 +00:00
|
|
|
let arch_mem_regions = arch::arch_memory_regions(config.memory_size << 20);
|
2019-02-28 13:16:58 +00:00
|
|
|
let guest_memory = GuestMemoryMmap::new(&arch_mem_regions).map_err(Error::GuestMemory)?;
|
|
|
|
|
|
|
|
guest_memory
|
|
|
|
.with_regions(|index, region| {
|
|
|
|
let mem_region = kvm_userspace_memory_region {
|
|
|
|
slot: index as u32,
|
|
|
|
guest_phys_addr: region.start_addr().raw_value(),
|
|
|
|
memory_size: region.len() as u64,
|
|
|
|
userspace_addr: region.as_ptr() as u64,
|
|
|
|
flags: 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Safe because the guest regions are guaranteed not to overlap.
|
|
|
|
fd.set_user_memory_region(mem_region)
|
|
|
|
})
|
|
|
|
.map_err(|_| Error::GuestMemory(MmapError::NoMemoryRegion))?;
|
|
|
|
|
|
|
|
// Set TSS
|
|
|
|
fd.set_tss_address(arch::x86_64::layout::KVM_TSS_ADDRESS.raw_value() as usize)
|
|
|
|
.map_err(Error::VmSetup)?;
|
|
|
|
|
2019-02-28 14:26:30 +00:00
|
|
|
// Create IRQ chip
|
|
|
|
fd.create_irq_chip().map_err(Error::VmSetup)?;
|
|
|
|
|
2019-03-06 11:04:14 +00:00
|
|
|
// Creates an in-kernel device model for the PIT.
|
|
|
|
let mut pit_config = kvm_pit_config::default();
|
|
|
|
// We need to enable the emulation of a dummy speaker port stub so that writing to port 0x61
|
|
|
|
// (i.e. KVM_SPEAKER_BASE_ADDRESS) does not trigger an exit to user space.
|
|
|
|
pit_config.flags = KVM_PIT_SPEAKER_DUMMY;
|
|
|
|
fd.create_pit2(pit_config).map_err(Error::VmSetup)?;
|
|
|
|
|
2019-03-01 15:22:26 +00:00
|
|
|
// Supported CPUID
|
2019-04-25 17:10:42 +00:00
|
|
|
let mut cpuid = kvm
|
2019-03-01 15:22:26 +00:00
|
|
|
.get_supported_cpuid(MAX_KVM_CPUID_ENTRIES)
|
|
|
|
.map_err(Error::VmSetup)?;
|
2019-04-25 17:10:42 +00:00
|
|
|
Vm::patch_cpuid(&mut cpuid);
|
2019-03-01 15:22:26 +00:00
|
|
|
|
2019-05-06 17:27:40 +00:00
|
|
|
// Let's allocate 64 GiB of addressable MMIO space, starting at 0.
|
|
|
|
let mut allocator = SystemAllocator::new(
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
GuestAddress(0),
|
|
|
|
1 << 36 as GuestUsize,
|
|
|
|
X86_64_IRQ_BASE,
|
|
|
|
)
|
|
|
|
.ok_or(Error::CreateSystemAllocator)?;
|
|
|
|
|
2019-05-06 19:15:44 +00:00
|
|
|
let device_manager = DeviceManager::new(guest_memory.clone(), &mut allocator, &fd, &config)
|
2019-05-14 01:12:40 +00:00
|
|
|
.map_err(Error::DeviceManager)?;
|
2019-03-18 21:03:00 +00:00
|
|
|
fd.register_irqfd(device_manager.serial_evt.as_raw_fd(), 4)
|
|
|
|
.map_err(Error::Irq)?;
|
2019-03-07 13:56:43 +00:00
|
|
|
|
2019-03-18 20:59:50 +00:00
|
|
|
// Let's add our STDIN fd.
|
2019-04-15 17:14:31 +00:00
|
|
|
let mut epoll = EpollContext::new().map_err(Error::EpollError)?;
|
2019-03-18 20:59:50 +00:00
|
|
|
epoll.add_stdin().map_err(Error::EpollError)?;
|
|
|
|
|
2019-04-15 17:59:38 +00:00
|
|
|
// Let's add an exit event.
|
|
|
|
epoll
|
|
|
|
.add_event(&device_manager.exit_evt, EpollDispatch::Exit)
|
|
|
|
.map_err(Error::EpollError)?;
|
|
|
|
|
2019-05-08 06:47:59 +00:00
|
|
|
let vcpus = Vec::with_capacity(config.vcpu_count as usize);
|
|
|
|
|
2019-02-28 13:16:58 +00:00
|
|
|
Ok(Vm {
|
|
|
|
fd,
|
|
|
|
kernel,
|
|
|
|
memory: guest_memory,
|
2019-05-08 06:47:59 +00:00
|
|
|
vcpus,
|
2019-03-07 13:56:43 +00:00
|
|
|
devices: device_manager,
|
2019-03-01 15:22:26 +00:00
|
|
|
cpuid,
|
2019-03-07 13:56:43 +00:00
|
|
|
config,
|
2019-03-18 20:59:50 +00:00
|
|
|
epoll,
|
2019-02-28 13:16:58 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn load_kernel(&mut self) -> Result<GuestAddress> {
|
2019-05-06 20:24:57 +00:00
|
|
|
let cmdline_cstring =
|
|
|
|
CString::new(self.config.cmdline.clone()).map_err(|_| Error::CmdLine)?;
|
2019-02-28 13:16:58 +00:00
|
|
|
let entry_addr = linux_loader::loader::Elf::load(
|
|
|
|
&self.memory,
|
|
|
|
None,
|
|
|
|
&mut self.kernel,
|
|
|
|
Some(arch::HIMEM_START),
|
|
|
|
)
|
|
|
|
.map_err(Error::KernelLoad)?;
|
|
|
|
|
|
|
|
linux_loader::loader::load_cmdline(
|
|
|
|
&self.memory,
|
|
|
|
self.config.cmdline_addr,
|
|
|
|
&cmdline_cstring,
|
|
|
|
)
|
|
|
|
.map_err(|_| Error::CmdLine)?;
|
|
|
|
|
|
|
|
let vcpu_count = self.config.vcpu_count;
|
|
|
|
|
|
|
|
arch::configure_system(
|
|
|
|
&self.memory,
|
|
|
|
self.config.cmdline_addr,
|
|
|
|
cmdline_cstring.to_bytes().len() + 1,
|
|
|
|
vcpu_count,
|
|
|
|
)
|
|
|
|
.map_err(|_| Error::CmdLine)?;
|
|
|
|
|
|
|
|
Ok(entry_addr.kernel_load)
|
|
|
|
}
|
|
|
|
|
2019-04-15 17:14:31 +00:00
|
|
|
pub fn control_loop(&mut self) -> Result<()> {
|
2019-03-18 20:59:50 +00:00
|
|
|
// Let's start the STDIN polling thread.
|
2019-04-15 17:14:31 +00:00
|
|
|
const EPOLL_EVENTS_LEN: usize = 100;
|
|
|
|
|
2019-03-18 20:59:50 +00:00
|
|
|
let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN];
|
|
|
|
let epoll_fd = self.epoll.as_raw_fd();
|
|
|
|
|
2019-05-07 18:34:03 +00:00
|
|
|
let stdin = io::stdin();
|
|
|
|
let stdin_lock = stdin.lock();
|
|
|
|
stdin_lock.set_raw_mode().map_err(Error::SetTerminalRaw)?;
|
|
|
|
|
2019-03-18 20:59:50 +00:00
|
|
|
loop {
|
|
|
|
let num_events =
|
|
|
|
epoll::wait(epoll_fd, -1, &mut events[..]).map_err(Error::EpollError)?;
|
|
|
|
|
|
|
|
for event in events.iter().take(num_events) {
|
2019-04-15 17:14:31 +00:00
|
|
|
let dispatch_idx = event.data as usize;
|
|
|
|
|
|
|
|
if let Some(dispatch_type) = self.epoll.dispatch_table[dispatch_idx] {
|
|
|
|
match dispatch_type {
|
2019-04-15 17:59:38 +00:00
|
|
|
EpollDispatch::Exit => {
|
|
|
|
// Consume the event.
|
|
|
|
self.devices.exit_evt.read().map_err(Error::EventFd)?;
|
|
|
|
|
2019-05-07 18:34:03 +00:00
|
|
|
// Don't forget to set the terminal in canonical mode
|
|
|
|
// before to exit.
|
|
|
|
stdin_lock
|
|
|
|
.set_canon_mode()
|
|
|
|
.map_err(Error::SetTerminalCanon)?;
|
|
|
|
|
2019-04-15 17:59:38 +00:00
|
|
|
// Safe because we're terminating the process anyway.
|
|
|
|
unsafe {
|
|
|
|
libc::_exit(0);
|
|
|
|
}
|
|
|
|
}
|
2019-04-15 17:14:31 +00:00
|
|
|
EpollDispatch::Stdin => {
|
|
|
|
let mut out = [0u8; 64];
|
|
|
|
let count = stdin_lock.read_raw(&mut out).map_err(Error::Serial)?;
|
|
|
|
|
|
|
|
self.devices
|
|
|
|
.serial
|
|
|
|
.lock()
|
|
|
|
.expect("Failed to process stdin event due to poisoned lock")
|
|
|
|
.queue_input_bytes(&out[..count])
|
|
|
|
.map_err(Error::Serial)?;
|
|
|
|
}
|
|
|
|
}
|
2019-03-18 20:59:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-28 14:26:30 +00:00
|
|
|
pub fn start(&mut self, entry_addr: GuestAddress) -> Result<()> {
|
2019-03-07 13:56:43 +00:00
|
|
|
self.devices.register_devices()?;
|
|
|
|
|
2019-02-28 14:26:30 +00:00
|
|
|
let vcpu_count = self.config.vcpu_count;
|
|
|
|
|
2019-05-08 06:47:59 +00:00
|
|
|
// let vcpus: Vec<thread::JoinHandle<()>> = Vec::with_capacity(vcpu_count as usize);
|
2019-02-28 14:26:30 +00:00
|
|
|
let vcpu_thread_barrier = Arc::new(Barrier::new((vcpu_count + 1) as usize));
|
|
|
|
|
|
|
|
for cpu_id in 0..vcpu_count {
|
2019-03-07 13:56:43 +00:00
|
|
|
let io_bus = self.devices.io_bus.clone();
|
2019-05-06 17:27:40 +00:00
|
|
|
let mmio_bus = self.devices.mmio_bus.clone();
|
2019-05-19 02:24:47 +00:00
|
|
|
let mut vcpu = Vcpu::new(cpu_id, &self, io_bus, mmio_bus)?;
|
2019-02-28 14:26:30 +00:00
|
|
|
vcpu.configure(entry_addr, &self)?;
|
|
|
|
|
2019-03-06 11:04:14 +00:00
|
|
|
let vcpu_thread_barrier = vcpu_thread_barrier.clone();
|
|
|
|
|
2019-05-08 06:47:59 +00:00
|
|
|
self.vcpus.push(
|
2019-02-28 14:26:30 +00:00
|
|
|
thread::Builder::new()
|
|
|
|
.name(format!("cloud-hypervisor_vcpu{}", vcpu.id))
|
|
|
|
.spawn(move || {
|
|
|
|
unsafe {
|
|
|
|
extern "C" fn handle_signal(_: i32, _: *mut siginfo_t, _: *mut c_void) {
|
|
|
|
}
|
|
|
|
// This uses an async signal safe handler to kill the vcpu handles.
|
|
|
|
register_signal_handler(
|
|
|
|
VCPU_RTSIG_OFFSET,
|
|
|
|
vmm_sys_util::signal::SignalHandler::Siginfo(handle_signal),
|
|
|
|
true,
|
|
|
|
0,
|
|
|
|
)
|
|
|
|
.expect("Failed to register vcpu signal handler");
|
|
|
|
}
|
|
|
|
|
2019-03-06 11:04:14 +00:00
|
|
|
// Block until all CPUs are ready.
|
2019-02-28 14:26:30 +00:00
|
|
|
vcpu_thread_barrier.wait();
|
|
|
|
|
2019-05-19 02:24:47 +00:00
|
|
|
while vcpu.run().is_ok() {}
|
2019-02-28 14:26:30 +00:00
|
|
|
})
|
|
|
|
.map_err(Error::VcpuSpawn)?,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-03-06 11:04:14 +00:00
|
|
|
// Unblock all CPU threads.
|
2019-02-28 14:26:30 +00:00
|
|
|
vcpu_thread_barrier.wait();
|
2019-03-06 11:04:14 +00:00
|
|
|
|
2019-04-15 17:14:31 +00:00
|
|
|
self.control_loop()?;
|
2019-03-18 20:59:50 +00:00
|
|
|
|
2019-02-28 13:16:58 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2019-02-28 14:26:30 +00:00
|
|
|
|
|
|
|
/// Gets a reference to the guest memory owned by this VM.
|
|
|
|
///
|
|
|
|
/// Note that `GuestMemory` does not include any device memory that may have been added after
|
|
|
|
/// this VM was constructed.
|
|
|
|
pub fn get_memory(&self) -> &GuestMemoryMmap {
|
|
|
|
&self.memory
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets a reference to the kvm file descriptor owned by this VM.
|
|
|
|
///
|
|
|
|
pub fn get_fd(&self) -> &VmFd {
|
|
|
|
&self.fd
|
|
|
|
}
|
2019-04-25 17:10:42 +00:00
|
|
|
|
|
|
|
fn patch_cpuid(cpuid: &mut CpuId) {
|
|
|
|
let entries = cpuid.mut_entries_slice();
|
|
|
|
|
|
|
|
for entry in entries.iter_mut() {
|
|
|
|
if let 1 = entry.function {
|
|
|
|
if entry.index == 0 {
|
|
|
|
entry.ecx |= 1 << ECX_HYPERVISOR_SHIFT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-02-28 13:16:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(unused)]
|
|
|
|
pub fn test_vm() {
|
|
|
|
// This example based on https://lwn.net/Articles/658511/
|
|
|
|
let code = [
|
|
|
|
0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */
|
|
|
|
0x00, 0xd8, /* add %bl, %al */
|
|
|
|
0x04, b'0', /* add $'0', %al */
|
|
|
|
0xee, /* out %al, (%dx) */
|
|
|
|
0xb0, b'\n', /* mov $'\n', %al */
|
|
|
|
0xee, /* out %al, (%dx) */
|
|
|
|
0xf4, /* hlt */
|
|
|
|
];
|
|
|
|
|
|
|
|
let mem_size = 0x1000;
|
|
|
|
let load_addr = GuestAddress(0x1000);
|
|
|
|
let mem = GuestMemoryMmap::new(&[(load_addr, mem_size)]).unwrap();
|
|
|
|
|
|
|
|
let kvm = Kvm::new().expect("new KVM instance creation failed");
|
|
|
|
let vm_fd = kvm.create_vm().expect("new VM fd creation failed");
|
|
|
|
|
|
|
|
mem.with_regions(|index, region| {
|
|
|
|
let mem_region = kvm_userspace_memory_region {
|
|
|
|
slot: index as u32,
|
|
|
|
guest_phys_addr: region.start_addr().raw_value(),
|
|
|
|
memory_size: region.len() as u64,
|
|
|
|
userspace_addr: region.as_ptr() as u64,
|
|
|
|
flags: 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Safe because the guest regions are guaranteed not to overlap.
|
|
|
|
vm_fd.set_user_memory_region(mem_region)
|
|
|
|
})
|
|
|
|
.expect("Cannot configure guest memory");
|
|
|
|
mem.write_slice(&code, load_addr)
|
|
|
|
.expect("Writing code to memory failed");
|
|
|
|
|
|
|
|
let vcpu_fd = vm_fd.create_vcpu(0).expect("new VcpuFd failed");
|
|
|
|
|
|
|
|
let mut vcpu_sregs = vcpu_fd.get_sregs().expect("get sregs failed");
|
|
|
|
vcpu_sregs.cs.base = 0;
|
|
|
|
vcpu_sregs.cs.selector = 0;
|
|
|
|
vcpu_fd.set_sregs(&vcpu_sregs).expect("set sregs failed");
|
|
|
|
|
|
|
|
let mut vcpu_regs = vcpu_fd.get_regs().expect("get regs failed");
|
|
|
|
vcpu_regs.rip = 0x1000;
|
|
|
|
vcpu_regs.rax = 2;
|
|
|
|
vcpu_regs.rbx = 3;
|
|
|
|
vcpu_regs.rflags = 2;
|
|
|
|
vcpu_fd.set_regs(&vcpu_regs).expect("set regs failed");
|
|
|
|
|
|
|
|
loop {
|
|
|
|
match vcpu_fd.run().expect("run failed") {
|
|
|
|
VcpuExit::IoIn(addr, data) => {
|
|
|
|
println!(
|
|
|
|
"IO in -- addr: {:#x} data [{:?}]",
|
|
|
|
addr,
|
|
|
|
str::from_utf8(&data).unwrap()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
VcpuExit::IoOut(addr, data) => {
|
|
|
|
println!(
|
|
|
|
"IO out -- addr: {:#x} data [{:?}]",
|
|
|
|
addr,
|
|
|
|
str::from_utf8(&data).unwrap()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
VcpuExit::MmioRead(_addr, _data) => {}
|
|
|
|
VcpuExit::MmioWrite(_addr, _data) => {}
|
|
|
|
VcpuExit::Unknown => {}
|
|
|
|
VcpuExit::Exception => {}
|
|
|
|
VcpuExit::Hypercall => {}
|
|
|
|
VcpuExit::Debug => {}
|
|
|
|
VcpuExit::Hlt => {
|
|
|
|
println!("HLT");
|
|
|
|
}
|
|
|
|
VcpuExit::IrqWindowOpen => {}
|
|
|
|
VcpuExit::Shutdown => {}
|
|
|
|
VcpuExit::FailEntry => {}
|
|
|
|
VcpuExit::Intr => {}
|
|
|
|
VcpuExit::SetTpr => {}
|
|
|
|
VcpuExit::TprAccess => {}
|
|
|
|
VcpuExit::S390Sieic => {}
|
|
|
|
VcpuExit::S390Reset => {}
|
|
|
|
VcpuExit::Dcr => {}
|
|
|
|
VcpuExit::Nmi => {}
|
|
|
|
VcpuExit::InternalError => {}
|
|
|
|
VcpuExit::Osi => {}
|
|
|
|
VcpuExit::PaprHcall => {}
|
|
|
|
VcpuExit::S390Ucontrol => {}
|
|
|
|
VcpuExit::Watchdog => {}
|
|
|
|
VcpuExit::S390Tsch => {}
|
|
|
|
VcpuExit::Epr => {}
|
|
|
|
VcpuExit::SystemEvent => {}
|
|
|
|
VcpuExit::S390Stsi => {}
|
|
|
|
VcpuExit::IoapicEoi => {}
|
|
|
|
VcpuExit::Hyperv => {}
|
|
|
|
}
|
|
|
|
// r => panic!("unexpected exit reason: {:?}", r),
|
|
|
|
}
|
|
|
|
}
|