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-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};
|
2019-03-18 20:59:50 +00:00
|
|
|
use std::os::unix::io::{AsRawFd, RawFd};
|
2019-02-28 13:16:58 +00:00
|
|
|
use std::path::Path;
|
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-02-28 13:16:58 +00:00
|
|
|
/// Errors associated with the wrappers over KVM ioctls.
|
|
|
|
#[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
|
|
|
|
|
|
|
/// Cannot create EventFd.
|
|
|
|
EventFd(io::Error),
|
|
|
|
|
|
|
|
/// Cannot create a device manager.
|
|
|
|
DeviceManager,
|
|
|
|
|
|
|
|
/// 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-05-06 17:27:40 +00:00
|
|
|
/// Cannot allocate IRQ.
|
|
|
|
AllocateIrq,
|
|
|
|
|
|
|
|
/// Cannot allocate PCI BARs
|
|
|
|
AllocateBars(pci::PciDeviceError),
|
|
|
|
|
|
|
|
/// Cannot register ioevent.
|
|
|
|
RegisterIoevent(io::Error),
|
|
|
|
|
2019-03-18 21:03:00 +00:00
|
|
|
/// Cannot configure the IRQ.
|
|
|
|
Irq(io::Error),
|
2019-05-06 17:27:40 +00:00
|
|
|
|
|
|
|
/// Cannot create virtio device
|
|
|
|
VirtioDevice,
|
|
|
|
|
|
|
|
/// Cannot add PCI device
|
|
|
|
AddPciDevice(pci::PciRootError),
|
|
|
|
|
|
|
|
/// 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-06 17:27:40 +00:00
|
|
|
/// Cannot create the system allocator
|
|
|
|
CreateSystemAllocator,
|
2019-05-09 15:01:42 +00:00
|
|
|
|
|
|
|
/// Failed parsing network parameters
|
|
|
|
ParseNetworkParameters,
|
2019-02-28 13:16:58 +00:00
|
|
|
}
|
|
|
|
pub type Result<T> = result::Result<T, Error>;
|
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
|
|
|
pub fn new(id: u8, vm: &Vm) -> Result<Self> {
|
|
|
|
let kvm_vcpu = vm.fd.create_vcpu(id).map_err(Error::VcpuFd)?;
|
|
|
|
// Initially the cpuid per vCPU is the one supported by this VM.
|
|
|
|
Ok(Vcpu { fd: kvm_vcpu, id })
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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.
|
|
|
|
pub fn run(&self) -> Result<VcpuExit> {
|
|
|
|
self.fd.run().map_err(Error::VcpuRun)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-06 19:15:44 +00:00
|
|
|
disk_path: &'a Path,
|
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,
|
|
|
|
disk_path: &'a Path,
|
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-06 19:15:44 +00:00
|
|
|
disk_path,
|
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
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-09 15:01:42 +00:00
|
|
|
fn parse_net_params(net_params: &str) -> Result<(&str, &str)> {
|
|
|
|
// Split the parameters based on the comma delimiter
|
|
|
|
let params_list: Vec<&str> = net_params.split(',').collect();
|
|
|
|
|
|
|
|
let mut if_name: Option<&str> = None;
|
|
|
|
let mut mac: Option<&str> = None;
|
|
|
|
|
|
|
|
for param in params_list.iter() {
|
|
|
|
if param.starts_with("tap=") {
|
|
|
|
if_name = Some(¶m[4..]);
|
|
|
|
} else if param.starts_with("mac=") {
|
|
|
|
mac = Some(¶m[4..]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(if_name) = if_name {
|
|
|
|
if let Some(mac) = mac {
|
|
|
|
return Ok((if_name, mac));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(Error::ParseNetworkParameters)
|
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
) -> Result<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-03-07 13:56:43 +00:00
|
|
|
let serial_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFd)?;
|
|
|
|
let serial = Arc::new(Mutex::new(devices::legacy::Serial::new_out(
|
|
|
|
serial_evt.try_clone().map_err(Error::EventFd)?,
|
|
|
|
Box::new(stdout()),
|
|
|
|
)));
|
|
|
|
|
2019-04-15 17:59:38 +00:00
|
|
|
let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFd)?;
|
2019-04-15 18:57:30 +00:00
|
|
|
let i8042 = Arc::new(Mutex::new(devices::legacy::I8042Device::new(
|
|
|
|
exit_evt.try_clone().map_err(Error::EventFd)?,
|
|
|
|
)));
|
2019-04-15 17:59:38 +00:00
|
|
|
|
2019-05-06 17:27:40 +00:00
|
|
|
let mut pci_root = PciRoot::new(None);
|
|
|
|
|
|
|
|
// Open block device path
|
|
|
|
let raw_img: File = OpenOptions::new()
|
|
|
|
.read(true)
|
|
|
|
.write(true)
|
2019-05-06 19:15:44 +00:00
|
|
|
.open(&vm_cfg.disk_path)
|
2019-05-06 17:27:40 +00:00
|
|
|
.map_err(Error::Disk)?;
|
|
|
|
|
2019-05-09 15:01:42 +00:00
|
|
|
// Add virtio-blk
|
2019-05-06 17:27:40 +00:00
|
|
|
let virtio_block_device =
|
|
|
|
vm_virtio::Block::new(raw_img, false).map_err(Error::CreateVirtioBlock)?;
|
2019-05-08 10:41:24 +00:00
|
|
|
|
|
|
|
DeviceManager::add_virtio_pci_device(
|
|
|
|
Box::new(virtio_block_device),
|
|
|
|
memory.clone(),
|
|
|
|
allocator,
|
|
|
|
vm_fd,
|
|
|
|
&mut pci_root,
|
|
|
|
&mut mmio_bus,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// Add virtio-net if required
|
|
|
|
if let Some(net_params) = &vm_cfg.net_params {
|
|
|
|
if let Ok((tap_if_name, mac_addr)) = parse_net_params(net_params) {
|
|
|
|
let mac = MacAddr::parse_str(mac_addr).unwrap();
|
|
|
|
let tap = Tap::open_named(tap_if_name).unwrap();
|
|
|
|
let virtio_net_device = vm_virtio::Net::new_with_tap(tap, Some(&mac))
|
|
|
|
.map_err(Error::CreateVirtioNet)?;
|
|
|
|
|
|
|
|
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 =
|
|
|
|
vm_virtio::Rng::new(rng_path).map_err(Error::CreateVirtioRng)?;
|
|
|
|
|
|
|
|
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,
|
|
|
|
) -> Result<()> {
|
|
|
|
let mut virtio_pci_device =
|
|
|
|
VirtioPciDevice::new(memory, virtio_device).map_err(|_| Error::VirtioDevice)?;
|
2019-05-06 17:27:40 +00:00
|
|
|
let bars = virtio_pci_device
|
|
|
|
.allocate_bars(allocator)
|
|
|
|
.map_err(Error::AllocateBars)?;
|
|
|
|
|
|
|
|
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)
|
|
|
|
.map_err(Error::RegisterIoevent)?;
|
|
|
|
}
|
|
|
|
|
2019-05-08 10:41:24 +00:00
|
|
|
// Assign IRQ to the virtio-pci device
|
2019-05-06 17:27:40 +00:00
|
|
|
let irqfd = EventFd::new(EFD_NONBLOCK).map_err(Error::EventFd)?;
|
|
|
|
let irq_num = allocator.allocate_irq().ok_or(Error::AllocateIrq)?;
|
|
|
|
vm_fd
|
|
|
|
.register_irqfd(irqfd.as_raw_fd(), irq_num)
|
|
|
|
.map_err(Error::Irq)?;
|
|
|
|
// 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-06 17:27:40 +00:00
|
|
|
.map_err(Error::AddPciDevice)?;
|
|
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
|
|
|
println!(
|
|
|
|
"Size {:?} guest addr 0x{:x} host addr 0x{:x}",
|
|
|
|
mem_region.memory_size, mem_region.guest_phys_addr, mem_region.userspace_addr
|
|
|
|
);
|
|
|
|
|
|
|
|
// 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-06 17:27: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 {
|
|
|
|
println!("Starting VCPU {:?}", cpu_id);
|
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-02-28 14:26:30 +00:00
|
|
|
let mut vcpu = Vcpu::new(cpu_id, &self)?;
|
|
|
|
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();
|
|
|
|
|
|
|
|
loop {
|
|
|
|
match vcpu.run() {
|
|
|
|
Ok(run) => match run {
|
2019-03-07 13:56:43 +00:00
|
|
|
VcpuExit::IoIn(addr, data) => {
|
|
|
|
io_bus.read(u64::from(addr), data);
|
|
|
|
}
|
2019-02-28 14:26:30 +00:00
|
|
|
VcpuExit::IoOut(addr, data) => {
|
2019-03-07 13:56:43 +00:00
|
|
|
io_bus.write(u64::from(addr), data);
|
2019-03-06 11:04:14 +00:00
|
|
|
}
|
2019-05-06 17:27:40 +00:00
|
|
|
VcpuExit::MmioRead(addr, data) => {
|
|
|
|
mmio_bus.read(addr as u64, data);
|
2019-03-06 11:04:14 +00:00
|
|
|
}
|
2019-05-06 17:27:40 +00:00
|
|
|
VcpuExit::MmioWrite(addr, data) => {
|
|
|
|
mmio_bus.write(addr as u64, data);
|
2019-03-06 11:04:14 +00:00
|
|
|
}
|
|
|
|
VcpuExit::Unknown => {
|
|
|
|
println!("Unknown");
|
|
|
|
}
|
|
|
|
VcpuExit::Exception => {
|
|
|
|
println!("Exception");
|
2019-02-28 14:26:30 +00:00
|
|
|
}
|
|
|
|
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 => {}
|
|
|
|
},
|
2019-03-12 13:55:35 +00:00
|
|
|
Err(Error::VcpuRun(ref e)) => {
|
|
|
|
match e.raw_os_error().unwrap() {
|
|
|
|
// Why do we check for these if we only return EINVAL?
|
|
|
|
libc::EAGAIN | libc::EINTR => {}
|
|
|
|
_ => {
|
|
|
|
println! {"VCPU {:?} error {:?}", cpu_id, e};
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-02-28 14:26:30 +00:00
|
|
|
}
|
2019-03-12 13:55:35 +00:00
|
|
|
_ => (),
|
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),
|
|
|
|
}
|
|
|
|
}
|