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;
|
vm-virtio: Implement console size config feature
One of the features of the virtio console device is its size can be
configured and updated. Our first iteration of the console device
implementation is lack of this feature. As a result, it had a
default fixed size which could not be changed. This commit implements
the console config feature and lets us change the console size from
the vmm side.
During the activation of the device, vmm reads the current terminal
size, sets the console configuration accordinly, and lets the driver
know about this configuration by sending an interrupt. Later, if
someone changes the terminal size, the vmm detects the corresponding
event, updates the configuration, and sends interrupt as before. As a
result, the console device driver, in the guest, updates the console
size.
Signed-off-by: A K M Fazla Mehrab <fazla.mehrab.akm@intel.com>
2019-07-23 19:18:20 +00:00
|
|
|
extern crate signal_hook;
|
2019-09-11 16:07:33 +00:00
|
|
|
#[cfg(feature = "pci_support")]
|
2019-07-15 09:42:40 +00:00
|
|
|
extern crate vfio;
|
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 13:16:58 +00:00
|
|
|
|
2019-07-22 19:29:02 +00:00
|
|
|
use crate::config::{ConsoleOutputMode, VmConfig};
|
2019-09-06 15:42:41 +00:00
|
|
|
use crate::device_manager::{get_win_size, Console, DeviceManager, DeviceManagerError};
|
2019-07-17 16:54:11 +00:00
|
|
|
use arch::RegionType;
|
2019-06-18 17:31:50 +00:00
|
|
|
use devices::ioapic;
|
|
|
|
use kvm_bindings::{
|
2019-09-04 13:55:14 +00:00
|
|
|
kvm_enable_cap, kvm_pit_config, kvm_userspace_memory_region, KVM_CAP_SPLIT_IRQCHIP,
|
2019-06-18 17:31:50 +00:00
|
|
|
KVM_PIT_SPEAKER_DUMMY,
|
|
|
|
};
|
2019-02-28 13:16:58 +00:00
|
|
|
use kvm_ioctls::*;
|
2019-09-04 13:55:14 +00:00
|
|
|
use libc::{c_void, siginfo_t};
|
2019-09-27 08:39:56 +00:00
|
|
|
use linux_loader::cmdline::Cmdline;
|
2019-02-28 13:16:58 +00:00
|
|
|
use linux_loader::loader::KernelLoader;
|
vm-virtio: Implement console size config feature
One of the features of the virtio console device is its size can be
configured and updated. Our first iteration of the console device
implementation is lack of this feature. As a result, it had a
default fixed size which could not be changed. This commit implements
the console config feature and lets us change the console size from
the vmm side.
During the activation of the device, vmm reads the current terminal
size, sets the console configuration accordinly, and lets the driver
know about this configuration by sending an interrupt. Later, if
someone changes the terminal size, the vmm detects the corresponding
event, updates the configuration, and sends interrupt as before. As a
result, the console device driver, in the guest, updates the console
size.
Signed-off-by: A K M Fazla Mehrab <fazla.mehrab.akm@intel.com>
2019-07-23 19:18:20 +00:00
|
|
|
use signal_hook::{iterator::Signals, SIGWINCH};
|
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-09-04 13:55:14 +00:00
|
|
|
use std::io;
|
2019-08-20 22:43:23 +00:00
|
|
|
use std::ops::Deref;
|
2019-09-25 13:01:49 +00:00
|
|
|
use std::os::unix::io::FromRawFd;
|
2019-09-04 11:59:14 +00:00
|
|
|
use std::os::unix::thread::JoinHandleExt;
|
2019-09-03 09:00:15 +00:00
|
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
2019-08-20 22:43:23 +00:00
|
|
|
use std::sync::{Arc, Barrier, Mutex, RwLock};
|
2019-08-14 12:10:29 +00:00
|
|
|
use std::{fmt, result, str, thread};
|
2019-07-11 09:48:14 +00:00
|
|
|
use vm_allocator::{GsiApic, SystemAllocator};
|
2019-05-22 20:06:49 +00:00
|
|
|
use vm_memory::guest_memory::FileOffset;
|
2019-02-28 13:16:58 +00:00
|
|
|
use vm_memory::{
|
2019-06-26 09:06:49 +00:00
|
|
|
Address, Bytes, Error as MmapError, GuestAddress, GuestMemory, GuestMemoryMmap,
|
|
|
|
GuestMemoryRegion, GuestUsize,
|
2019-02-28 13:16:58 +00:00
|
|
|
};
|
2019-09-04 14:28:48 +00:00
|
|
|
use vmm_sys_util::eventfd::EventFd;
|
2019-09-03 09:00:15 +00:00
|
|
|
use vmm_sys_util::signal::{register_signal_handler, validate_signal_num};
|
2019-03-18 20:59:50 +00:00
|
|
|
use vmm_sys_util::terminal::Terminal;
|
2019-02-28 13:16:58 +00:00
|
|
|
|
2019-02-28 14:26:30 +00:00
|
|
|
const VCPU_RTSIG_OFFSET: i32 = 0;
|
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
|
2019-06-07 21:30:20 +00:00
|
|
|
const TSC_DEADLINE_TIMER_ECX_BIT: u8 = 24; // tsc deadline timer ecx bit.
|
|
|
|
const HYPERVISOR_ECX_BIT: u8 = 31; // Hypervisor ecx bit.
|
2019-04-25 17:10:42 +00:00
|
|
|
|
2019-06-10 09:14:02 +00:00
|
|
|
// 64 bit direct boot entry offset for bzImage
|
|
|
|
const KERNEL_64BIT_ENTRY_OFFSET: u64 = 0x200;
|
|
|
|
|
2019-08-14 12:10:29 +00:00
|
|
|
// Debug I/O port
|
|
|
|
#[cfg(target_arch = "x86_64")]
|
|
|
|
const DEBUG_IOPORT: u16 = 0x80;
|
|
|
|
const DEBUG_IOPORT_PREFIX: &str = "Debug I/O port";
|
|
|
|
|
|
|
|
/// Debug I/O port, see:
|
|
|
|
/// https://www.intel.com/content/www/us/en/support/articles/000005500/boards-and-kits.html
|
|
|
|
///
|
|
|
|
/// Since we're not a physical platform, we can freely assign code ranges for
|
|
|
|
/// debugging specific parts of our virtual platform.
|
|
|
|
pub enum DebugIoPortRange {
|
|
|
|
Firmware,
|
|
|
|
Bootloader,
|
|
|
|
Kernel,
|
|
|
|
Userspace,
|
|
|
|
Custom,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DebugIoPortRange {
|
|
|
|
fn from_u8(value: u8) -> DebugIoPortRange {
|
|
|
|
match value {
|
2019-08-15 15:41:40 +00:00
|
|
|
0x00..=0x1f => DebugIoPortRange::Firmware,
|
|
|
|
0x20..=0x3f => DebugIoPortRange::Bootloader,
|
|
|
|
0x40..=0x5f => DebugIoPortRange::Kernel,
|
|
|
|
0x60..=0x7f => DebugIoPortRange::Userspace,
|
2019-08-14 12:10:29 +00:00
|
|
|
_ => DebugIoPortRange::Custom,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for DebugIoPortRange {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
match self {
|
|
|
|
DebugIoPortRange::Firmware => write!(f, "{}: Firmware", DEBUG_IOPORT_PREFIX),
|
|
|
|
DebugIoPortRange::Bootloader => write!(f, "{}: Bootloader", DEBUG_IOPORT_PREFIX),
|
|
|
|
DebugIoPortRange::Kernel => write!(f, "{}: Kernel", DEBUG_IOPORT_PREFIX),
|
|
|
|
DebugIoPortRange::Userspace => write!(f, "{}: Userspace", DEBUG_IOPORT_PREFIX),
|
|
|
|
DebugIoPortRange::Custom => write!(f, "{}: Custom", DEBUG_IOPORT_PREFIX),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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),
|
|
|
|
|
2019-10-01 08:14:08 +00:00
|
|
|
PoisonedState,
|
|
|
|
|
2019-02-28 14:26:30 +00:00
|
|
|
#[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-09-06 15:42:41 +00:00
|
|
|
/// Write to the console failed.
|
2019-08-02 14:23:52 +00:00
|
|
|
Console(vmm_sys_util::errno::Error),
|
2019-07-22 19:29:02 +00:00
|
|
|
|
2019-05-07 18:34:03 +00:00
|
|
|
/// Cannot setup terminal in raw mode.
|
2019-08-02 14:23:52 +00:00
|
|
|
SetTerminalRaw(vmm_sys_util::errno::Error),
|
2019-05-07 18:34:03 +00:00
|
|
|
|
|
|
|
/// Cannot setup terminal in canonical mode.
|
2019-08-02 14:23:52 +00:00
|
|
|
SetTerminalCanon(vmm_sys_util::errno::Error),
|
2019-05-07 18:34:03 +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-06-10 09:14:02 +00:00
|
|
|
|
|
|
|
/// Memory is overflow
|
|
|
|
MemOverflow,
|
2019-05-22 20:06:49 +00:00
|
|
|
|
|
|
|
/// Failed to create shared file.
|
|
|
|
SharedFileCreate(io::Error),
|
|
|
|
|
|
|
|
/// Failed to set shared file length.
|
|
|
|
SharedFileSetLen(io::Error),
|
2019-07-17 16:54:11 +00:00
|
|
|
|
|
|
|
/// Failed to allocate a memory range.
|
|
|
|
MemoryRangeAllocation,
|
|
|
|
|
|
|
|
/// Failed to allocate the IOAPIC memory range.
|
|
|
|
IoapicRangeAllocation,
|
2019-08-30 17:24:01 +00:00
|
|
|
|
|
|
|
/// Cannot spawn a signal handler thread
|
|
|
|
SignalHandlerSpawn(io::Error),
|
2019-09-03 09:00:15 +00:00
|
|
|
|
|
|
|
/// Failed to join on vCPU threads
|
|
|
|
ThreadCleanup,
|
2019-09-23 23:12:07 +00:00
|
|
|
|
|
|
|
/// Failed to create a new KVM instance
|
|
|
|
KvmNew(io::Error),
|
2019-10-01 14:41:50 +00:00
|
|
|
|
|
|
|
/// VM is not created
|
|
|
|
VmNotCreated,
|
|
|
|
|
2019-10-10 16:00:44 +00:00
|
|
|
/// VM is not running
|
|
|
|
VmNotRunning,
|
2019-10-01 15:03:38 +00:00
|
|
|
|
2019-10-01 14:41:50 +00:00
|
|
|
/// Cannot clone EventFd.
|
|
|
|
EventFdClone(io::Error),
|
2019-10-11 12:47:57 +00:00
|
|
|
|
|
|
|
/// Invalid VM state transition
|
|
|
|
InvalidStateTransition(VmState, VmState),
|
2019-05-14 01:12:40 +00:00
|
|
|
}
|
|
|
|
pub type Result<T> = result::Result<T, Error>;
|
|
|
|
|
2019-06-11 13:53:38 +00:00
|
|
|
#[allow(dead_code)]
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
enum CpuidReg {
|
|
|
|
EAX,
|
|
|
|
EBX,
|
|
|
|
ECX,
|
|
|
|
EDX,
|
|
|
|
}
|
|
|
|
|
2019-06-12 13:42:31 +00:00
|
|
|
struct CpuidPatch {
|
|
|
|
function: u32,
|
|
|
|
index: u32,
|
|
|
|
flags_bit: Option<u8>,
|
|
|
|
eax_bit: Option<u8>,
|
|
|
|
ebx_bit: Option<u8>,
|
|
|
|
ecx_bit: Option<u8>,
|
|
|
|
edx_bit: Option<u8>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CpuidPatch {
|
|
|
|
fn set_cpuid_reg(
|
|
|
|
cpuid: &mut CpuId,
|
|
|
|
function: u32,
|
|
|
|
index: Option<u32>,
|
|
|
|
reg: CpuidReg,
|
|
|
|
value: u32,
|
|
|
|
) {
|
2019-10-30 16:38:38 +00:00
|
|
|
let entries = cpuid.as_mut_slice();
|
2019-06-12 13:42:31 +00:00
|
|
|
|
|
|
|
for entry in entries.iter_mut() {
|
|
|
|
if entry.function == function && (index == None || index.unwrap() == entry.index) {
|
|
|
|
match reg {
|
|
|
|
CpuidReg::EAX => {
|
|
|
|
entry.eax = value;
|
|
|
|
}
|
|
|
|
CpuidReg::EBX => {
|
|
|
|
entry.ebx = value;
|
|
|
|
}
|
|
|
|
CpuidReg::ECX => {
|
|
|
|
entry.ecx = value;
|
|
|
|
}
|
|
|
|
CpuidReg::EDX => {
|
|
|
|
entry.edx = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn patch_cpuid(cpuid: &mut CpuId, patches: Vec<CpuidPatch>) {
|
2019-10-30 16:38:38 +00:00
|
|
|
let entries = cpuid.as_mut_slice();
|
2019-06-12 13:42:31 +00:00
|
|
|
|
|
|
|
for entry in entries.iter_mut() {
|
|
|
|
for patch in patches.iter() {
|
|
|
|
if entry.function == patch.function && entry.index == patch.index {
|
|
|
|
if let Some(flags_bit) = patch.flags_bit {
|
|
|
|
entry.flags |= 1 << flags_bit;
|
|
|
|
}
|
|
|
|
if let Some(eax_bit) = patch.eax_bit {
|
|
|
|
entry.eax |= 1 << eax_bit;
|
|
|
|
}
|
|
|
|
if let Some(ebx_bit) = patch.ebx_bit {
|
|
|
|
entry.ebx |= 1 << ebx_bit;
|
|
|
|
}
|
|
|
|
if let Some(ecx_bit) = patch.ecx_bit {
|
|
|
|
entry.ecx |= 1 << ecx_bit;
|
|
|
|
}
|
|
|
|
if let Some(edx_bit) = patch.edx_bit {
|
|
|
|
entry.edx |= 1 << edx_bit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-10-23 21:06:13 +00:00
|
|
|
io_bus: Arc<devices::Bus>,
|
|
|
|
mmio_bus: Arc<devices::Bus>,
|
2019-06-18 17:31:50 +00:00
|
|
|
ioapic: Option<Arc<Mutex<ioapic::Ioapic>>>,
|
2019-08-14 13:25:32 +00:00
|
|
|
vm_ts: std::time::Instant,
|
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-06-18 17:31:50 +00:00
|
|
|
pub fn new(
|
|
|
|
id: u8,
|
|
|
|
vm: &Vm,
|
2019-10-23 21:06:13 +00:00
|
|
|
io_bus: Arc<devices::Bus>,
|
|
|
|
mmio_bus: Arc<devices::Bus>,
|
2019-06-18 17:31:50 +00:00
|
|
|
ioapic: Option<Arc<Mutex<ioapic::Ioapic>>>,
|
|
|
|
) -> 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-06-18 17:31:50 +00:00
|
|
|
ioapic,
|
2019-08-14 13:25:32 +00:00
|
|
|
vm_ts: vm.creation_ts,
|
2019-05-19 02:24:47 +00:00
|
|
|
})
|
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-06-11 13:53:38 +00:00
|
|
|
let mut cpuid = vm.cpuid.clone();
|
2019-06-12 13:42:31 +00:00
|
|
|
CpuidPatch::set_cpuid_reg(&mut cpuid, 0xb, None, CpuidReg::EDX, u32::from(self.id));
|
2019-03-01 15:22:26 +00:00
|
|
|
self.fd
|
2019-06-11 13:53:38 +00:00
|
|
|
.set_cpuid2(&cpuid)
|
2019-03-01 15:22:26 +00:00
|
|
|
.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)?;
|
2019-08-20 22:43:23 +00:00
|
|
|
arch::x86_64::regs::setup_sregs(&vm_memory.read().unwrap(), &self.fd)
|
|
|
|
.map_err(Error::SREGSConfiguration)?;
|
2019-02-28 14:26:30 +00:00
|
|
|
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-08-29 15:39:00 +00:00
|
|
|
pub fn run(&self) -> Result<bool> {
|
2019-05-19 02:24:47 +00:00
|
|
|
match self.fd.run() {
|
|
|
|
Ok(run) => match run {
|
|
|
|
VcpuExit::IoIn(addr, data) => {
|
|
|
|
self.io_bus.read(u64::from(addr), data);
|
2019-08-29 15:39:00 +00:00
|
|
|
Ok(true)
|
2019-05-19 02:24:47 +00:00
|
|
|
}
|
|
|
|
VcpuExit::IoOut(addr, data) => {
|
2019-08-14 12:10:29 +00:00
|
|
|
if addr == DEBUG_IOPORT && data.len() == 1 {
|
|
|
|
self.log_debug_ioport(data[0]);
|
|
|
|
}
|
2019-05-19 02:24:47 +00:00
|
|
|
self.io_bus.write(u64::from(addr), data);
|
2019-08-29 15:39:00 +00:00
|
|
|
Ok(true)
|
2019-05-19 02:24:47 +00:00
|
|
|
}
|
|
|
|
VcpuExit::MmioRead(addr, data) => {
|
|
|
|
self.mmio_bus.read(addr as u64, data);
|
2019-08-29 15:39:00 +00:00
|
|
|
Ok(true)
|
2019-05-19 02:24:47 +00:00
|
|
|
}
|
|
|
|
VcpuExit::MmioWrite(addr, data) => {
|
|
|
|
self.mmio_bus.write(addr as u64, data);
|
2019-08-29 15:39:00 +00:00
|
|
|
Ok(true)
|
2019-05-19 02:24:47 +00:00
|
|
|
}
|
2019-06-18 17:31:50 +00:00
|
|
|
VcpuExit::IoapicEoi(vector) => {
|
|
|
|
if let Some(ioapic) = &self.ioapic {
|
|
|
|
ioapic.lock().unwrap().end_of_interrupt(vector);
|
|
|
|
}
|
2019-08-29 15:39:00 +00:00
|
|
|
Ok(true)
|
|
|
|
}
|
|
|
|
VcpuExit::Shutdown => {
|
|
|
|
// Triple fault to trigger a reboot
|
|
|
|
Ok(false)
|
2019-06-18 17:31:50 +00:00
|
|
|
}
|
2019-05-19 02:24:47 +00:00
|
|
|
r => {
|
|
|
|
error!("Unexpected exit reason on vcpu run: {:?}", r);
|
|
|
|
Err(Error::VcpuUnhandledKvmExit)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
Err(ref e) => match e.raw_os_error().unwrap() {
|
2019-08-29 15:39:00 +00:00
|
|
|
libc::EAGAIN | libc::EINTR => Ok(true),
|
2019-05-19 02:24:47 +00:00
|
|
|
_ => {
|
|
|
|
error!("VCPU {:?} error {:?}", self.id, e);
|
|
|
|
Err(Error::VcpuUnhandledKvmExit)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
2019-02-28 14:26:30 +00:00
|
|
|
}
|
2019-08-14 12:10:29 +00:00
|
|
|
|
|
|
|
// Log debug io port codes.
|
|
|
|
fn log_debug_ioport(&self, code: u8) {
|
2019-08-14 13:25:32 +00:00
|
|
|
let ts = self.vm_ts.elapsed();
|
|
|
|
|
|
|
|
debug!(
|
|
|
|
"[{} code 0x{:x}] {}.{:>06} seconds",
|
|
|
|
DebugIoPortRange::from_u8(code),
|
|
|
|
code,
|
|
|
|
ts.as_secs(),
|
|
|
|
ts.as_micros()
|
|
|
|
);
|
2019-08-14 12:10:29 +00:00
|
|
|
}
|
2019-02-28 14:26:30 +00:00
|
|
|
}
|
|
|
|
|
2019-09-04 13:55:14 +00:00
|
|
|
pub struct VmInfo<'a> {
|
|
|
|
pub memory: &'a Arc<RwLock<GuestMemoryMmap>>,
|
|
|
|
pub vm_fd: &'a Arc<VmFd>,
|
2019-09-23 17:46:35 +00:00
|
|
|
pub vm_cfg: &'a VmConfig,
|
2019-09-05 09:10:44 +00:00
|
|
|
}
|
|
|
|
|
2019-10-11 12:47:57 +00:00
|
|
|
#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq)]
|
2019-10-01 08:14:08 +00:00
|
|
|
pub enum VmState {
|
|
|
|
Created,
|
2019-10-10 16:00:44 +00:00
|
|
|
Running,
|
2019-10-01 08:14:08 +00:00
|
|
|
Shutdown,
|
2019-10-10 15:16:58 +00:00
|
|
|
Paused,
|
2019-10-01 08:14:08 +00:00
|
|
|
}
|
|
|
|
|
2019-10-11 12:47:57 +00:00
|
|
|
impl VmState {
|
|
|
|
fn valid_transition(self, new_state: VmState) -> Result<()> {
|
|
|
|
match self {
|
|
|
|
VmState::Created => match new_state {
|
|
|
|
VmState::Created | VmState::Shutdown | VmState::Paused => {
|
|
|
|
Err(Error::InvalidStateTransition(self, new_state))
|
|
|
|
}
|
|
|
|
VmState::Running => Ok(()),
|
|
|
|
},
|
|
|
|
|
|
|
|
VmState::Running => match new_state {
|
|
|
|
VmState::Created | VmState::Running => {
|
|
|
|
Err(Error::InvalidStateTransition(self, new_state))
|
|
|
|
}
|
|
|
|
VmState::Paused | VmState::Shutdown => Ok(()),
|
|
|
|
},
|
|
|
|
|
|
|
|
VmState::Shutdown => match new_state {
|
|
|
|
VmState::Paused | VmState::Created | VmState::Shutdown => {
|
|
|
|
Err(Error::InvalidStateTransition(self, new_state))
|
|
|
|
}
|
|
|
|
VmState::Running => Ok(()),
|
|
|
|
},
|
|
|
|
|
|
|
|
VmState::Paused => match new_state {
|
|
|
|
VmState::Created | VmState::Paused => {
|
|
|
|
Err(Error::InvalidStateTransition(self, new_state))
|
|
|
|
}
|
|
|
|
VmState::Running | VmState::Shutdown => Ok(()),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-24 14:00:00 +00:00
|
|
|
pub struct Vm {
|
2019-05-29 23:33:29 +00:00
|
|
|
fd: Arc<VmFd>,
|
2019-02-28 13:16:58 +00:00
|
|
|
kernel: File,
|
2019-08-20 22:43:23 +00:00
|
|
|
memory: Arc<RwLock<GuestMemoryMmap>>,
|
2019-09-04 11:59:14 +00:00
|
|
|
threads: Vec<thread::JoinHandle<()>>,
|
2019-03-07 13:56:43 +00:00
|
|
|
devices: DeviceManager,
|
2019-03-01 15:22:26 +00:00
|
|
|
cpuid: CpuId,
|
2019-09-24 14:00:00 +00:00
|
|
|
config: Arc<VmConfig>,
|
2019-05-30 15:17:57 +00:00
|
|
|
on_tty: bool,
|
2019-08-14 13:25:32 +00:00
|
|
|
creation_ts: std::time::Instant,
|
2019-09-03 09:00:15 +00:00
|
|
|
vcpus_kill_signalled: Arc<AtomicBool>,
|
2019-10-10 15:16:58 +00:00
|
|
|
vcpus_pause_signalled: Arc<AtomicBool>,
|
2019-09-25 13:01:49 +00:00
|
|
|
// Reboot (reset) control
|
2019-09-04 14:28:48 +00:00
|
|
|
reset_evt: EventFd,
|
2019-09-09 12:43:03 +00:00
|
|
|
signals: Option<Signals>,
|
2019-10-01 08:14:08 +00:00
|
|
|
state: RwLock<VmState>,
|
2019-02-28 13:16:58 +00:00
|
|
|
}
|
|
|
|
|
2019-09-18 14:36:14 +00:00
|
|
|
fn get_host_cpu_phys_bits() -> u8 {
|
|
|
|
use core::arch::x86_64;
|
|
|
|
unsafe {
|
|
|
|
let leaf = x86_64::__cpuid(0x8000_0000);
|
|
|
|
|
|
|
|
if leaf.eax >= 0x8000_0008 {
|
|
|
|
let leaf = x86_64::__cpuid(0x8000_0008);
|
|
|
|
(leaf.eax & 0xff) as u8
|
|
|
|
} else {
|
|
|
|
36
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-24 14:00:00 +00:00
|
|
|
impl Vm {
|
2019-09-25 12:09:33 +00:00
|
|
|
pub fn new(config: Arc<VmConfig>, exit_evt: EventFd, reset_evt: EventFd) -> Result<Self> {
|
2019-09-23 23:12:07 +00:00
|
|
|
let kvm = Kvm::new().map_err(Error::KvmNew)?;
|
2019-09-19 07:52:55 +00:00
|
|
|
let kernel =
|
|
|
|
File::open(&config.kernel.as_ref().unwrap().path).map_err(Error::KernelFile)?;
|
2019-02-28 13:16:58 +00:00
|
|
|
let fd = kvm.create_vm().map_err(Error::VmCreate)?;
|
2019-05-29 23:33:29 +00:00
|
|
|
let fd = Arc::new(fd);
|
2019-08-14 13:25:32 +00:00
|
|
|
let creation_ts = std::time::Instant::now();
|
2019-02-28 13:16:58 +00:00
|
|
|
|
|
|
|
// Init guest memory
|
2019-07-09 03:13:02 +00:00
|
|
|
let arch_mem_regions = arch::arch_memory_regions(config.memory.size);
|
2019-05-24 19:21:23 +00:00
|
|
|
|
2019-07-17 16:54:11 +00:00
|
|
|
let ram_regions: Vec<(GuestAddress, usize)> = arch_mem_regions
|
|
|
|
.iter()
|
|
|
|
.filter(|r| r.2 == RegionType::Ram)
|
|
|
|
.map(|r| (r.0, r.1))
|
|
|
|
.collect();
|
2019-07-25 08:20:59 +00:00
|
|
|
let sub_regions: Vec<(GuestAddress, usize)> = arch_mem_regions
|
|
|
|
.iter()
|
|
|
|
.filter(|r| r.2 == RegionType::SubRegion)
|
|
|
|
.map(|r| (r.0, r.1))
|
|
|
|
.collect();
|
2019-07-17 16:54:11 +00:00
|
|
|
|
|
|
|
// Check the number of reserved regions, and only take the first one
|
|
|
|
// that's acrtually a 32-bit hole.
|
|
|
|
let mut mem_hole = (GuestAddress(0), 0);
|
2019-07-25 08:20:59 +00:00
|
|
|
for region in sub_regions.iter() {
|
2019-07-17 16:54:11 +00:00
|
|
|
if region.0.unchecked_add(region.1 as u64).raw_value() <= 0x1_0000_0000 {
|
|
|
|
mem_hole = (region.0, region.1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-24 19:21:23 +00:00
|
|
|
let guest_memory = match config.memory.file {
|
2019-09-23 16:51:36 +00:00
|
|
|
Some(ref file) => {
|
2019-05-24 19:21:23 +00:00
|
|
|
let mut mem_regions = Vec::<(GuestAddress, usize, Option<FileOffset>)>::new();
|
2019-07-17 16:54:11 +00:00
|
|
|
for region in ram_regions.iter() {
|
2019-08-28 10:01:38 +00:00
|
|
|
if file.is_file() {
|
|
|
|
let file = OpenOptions::new()
|
|
|
|
.read(true)
|
|
|
|
.write(true)
|
|
|
|
.open(file)
|
|
|
|
.map_err(Error::SharedFileCreate)?;
|
|
|
|
|
|
|
|
file.set_len(region.1 as u64)
|
|
|
|
.map_err(Error::SharedFileSetLen)?;
|
|
|
|
|
|
|
|
mem_regions.push((region.0, region.1, Some(FileOffset::new(file, 0))));
|
|
|
|
} else if file.is_dir() {
|
|
|
|
let fs_str = format!("{}{}", file.display(), "/tmpfile_XXXXXX");
|
|
|
|
let fs = std::ffi::CString::new(fs_str).unwrap();
|
|
|
|
let mut path = fs.as_bytes_with_nul().to_owned();
|
|
|
|
let path_ptr = path.as_mut_ptr() as *mut _;
|
|
|
|
let fd = unsafe { libc::mkstemp(path_ptr) };
|
|
|
|
unsafe { libc::unlink(path_ptr) };
|
|
|
|
|
|
|
|
let f = unsafe { File::from_raw_fd(fd) };
|
|
|
|
f.set_len(region.1 as u64)
|
|
|
|
.map_err(Error::SharedFileSetLen)?;
|
|
|
|
|
|
|
|
mem_regions.push((region.0, region.1, Some(FileOffset::new(f, 0))));
|
|
|
|
}
|
2019-05-24 19:21:23 +00:00
|
|
|
}
|
2019-05-22 20:06:49 +00:00
|
|
|
|
2019-05-24 19:21:23 +00:00
|
|
|
GuestMemoryMmap::with_files(&mem_regions).map_err(Error::GuestMemory)?
|
|
|
|
}
|
2019-07-17 16:54:11 +00:00
|
|
|
None => GuestMemoryMmap::new(&ram_regions).map_err(Error::GuestMemory)?,
|
2019-05-24 19:21:23 +00:00
|
|
|
};
|
2019-02-28 13:16:58 +00:00
|
|
|
|
|
|
|
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.
|
2019-06-03 21:09:01 +00:00
|
|
|
unsafe { fd.set_user_memory_region(mem_region) }
|
2019-02-28 13:16:58 +00:00
|
|
|
})
|
|
|
|
.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-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-06-07 21:30:20 +00:00
|
|
|
|
2019-06-18 17:31:50 +00:00
|
|
|
let msi_capable = kvm.check_extension(Cap::SignalMsi);
|
|
|
|
|
2019-06-07 21:30:20 +00:00
|
|
|
let mut cpuid_patches = Vec::new();
|
2019-06-18 17:31:50 +00:00
|
|
|
let mut userspace_ioapic = false;
|
2019-06-07 21:30:20 +00:00
|
|
|
if kvm.check_extension(Cap::TscDeadlineTimer) {
|
2019-06-18 17:31:50 +00:00
|
|
|
if kvm.check_extension(Cap::SplitIrqchip) && msi_capable {
|
|
|
|
// Create split irqchip
|
|
|
|
// Only the local APIC is emulated in kernel, both PICs and IOAPIC
|
|
|
|
// are not.
|
|
|
|
let mut cap: kvm_enable_cap = Default::default();
|
|
|
|
cap.cap = KVM_CAP_SPLIT_IRQCHIP;
|
|
|
|
cap.args[0] = ioapic::NUM_IOAPIC_PINS as u64;
|
|
|
|
fd.enable_cap(&cap).map_err(Error::VmSetup)?;
|
|
|
|
|
|
|
|
// Because of the split irqchip, we need a userspace IOAPIC.
|
|
|
|
userspace_ioapic = true;
|
|
|
|
} else {
|
|
|
|
// Create irqchip
|
|
|
|
// A local APIC, 2 PICs and an IOAPIC are emulated in kernel.
|
|
|
|
fd.create_irq_chip().map_err(Error::VmSetup)?;
|
|
|
|
}
|
|
|
|
|
2019-06-07 21:30:20 +00:00
|
|
|
// Patch tsc deadline timer bit
|
|
|
|
cpuid_patches.push(CpuidPatch {
|
|
|
|
function: 1,
|
|
|
|
index: 0,
|
|
|
|
flags_bit: None,
|
|
|
|
eax_bit: None,
|
|
|
|
ebx_bit: None,
|
|
|
|
ecx_bit: Some(TSC_DEADLINE_TIMER_ECX_BIT),
|
|
|
|
edx_bit: None,
|
|
|
|
});
|
|
|
|
} else {
|
2019-06-18 17:31:50 +00:00
|
|
|
// Create irqchip
|
|
|
|
// A local APIC, 2 PICs and an IOAPIC are emulated in kernel.
|
|
|
|
fd.create_irq_chip().map_err(Error::VmSetup)?;
|
2019-06-07 21:30:20 +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)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Patch hypervisor bit
|
|
|
|
cpuid_patches.push(CpuidPatch {
|
|
|
|
function: 1,
|
|
|
|
index: 0,
|
|
|
|
flags_bit: None,
|
|
|
|
eax_bit: None,
|
|
|
|
ebx_bit: None,
|
|
|
|
ecx_bit: Some(HYPERVISOR_ECX_BIT),
|
|
|
|
edx_bit: None,
|
|
|
|
});
|
|
|
|
|
2019-06-12 13:42:31 +00:00
|
|
|
CpuidPatch::patch_cpuid(&mut cpuid, cpuid_patches);
|
2019-03-01 15:22:26 +00:00
|
|
|
|
2019-07-11 09:48:14 +00:00
|
|
|
let ioapic = GsiApic::new(
|
|
|
|
X86_64_IRQ_BASE,
|
|
|
|
ioapic::NUM_IOAPIC_PINS as u32 - X86_64_IRQ_BASE,
|
|
|
|
);
|
|
|
|
|
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(
|
2019-06-26 16:02:00 +00:00
|
|
|
GuestAddress(0),
|
|
|
|
1 << 16 as GuestUsize,
|
2019-05-06 17:27:40 +00:00
|
|
|
GuestAddress(0),
|
2019-09-18 14:36:14 +00:00
|
|
|
1 << get_host_cpu_phys_bits(),
|
2019-07-17 16:54:11 +00:00
|
|
|
mem_hole.0,
|
|
|
|
mem_hole.1 as GuestUsize,
|
2019-07-11 09:48:14 +00:00
|
|
|
vec![ioapic],
|
2019-05-06 17:27:40 +00:00
|
|
|
)
|
|
|
|
.ok_or(Error::CreateSystemAllocator)?;
|
|
|
|
|
2019-07-17 16:54:11 +00:00
|
|
|
// Allocate RAM and Reserved address ranges.
|
|
|
|
for region in arch_mem_regions.iter() {
|
|
|
|
allocator
|
|
|
|
.allocate_mmio_addresses(Some(region.0), region.1 as GuestUsize, None)
|
|
|
|
.ok_or(Error::MemoryRangeAllocation)?;
|
|
|
|
}
|
|
|
|
|
2019-08-20 21:12:00 +00:00
|
|
|
// Convert the guest memory into an Arc. The point being able to use it
|
|
|
|
// anywhere in the code, no matter which thread might use it.
|
2019-08-20 22:43:23 +00:00
|
|
|
// Add the RwLock aspect to guest memory as we might want to perform
|
|
|
|
// additions to the memory during runtime.
|
|
|
|
let guest_memory = Arc::new(RwLock::new(guest_memory));
|
2019-08-20 21:12:00 +00:00
|
|
|
|
2019-08-01 16:27:23 +00:00
|
|
|
let vm_info = VmInfo {
|
2019-08-20 21:12:00 +00:00
|
|
|
memory: &guest_memory,
|
2019-08-01 16:27:23 +00:00
|
|
|
vm_fd: &fd,
|
2019-09-24 14:00:00 +00:00
|
|
|
vm_cfg: &config,
|
2019-08-01 16:27:23 +00:00
|
|
|
};
|
|
|
|
|
2019-05-29 23:33:29 +00:00
|
|
|
let device_manager = DeviceManager::new(
|
2019-08-01 16:27:23 +00:00
|
|
|
&vm_info,
|
2019-10-23 22:14:13 +00:00
|
|
|
allocator,
|
2019-06-18 17:31:50 +00:00
|
|
|
msi_capable,
|
|
|
|
userspace_ioapic,
|
2019-07-25 04:31:44 +00:00
|
|
|
ram_regions.len() as u32,
|
2019-09-04 14:28:48 +00:00
|
|
|
&exit_evt,
|
|
|
|
&reset_evt,
|
2019-05-29 23:33:29 +00:00
|
|
|
)
|
|
|
|
.map_err(Error::DeviceManager)?;
|
2019-03-07 13:56:43 +00:00
|
|
|
|
2019-05-30 15:17:57 +00:00
|
|
|
let on_tty = unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0;
|
2019-10-16 05:38:42 +00:00
|
|
|
let threads = Vec::with_capacity(config.cpus.cpu_count as usize + 1);
|
2019-05-08 06:47:59 +00:00
|
|
|
|
2019-02-28 13:16:58 +00:00
|
|
|
Ok(Vm {
|
|
|
|
fd,
|
|
|
|
kernel,
|
|
|
|
memory: guest_memory,
|
2019-09-04 11:59:14 +00:00
|
|
|
threads,
|
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-05-30 15:17:57 +00:00
|
|
|
on_tty,
|
2019-08-14 13:25:32 +00:00
|
|
|
creation_ts,
|
2019-09-03 09:00:15 +00:00
|
|
|
vcpus_kill_signalled: Arc::new(AtomicBool::new(false)),
|
2019-10-10 15:16:58 +00:00
|
|
|
vcpus_pause_signalled: Arc::new(AtomicBool::new(false)),
|
2019-09-04 14:28:48 +00:00
|
|
|
reset_evt,
|
2019-09-09 12:43:03 +00:00
|
|
|
signals: None,
|
2019-10-01 08:14:08 +00:00
|
|
|
state: RwLock::new(VmState::Created),
|
2019-02-28 13:16:58 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-09-23 23:03:05 +00:00
|
|
|
fn load_kernel(&mut self) -> Result<GuestAddress> {
|
2019-09-27 08:39:56 +00:00
|
|
|
let mut cmdline = Cmdline::new(arch::CMDLINE_MAX_SIZE);
|
|
|
|
cmdline
|
|
|
|
.insert_str(self.config.cmdline.args.clone())
|
|
|
|
.map_err(|_| Error::CmdLine)?;
|
2019-09-11 15:22:00 +00:00
|
|
|
for entry in self.devices.cmdline_additions() {
|
|
|
|
cmdline.insert_str(entry).map_err(|_| Error::CmdLine)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
let cmdline_cstring = CString::new(cmdline).map_err(|_| Error::CmdLine)?;
|
2019-08-20 22:43:23 +00:00
|
|
|
let mem = self.memory.read().unwrap();
|
2019-06-10 09:14:02 +00:00
|
|
|
let entry_addr = match linux_loader::loader::Elf::load(
|
2019-08-20 22:43:23 +00:00
|
|
|
mem.deref(),
|
2019-02-28 13:16:58 +00:00
|
|
|
None,
|
|
|
|
&mut self.kernel,
|
2019-09-27 13:11:50 +00:00
|
|
|
Some(arch::layout::HIGH_RAM_START),
|
2019-06-10 09:14:02 +00:00
|
|
|
) {
|
|
|
|
Ok(entry_addr) => entry_addr,
|
|
|
|
Err(linux_loader::loader::Error::InvalidElfMagicNumber) => {
|
|
|
|
linux_loader::loader::BzImage::load(
|
2019-08-20 22:43:23 +00:00
|
|
|
mem.deref(),
|
2019-06-10 09:14:02 +00:00
|
|
|
None,
|
|
|
|
&mut self.kernel,
|
2019-09-27 13:11:50 +00:00
|
|
|
Some(arch::layout::HIGH_RAM_START),
|
2019-06-10 09:14:02 +00:00
|
|
|
)
|
|
|
|
.map_err(Error::KernelLoad)?
|
|
|
|
}
|
|
|
|
_ => panic!("Invalid elf file"),
|
|
|
|
};
|
2019-02-28 13:16:58 +00:00
|
|
|
|
|
|
|
linux_loader::loader::load_cmdline(
|
2019-08-20 22:43:23 +00:00
|
|
|
mem.deref(),
|
2019-09-27 16:06:53 +00:00
|
|
|
arch::layout::CMDLINE_START,
|
2019-02-28 13:16:58 +00:00
|
|
|
&cmdline_cstring,
|
|
|
|
)
|
|
|
|
.map_err(|_| Error::CmdLine)?;
|
2019-10-16 05:38:42 +00:00
|
|
|
let vcpu_count = self.config.cpus.cpu_count;
|
2019-09-18 14:36:14 +00:00
|
|
|
let end_of_range = GuestAddress((1 << get_host_cpu_phys_bits()) - 1);
|
2019-06-10 09:14:02 +00:00
|
|
|
match entry_addr.setup_header {
|
|
|
|
Some(hdr) => {
|
|
|
|
arch::configure_system(
|
2019-08-20 22:43:23 +00:00
|
|
|
&mem,
|
2019-09-27 16:06:53 +00:00
|
|
|
arch::layout::CMDLINE_START,
|
2019-06-10 09:14:02 +00:00
|
|
|
cmdline_cstring.to_bytes().len() + 1,
|
|
|
|
vcpu_count,
|
|
|
|
Some(hdr),
|
2019-08-22 10:18:48 +00:00
|
|
|
self.config.serial.mode != ConsoleOutputMode::Off,
|
2019-09-18 14:11:56 +00:00
|
|
|
end_of_range,
|
2019-10-02 20:57:20 +00:00
|
|
|
self.devices.virt_iommu(),
|
2019-06-10 09:14:02 +00:00
|
|
|
)
|
|
|
|
.map_err(|_| Error::CmdLine)?;
|
|
|
|
|
|
|
|
let load_addr = entry_addr
|
|
|
|
.kernel_load
|
|
|
|
.raw_value()
|
|
|
|
.checked_add(KERNEL_64BIT_ENTRY_OFFSET)
|
|
|
|
.ok_or(Error::MemOverflow)?;
|
|
|
|
|
|
|
|
Ok(GuestAddress(load_addr))
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
arch::configure_system(
|
2019-08-20 22:43:23 +00:00
|
|
|
&mem,
|
2019-09-27 16:06:53 +00:00
|
|
|
arch::layout::CMDLINE_START,
|
2019-06-10 09:14:02 +00:00
|
|
|
cmdline_cstring.to_bytes().len() + 1,
|
|
|
|
vcpu_count,
|
|
|
|
None,
|
2019-08-22 10:18:48 +00:00
|
|
|
self.config.serial.mode != ConsoleOutputMode::Off,
|
2019-09-18 14:11:56 +00:00
|
|
|
end_of_range,
|
2019-10-02 20:57:20 +00:00
|
|
|
self.devices.virt_iommu(),
|
2019-06-10 09:14:02 +00:00
|
|
|
)
|
|
|
|
.map_err(|_| Error::CmdLine)?;
|
|
|
|
|
|
|
|
Ok(entry_addr.kernel_load)
|
|
|
|
}
|
|
|
|
}
|
2019-02-28 13:16:58 +00:00
|
|
|
}
|
|
|
|
|
2019-09-30 09:53:49 +00:00
|
|
|
pub fn shutdown(&mut self) -> Result<()> {
|
2019-10-11 12:47:57 +00:00
|
|
|
let mut state = self.state.try_write().map_err(|_| Error::PoisonedState)?;
|
|
|
|
let new_state = VmState::Shutdown;
|
|
|
|
|
|
|
|
state.valid_transition(new_state)?;
|
|
|
|
|
2019-05-30 15:17:57 +00:00
|
|
|
if self.on_tty {
|
|
|
|
// Don't forget to set the terminal in canonical mode
|
|
|
|
// before to exit.
|
|
|
|
io::stdin()
|
|
|
|
.lock()
|
|
|
|
.set_canon_mode()
|
|
|
|
.map_err(Error::SetTerminalCanon)?;
|
|
|
|
}
|
|
|
|
|
2019-09-09 12:43:03 +00:00
|
|
|
// Trigger the termination of the signal_handler thread
|
|
|
|
if let Some(signals) = self.signals.take() {
|
|
|
|
signals.close();
|
|
|
|
}
|
|
|
|
|
2019-09-03 09:00:15 +00:00
|
|
|
// Tell the vCPUs to stop themselves next time they go through the loop
|
|
|
|
self.vcpus_kill_signalled.store(true, Ordering::SeqCst);
|
|
|
|
|
2019-09-04 11:59:14 +00:00
|
|
|
// Signal to the spawned threads (vCPUs and console signal handler). For the vCPU threads
|
|
|
|
// this will interrupt the KVM_RUN ioctl() allowing the loop to check the boolean set
|
2019-09-09 12:43:03 +00:00
|
|
|
// above. The signal handler thread will ignore this signal
|
2019-09-04 11:59:14 +00:00
|
|
|
for thread in self.threads.iter() {
|
2019-09-03 09:00:15 +00:00
|
|
|
let signum = validate_signal_num(VCPU_RTSIG_OFFSET, true).unwrap();
|
|
|
|
unsafe {
|
2019-09-04 11:59:14 +00:00
|
|
|
libc::pthread_kill(thread.as_pthread_t(), signum);
|
2019-09-03 09:00:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-04 11:59:14 +00:00
|
|
|
// Wait for all the threads to finish
|
|
|
|
for thread in self.threads.drain(..) {
|
|
|
|
thread.join().map_err(|_| Error::ThreadCleanup)?
|
2019-09-03 09:00:15 +00:00
|
|
|
}
|
|
|
|
|
2019-10-11 12:47:57 +00:00
|
|
|
*state = new_state;
|
2019-10-01 08:14:08 +00:00
|
|
|
|
2019-09-24 14:14:04 +00:00
|
|
|
Ok(())
|
2019-03-18 20:59:50 +00:00
|
|
|
}
|
|
|
|
|
2019-10-10 15:16:58 +00:00
|
|
|
pub fn pause(&mut self) -> Result<()> {
|
2019-10-11 12:47:57 +00:00
|
|
|
let mut state = self.state.try_write().map_err(|_| Error::PoisonedState)?;
|
|
|
|
let new_state = VmState::Paused;
|
|
|
|
|
|
|
|
state.valid_transition(new_state)?;
|
|
|
|
|
2019-10-10 15:16:58 +00:00
|
|
|
// Tell the vCPUs to pause themselves next time they exit
|
|
|
|
self.vcpus_pause_signalled.store(true, Ordering::SeqCst);
|
|
|
|
|
|
|
|
// Signal to the spawned threads (vCPUs and console signal handler). For the vCPU threads
|
|
|
|
// this will interrupt the KVM_RUN ioctl() allowing the loop to check the boolean set
|
|
|
|
// above. The signal handler thread will ignore this signal
|
|
|
|
for thread in self.threads.iter() {
|
|
|
|
let signum = validate_signal_num(VCPU_RTSIG_OFFSET, true).unwrap();
|
|
|
|
unsafe {
|
|
|
|
libc::pthread_kill(thread.as_pthread_t(), signum);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-11 12:47:57 +00:00
|
|
|
*state = new_state;
|
2019-10-10 15:16:58 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-10-10 15:34:55 +00:00
|
|
|
pub fn resume(&mut self) -> Result<()> {
|
2019-10-11 12:47:57 +00:00
|
|
|
let mut state = self.state.try_write().map_err(|_| Error::PoisonedState)?;
|
|
|
|
let new_state = VmState::Running;
|
|
|
|
|
|
|
|
state.valid_transition(new_state)?;
|
|
|
|
|
2019-10-10 15:34:55 +00:00
|
|
|
// Toggle the vCPUs pause boolean
|
|
|
|
self.vcpus_pause_signalled.store(false, Ordering::SeqCst);
|
|
|
|
|
|
|
|
// Unpark all the VCPU threads.
|
|
|
|
// Once unparked, the next thing they will do is checking for the pause
|
|
|
|
// boolean. Since it'll be set to false, they will exit their pause loop
|
|
|
|
// and go back to vmx root.
|
|
|
|
for vcpu_thread in self.threads.iter() {
|
|
|
|
vcpu_thread.thread().unpark();
|
|
|
|
}
|
|
|
|
|
2019-10-10 16:00:44 +00:00
|
|
|
// And we're back to the Running state.
|
2019-10-11 12:47:57 +00:00
|
|
|
*state = new_state;
|
2019-10-10 15:34:55 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2019-09-09 12:43:03 +00:00
|
|
|
fn os_signal_handler(signals: Signals, console_input_clone: Arc<Console>) {
|
vm-virtio: Implement console size config feature
One of the features of the virtio console device is its size can be
configured and updated. Our first iteration of the console device
implementation is lack of this feature. As a result, it had a
default fixed size which could not be changed. This commit implements
the console config feature and lets us change the console size from
the vmm side.
During the activation of the device, vmm reads the current terminal
size, sets the console configuration accordinly, and lets the driver
know about this configuration by sending an interrupt. Later, if
someone changes the terminal size, the vmm detects the corresponding
event, updates the configuration, and sends interrupt as before. As a
result, the console device driver, in the guest, updates the console
size.
Signed-off-by: A K M Fazla Mehrab <fazla.mehrab.akm@intel.com>
2019-07-23 19:18:20 +00:00
|
|
|
for signal in signals.forever() {
|
2019-09-09 12:43:03 +00:00
|
|
|
if signal == SIGWINCH {
|
|
|
|
let (col, row) = get_win_size();
|
|
|
|
console_input_clone.update_console_size(col, row);
|
vm-virtio: Implement console size config feature
One of the features of the virtio console device is its size can be
configured and updated. Our first iteration of the console device
implementation is lack of this feature. As a result, it had a
default fixed size which could not be changed. This commit implements
the console config feature and lets us change the console size from
the vmm side.
During the activation of the device, vmm reads the current terminal
size, sets the console configuration accordinly, and lets the driver
know about this configuration by sending an interrupt. Later, if
someone changes the terminal size, the vmm detects the corresponding
event, updates the configuration, and sends interrupt as before. As a
result, the console device driver, in the guest, updates the console
size.
Signed-off-by: A K M Fazla Mehrab <fazla.mehrab.akm@intel.com>
2019-07-23 19:18:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-30 09:53:49 +00:00
|
|
|
pub fn boot(&mut self) -> Result<()> {
|
2019-10-11 12:47:57 +00:00
|
|
|
let current_state = self.get_state()?;
|
|
|
|
if current_state == VmState::Paused {
|
|
|
|
return self.resume();
|
|
|
|
}
|
|
|
|
|
|
|
|
let new_state = VmState::Running;
|
|
|
|
current_state.valid_transition(new_state)?;
|
|
|
|
|
2019-09-23 23:03:05 +00:00
|
|
|
let entry_addr = self.load_kernel()?;
|
2019-10-16 05:38:42 +00:00
|
|
|
let vcpu_count = self.config.cpus.cpu_count;
|
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-09-04 14:20:09 +00:00
|
|
|
let io_bus = self.devices.io_bus().clone();
|
|
|
|
let mmio_bus = self.devices.mmio_bus().clone();
|
|
|
|
let ioapic = if let Some(ioapic) = &self.devices.ioapic() {
|
2019-06-18 17:31:50 +00:00
|
|
|
Some(ioapic.clone())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut vcpu = Vcpu::new(cpu_id, &self, io_bus, mmio_bus, ioapic)?;
|
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-09-04 14:28:48 +00:00
|
|
|
let reset_evt = self.reset_evt.try_clone().unwrap();
|
2019-09-03 09:00:15 +00:00
|
|
|
let vcpu_kill_signalled = self.vcpus_kill_signalled.clone();
|
2019-10-10 15:16:58 +00:00
|
|
|
let vcpu_pause_signalled = self.vcpus_pause_signalled.clone();
|
2019-09-04 11:59:14 +00:00
|
|
|
self.threads.push(
|
2019-02-28 14:26:30 +00:00
|
|
|
thread::Builder::new()
|
2019-08-30 17:24:01 +00:00
|
|
|
.name(format!("vcpu{}", vcpu.id))
|
2019-02-28 14:26:30 +00:00
|
|
|
.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-08-29 15:39:00 +00:00
|
|
|
loop {
|
|
|
|
// vcpu.run() returns false on a KVM_EXIT_SHUTDOWN (triple-fault) so trigger a reset
|
|
|
|
match vcpu.run() {
|
|
|
|
Err(e) => {
|
|
|
|
error!("VCPU generated error: {:?}", e);
|
2019-08-30 17:24:01 +00:00
|
|
|
break;
|
2019-08-29 15:39:00 +00:00
|
|
|
}
|
|
|
|
Ok(true) => {}
|
|
|
|
Ok(false) => {
|
|
|
|
reset_evt.write(1).unwrap();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-09-03 09:00:15 +00:00
|
|
|
|
|
|
|
// We've been told to terminate
|
|
|
|
if vcpu_kill_signalled.load(Ordering::SeqCst) {
|
|
|
|
break;
|
|
|
|
}
|
2019-10-10 15:16:58 +00:00
|
|
|
|
|
|
|
// If we are being told to pause, we park the thread
|
|
|
|
// until the pause boolean is toggled.
|
|
|
|
// The resume operation is responsible for toggling
|
|
|
|
// the boolean and unpark the thread.
|
|
|
|
// We enter a loop because park() could spuriously
|
|
|
|
// return. We will then park() again unless the
|
|
|
|
// pause boolean has been toggled.
|
|
|
|
while vcpu_pause_signalled.load(Ordering::SeqCst) {
|
|
|
|
thread::park();
|
|
|
|
}
|
2019-08-29 15:39:00 +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-09-06 15:42:41 +00:00
|
|
|
if self.devices.console().input_enabled() {
|
|
|
|
let console = self.devices.console().clone();
|
2019-09-09 12:43:03 +00:00
|
|
|
let signals = Signals::new(&[SIGWINCH]);
|
vm-virtio: Implement console size config feature
One of the features of the virtio console device is its size can be
configured and updated. Our first iteration of the console device
implementation is lack of this feature. As a result, it had a
default fixed size which could not be changed. This commit implements
the console config feature and lets us change the console size from
the vmm side.
During the activation of the device, vmm reads the current terminal
size, sets the console configuration accordinly, and lets the driver
know about this configuration by sending an interrupt. Later, if
someone changes the terminal size, the vmm detects the corresponding
event, updates the configuration, and sends interrupt as before. As a
result, the console device driver, in the guest, updates the console
size.
Signed-off-by: A K M Fazla Mehrab <fazla.mehrab.akm@intel.com>
2019-07-23 19:18:20 +00:00
|
|
|
match signals {
|
2019-09-09 12:43:03 +00:00
|
|
|
Ok(signals) => {
|
|
|
|
self.signals = Some(signals.clone());
|
|
|
|
|
2019-09-04 11:59:14 +00:00
|
|
|
self.threads.push(
|
|
|
|
thread::Builder::new()
|
|
|
|
.name("signal_handler".to_string())
|
2019-09-09 12:43:03 +00:00
|
|
|
.spawn(move || Vm::os_signal_handler(signals, console))
|
2019-09-04 11:59:14 +00:00
|
|
|
.map_err(Error::SignalHandlerSpawn)?,
|
|
|
|
);
|
vm-virtio: Implement console size config feature
One of the features of the virtio console device is its size can be
configured and updated. Our first iteration of the console device
implementation is lack of this feature. As a result, it had a
default fixed size which could not be changed. This commit implements
the console config feature and lets us change the console size from
the vmm side.
During the activation of the device, vmm reads the current terminal
size, sets the console configuration accordinly, and lets the driver
know about this configuration by sending an interrupt. Later, if
someone changes the terminal size, the vmm detects the corresponding
event, updates the configuration, and sends interrupt as before. As a
result, the console device driver, in the guest, updates the console
size.
Signed-off-by: A K M Fazla Mehrab <fazla.mehrab.akm@intel.com>
2019-07-23 19:18:20 +00:00
|
|
|
}
|
|
|
|
Err(e) => error!("Signal not found {}", e),
|
|
|
|
}
|
2019-09-24 14:06:56 +00:00
|
|
|
|
|
|
|
if self.on_tty {
|
|
|
|
io::stdin()
|
|
|
|
.lock()
|
|
|
|
.set_raw_mode()
|
|
|
|
.map_err(Error::SetTerminalRaw)?;
|
|
|
|
}
|
vm-virtio: Implement console size config feature
One of the features of the virtio console device is its size can be
configured and updated. Our first iteration of the console device
implementation is lack of this feature. As a result, it had a
default fixed size which could not be changed. This commit implements
the console config feature and lets us change the console size from
the vmm side.
During the activation of the device, vmm reads the current terminal
size, sets the console configuration accordinly, and lets the driver
know about this configuration by sending an interrupt. Later, if
someone changes the terminal size, the vmm detects the corresponding
event, updates the configuration, and sends interrupt as before. As a
result, the console device driver, in the guest, updates the console
size.
Signed-off-by: A K M Fazla Mehrab <fazla.mehrab.akm@intel.com>
2019-07-23 19:18:20 +00:00
|
|
|
}
|
2019-09-24 14:06:56 +00:00
|
|
|
|
2019-10-01 08:14:08 +00:00
|
|
|
let mut state = self.state.try_write().map_err(|_| Error::PoisonedState)?;
|
2019-10-11 12:47:57 +00:00
|
|
|
*state = new_state;
|
2019-10-01 08:14:08 +00:00
|
|
|
|
2019-09-25 13:01:49 +00:00
|
|
|
Ok(())
|
2019-02-28 13:16:58 +00:00
|
|
|
}
|
2019-02-28 14:26:30 +00:00
|
|
|
|
2019-08-20 22:43:23 +00:00
|
|
|
/// Gets an Arc to the guest memory owned by this VM.
|
|
|
|
pub fn get_memory(&self) -> Arc<RwLock<GuestMemoryMmap>> {
|
|
|
|
self.memory.clone()
|
2019-02-28 14:26:30 +00:00
|
|
|
}
|
2019-09-24 14:22:35 +00:00
|
|
|
|
|
|
|
pub fn handle_stdin(&self) -> Result<()> {
|
|
|
|
let mut out = [0u8; 64];
|
|
|
|
let count = io::stdin()
|
|
|
|
.lock()
|
|
|
|
.read_raw(&mut out)
|
|
|
|
.map_err(Error::Console)?;
|
|
|
|
|
|
|
|
if self.devices.console().input_enabled() {
|
|
|
|
self.devices
|
|
|
|
.console()
|
|
|
|
.queue_input_bytes(&out[..count])
|
|
|
|
.map_err(Error::Console)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2019-09-25 09:26:11 +00:00
|
|
|
|
|
|
|
/// Gets a thread-safe reference counted pointer to the VM configuration.
|
|
|
|
pub fn get_config(&self) -> Arc<VmConfig> {
|
|
|
|
Arc::clone(&self.config)
|
|
|
|
}
|
2019-10-01 08:14:08 +00:00
|
|
|
|
|
|
|
/// Get the VM state. Returns an error if the state is poisoned.
|
|
|
|
pub fn get_state(&self) -> Result<VmState> {
|
|
|
|
self.state
|
|
|
|
.try_read()
|
|
|
|
.map_err(|_| Error::PoisonedState)
|
2019-10-11 12:47:57 +00:00
|
|
|
.map(|state| *state)
|
2019-10-01 08:14:08 +00:00
|
|
|
}
|
2019-02-28 13:16:58 +00:00
|
|
|
}
|
|
|
|
|
2019-10-11 16:59:29 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
fn test_vm_state_transitions(state: VmState) {
|
|
|
|
match state {
|
|
|
|
VmState::Created => {
|
|
|
|
// Check the transitions from Created
|
|
|
|
assert!(state.valid_transition(VmState::Created).is_err());
|
|
|
|
assert!(state.valid_transition(VmState::Running).is_ok());
|
|
|
|
assert!(state.valid_transition(VmState::Shutdown).is_err());
|
|
|
|
assert!(state.valid_transition(VmState::Paused).is_err());
|
|
|
|
}
|
|
|
|
VmState::Running => {
|
|
|
|
// Check the transitions from Running
|
|
|
|
assert!(state.valid_transition(VmState::Created).is_err());
|
|
|
|
assert!(state.valid_transition(VmState::Running).is_err());
|
|
|
|
assert!(state.valid_transition(VmState::Shutdown).is_ok());
|
|
|
|
assert!(state.valid_transition(VmState::Paused).is_ok());
|
|
|
|
}
|
|
|
|
VmState::Shutdown => {
|
|
|
|
// Check the transitions from Shutdown
|
|
|
|
assert!(state.valid_transition(VmState::Created).is_err());
|
|
|
|
assert!(state.valid_transition(VmState::Running).is_ok());
|
|
|
|
assert!(state.valid_transition(VmState::Shutdown).is_err());
|
|
|
|
assert!(state.valid_transition(VmState::Paused).is_err());
|
|
|
|
}
|
|
|
|
VmState::Paused => {
|
|
|
|
// Check the transitions from Paused
|
|
|
|
assert!(state.valid_transition(VmState::Created).is_err());
|
|
|
|
assert!(state.valid_transition(VmState::Running).is_ok());
|
|
|
|
assert!(state.valid_transition(VmState::Shutdown).is_ok());
|
|
|
|
assert!(state.valid_transition(VmState::Paused).is_err());
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_vm_created_transitions() {
|
|
|
|
test_vm_state_transitions(VmState::Created);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_vm_running_transitions() {
|
|
|
|
test_vm_state_transitions(VmState::Running);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_vm_shutdown_transitions() {
|
|
|
|
test_vm_state_transitions(VmState::Shutdown);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_vm_paused_transitions() {
|
|
|
|
test_vm_state_transitions(VmState::Paused);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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.
|
2019-06-03 21:09:01 +00:00
|
|
|
unsafe { vm_fd.set_user_memory_region(mem_region) }
|
2019-02-28 13:16:58 +00:00
|
|
|
})
|
|
|
|
.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 => {}
|
2019-06-18 17:31:50 +00:00
|
|
|
VcpuExit::IoapicEoi(_vector) => {}
|
2019-02-28 13:16:58 +00:00
|
|
|
VcpuExit::Hyperv => {}
|
|
|
|
}
|
|
|
|
// r => panic!("unexpected exit reason: {:?}", r),
|
|
|
|
}
|
|
|
|
}
|