refactor configure and launch of VM to be much simpler and nicer

This commit is contained in:
Bruce Leidl 2019-09-12 16:31:38 -04:00
parent c8153e4f51
commit 1a587bb6cc
7 changed files with 332 additions and 138 deletions

View File

@ -1,10 +1,8 @@
#![allow(non_snake_case)]
extern crate libc;
extern crate byteorder;
extern crate termios;
#[macro_use] extern crate lazy_static;
#[macro_use] mod log;
mod vm;
mod memory;
#[macro_use]
@ -12,48 +10,14 @@ mod system;
mod devices;
mod kvm;
mod virtio;
mod disk;
pub use log::{Logger,LogLevel};
use std::env;
use std::path::PathBuf;
fn main() {
let mut config = vm::VmConfig::new();
config.ram_size_megs(1024);
match find_kernel() {
Some(path) => config.kernel_path(&path),
None => { println!("Could not find kernel"); return; }
}
match find_init() {
Some(path) => config.init_path(&path),
None => { println!("Could not find init"); return; }
}
match vm::Vm::open(config) {
Ok(vm) => {
vm.start().unwrap();
},
Err(e) => println!("error :( {}", e)
}
}
fn find_init() -> Option<PathBuf> {
let mut cwd = env::current_dir().unwrap();
if cwd.join("rust/target/release/ph-init").exists() {
cwd.push("rust/target/release/ph-init");
return Some(cwd)
}
if cwd.join("rust/target/debug/ph-init").exists() {
cwd.push("rust/target/debug/ph-init");
return Some(cwd)
}
None
}
fn find_kernel() -> Option<PathBuf> {
let mut cwd = env::current_dir().unwrap();
if cwd.join("kernel/ph_linux").exists() {
cwd.push("kernel/ph_linux");
return Some(cwd)
}
None
vm::VmConfig::new(env::args())
.ram_size_megs(1024)
.use_realmfs("/home/user/Shared/main-realmfs.img")
.boot();
}

View File

@ -26,6 +26,10 @@ impl MemoryManager {
&self.ram
}
pub fn kvm_mut(&mut self) -> &mut Kvm {
&mut self.kvm
}
pub fn kvm(&self) -> &Kvm {
&self.kvm
}

185
rust/src/vm/config.rs Normal file
View File

@ -0,0 +1,185 @@
use std::path::{PathBuf, Path};
use crate::vm::{Vm, Result, ErrorKind};
use std::{env, process};
pub enum RootFS {
SelfRoot,
RealmFSImage(PathBuf),
RawImage(PathBuf),
RawOffset(PathBuf, usize),
}
pub struct VmConfig {
ram_size: usize,
ncpus: usize,
verbose: bool,
launch_systemd: bool,
kernel_path: Option<PathBuf>,
init_path: Option<PathBuf>,
init_cmd: Option<String>,
rootfs: RootFS,
}
#[allow(dead_code)]
impl VmConfig {
pub fn new(args: env::Args) -> VmConfig {
let mut config = VmConfig {
ram_size: 256 * 1024 * 1024,
ncpus: 1,
verbose: false,
launch_systemd: false,
kernel_path: None,
init_path: None,
init_cmd: None,
rootfs: RootFS::SelfRoot,
};
config.parse_args(args);
config
}
pub fn ram_size_megs(mut self, megs: usize) -> Self {
self.ram_size = megs * 1024 * 1024;
self
}
pub fn num_cpus(mut self, ncpus: usize) -> Self {
self.ncpus = ncpus;
self
}
pub fn init_cmdline(mut self, val: &str) -> Self {
self.init_cmd = Some(val.to_owned());
self
}
pub fn kernel_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.kernel_path = Some(path.into());
self
}
pub fn init_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.init_path = Some(path.into());
self
}
pub fn use_realmfs<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.rootfs = RootFS::RealmFSImage(path.into());
self
}
pub fn use_rawdisk<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.rootfs = RootFS::RawImage(path.into());
self
}
pub fn use_rawdisk_with_offset<P: Into<PathBuf>>(mut self, path: P, offset: usize) -> Self {
self.rootfs = RootFS::RawOffset(path.into(), offset);
self
}
pub fn use_systemd(mut self) -> Self {
self.launch_systemd = true;
self
}
pub fn boot(self) {
match Vm::open(self) {
Ok(vm) => if let Err(err) = vm.start() {
notify!("Error starting VM: {}", err);
}
Err(e) => notify!("Error creating VM: {}", e),
}
}
pub fn ram_size(&self) -> usize {
self.ram_size
}
pub fn ncpus(&self) -> usize {
self.ncpus
}
pub fn verbose(&self) -> bool {
self.verbose
}
pub fn launch_systemd(&self) -> bool {
self.launch_systemd
}
pub fn get_kernel_path(&self) -> Result<PathBuf> {
match self.kernel_path {
Some(ref path) if path.exists() => return Ok(path.to_path_buf()),
None => if let Some(path) = Self::search_kernel() {
return Ok(path)
}
_ => {},
}
Err(ErrorKind::KernelNotFound.into())
}
pub fn get_init_path(&self) -> Result<PathBuf> {
match self.init_path {
Some(ref path) if path.exists() => return Ok(path.to_path_buf()),
None => if let Some(path) = Self::search_init() {
return Ok(path)
}
_ => {},
}
Err(ErrorKind::InitNotFound.into())
}
pub fn get_init_cmdline(&self) -> Option<&str> {
self.init_cmd.as_ref().map(|s| s.as_str())
}
pub fn rootfs(&self) -> &RootFS {
&self.rootfs
}
fn search_init() -> Option<PathBuf> {
Self::search_binary("ph-init", &[
"rust/target/release", "rust/target/debug",
"target/debug", "target/release"
])
}
fn search_kernel() -> Option<PathBuf> {
Self::search_binary("ph_linux", &["kernel", "../kernel"])
}
fn search_binary(name: &str, paths: &[&str]) -> Option<PathBuf> {
let cwd = match env::current_dir() {
Ok(cwd) => cwd,
_ => return None,
};
for p in paths {
let p = Path::new(p).join(name);
let current = if p.is_absolute() {
p
} else {
cwd.join(p)
};
if current.exists() {
return Some(current);
}
}
None
}
fn parse_args(&mut self, args: env::Args) {
for arg in args.skip(1) {
self.parse_one_arg(&arg);
}
}
fn parse_one_arg(&mut self, arg: &str) {
if arg == "-v" {
self.verbose = true;
} else {
eprintln!("Unrecognized command line argument: {}", arg);
process::exit(1);
}
}
}

View File

@ -1,4 +1,4 @@
use std::result;
use std::{result, io};
use std::error;
use std::fmt;
use std::str;
@ -10,6 +10,8 @@ pub type Result<T> = result::Result<T, Error>;
#[derive(Debug)]
pub enum ErrorKind {
KernelNotFound,
InitNotFound,
InvalidAddress(u64),
InvalidMappingOffset(usize),
RegisterMemoryFailed,
@ -22,12 +24,15 @@ pub enum ErrorKind {
CreateVmFailed,
BadVersion,
EventFdError,
DiskImageOpen(disk::Error)
DiskImageOpen(disk::Error),
TerminalTermios(io::Error),
}
impl ErrorKind {
fn as_str(&self) -> &'static str {
match *self {
ErrorKind::KernelNotFound => "Could not find kernel image",
ErrorKind::InitNotFound => "Could not find init image",
ErrorKind::InvalidAddress(..) => "Invalid guest memory address",
ErrorKind::InvalidMappingOffset(..) => "Invalid memory mapping offset",
ErrorKind::RegisterMemoryFailed => "Failed to register memory region",
@ -41,6 +46,7 @@ impl ErrorKind {
ErrorKind::BadVersion => "unexpected kvm api version",
ErrorKind::EventFdError => "eventfd error",
ErrorKind::DiskImageOpen(_) => "failed to open disk image",
ErrorKind::TerminalTermios(_) => "failed termios",
}
}
}
@ -52,6 +58,7 @@ impl fmt::Display for ErrorKind {
ErrorKind::InvalidMappingOffset(offset) => write!(f, "{}: 0x{:x}", self.as_str(), offset),
ErrorKind::IoctlFailed(name) => write!(f, "Ioctl {} failed", name),
ErrorKind::DiskImageOpen(ref e) => write!(f, "failed to open disk image: {}", e),
ErrorKind::TerminalTermios(ref e) => write!(f, "error reading/restoring terminal state: {}", e),
_ => write!(f, "{}", self.as_str()),
}
}

View File

@ -5,11 +5,7 @@ use crate::memory::{GuestRam,KERNEL_CMDLINE_ADDRESS};
use super::Result;
fn add_defaults(cmdline: &mut KernelCmdLine, rdonly_root: bool, verbose: bool) {
let root_mount_type = if rdonly_root { "ro" } else { "rw" };
let output = if verbose {"earlyprintk=serial"} else {"quiet"};
fn add_defaults(cmdline: &mut KernelCmdLine) {
cmdline
.push("noapic")
.push("noacpi")
@ -24,10 +20,6 @@ fn add_defaults(cmdline: &mut KernelCmdLine, rdonly_root: bool, verbose: bool) {
.push_set_true("rcuupdate.rcu_normal_after_boot")
.push_set_val("console", "hvc0")
.push(root_mount_type)
.push_set_val("rootfstype", "9p")
.push_set_val("rootflags", "trans=virtio,version=9p2000.L,cache=loose")
.push_set_true("i8042.direct")
.push_set_true("i8042.dumbkbd")
.push_set_true("i8042.nopnp")
@ -37,11 +29,7 @@ fn add_defaults(cmdline: &mut KernelCmdLine, rdonly_root: bool, verbose: bool) {
.push_set_val("iommu", "off")
.push("cryptomgr.notests")
.push(output)
.push_set_val("8250.nr_uarts", "0")
//.push_set_val("init", "/home/user/virt/init");
.push_set_val("init", "/phinit");
.push_set_val("8250.nr_uarts", "0");
}
@ -55,13 +43,12 @@ impl KernelCmdLine {
KernelCmdLine { address: KERNEL_CMDLINE_ADDRESS, buffer: OsString::new() }
}
pub fn new_default(verbose: bool) -> KernelCmdLine {
pub fn new_default() -> KernelCmdLine {
let mut cmdline = KernelCmdLine::new();
add_defaults(&mut cmdline, true, verbose);
add_defaults(&mut cmdline);
cmdline
}
pub fn push(&mut self, option: &str) -> &mut Self {
if !self.buffer.is_empty() {
self.buffer.push(" ");
@ -89,13 +76,8 @@ impl KernelCmdLine {
pub fn write_to_memory(&self, memory: &GuestRam) -> Result<()> {
let bs = self.buffer.as_bytes();
let len = bs.len();
//println!("Kernel CmdLine: {:?}", self.buffer);
//println!("writing {} command line bytes to 0x{:x}", len + 1, KERNEL_CMDLINE_ADDRESS);
memory.write_bytes(KERNEL_CMDLINE_ADDRESS, bs)?;
memory.write_int(KERNEL_CMDLINE_ADDRESS + len as u64, 0u8)?;
Ok(())
}
}

View File

@ -1,22 +1,21 @@
use std::sync::Arc;
use std::thread;
use std::path::{PathBuf,Path};
use std::env;
use self::io::IoDispatcher;
use crate::virtio::VirtioBus;
use crate::devices;
use crate::disk;
use crate::memory::{GuestRam, KVM_KERNEL_LOAD_ADDRESS, MemoryManager, SystemAllocator, AddressRange};
use crate::kvm::*;
mod run;
pub mod io;
mod setup;
mod error;
mod kernel_cmdline;
mod config;
pub use config::VmConfig;
pub use self::error::{Result,Error,ErrorKind};
@ -24,48 +23,20 @@ pub use self::error::{Result,Error,ErrorKind};
use self::run::KvmRunArea;
use self::kernel_cmdline::KernelCmdLine;
pub struct VmConfig {
ram_size: usize,
ncpus: usize,
kernel_path: PathBuf,
init_path: PathBuf,
}
#[allow(dead_code)]
impl VmConfig {
pub fn new() -> VmConfig {
VmConfig {
ram_size: 256 * 1024 * 1024,
ncpus: 1,
kernel_path: PathBuf::new(),
init_path: PathBuf::new(),
}
}
pub fn ram_size_megs(&mut self, megs: usize) {
self.ram_size = megs * 1024 * 1024;
}
pub fn num_cpus(&mut self, ncpus: usize) {
self.ncpus = ncpus;
}
pub fn kernel_path(&mut self, path: &Path) {
self.kernel_path = path.to_path_buf();
}
pub fn init_path(&mut self, path: &Path) {
self.init_path = path.to_path_buf();
}
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use crate::disk::OpenType;
use termios::Termios;
use crate::vm::config::RootFS;
use std::path::Path;
}
pub struct Vm {
kvm: Kvm,
_config: VmConfig,
memory: MemoryManager,
io_dispatcher: Arc<IoDispatcher>,
_virtio: VirtioBus,
termios: Option<Termios>,
_virtio: Arc<VirtioBus>,
}
static REQUIRED_EXTENSIONS: &[u32] = &[
@ -89,55 +60,122 @@ fn get_base_dev_pfn(mem_size: u64) -> u64 {
}
impl Vm {
pub fn open(config: VmConfig) -> Result<Vm> {
let mut kvm = Kvm::open(&REQUIRED_EXTENSIONS)?;
fn create_kvm() -> Result<Kvm> {
let kvm = Kvm::open(&REQUIRED_EXTENSIONS)?;
kvm.set_tss_addr(0xFFFbd000)?;
kvm.create_pit2()?;
let ram = GuestRam::new(config.ram_size, &kvm)?;
let dev_addr_start = get_base_dev_pfn(config.ram_size as u64) * 4096;
kvm.create_irqchip()?;
Ok(kvm)
}
fn create_memory_manager(ram_size: usize) -> Result<MemoryManager> {
let kvm = Self::create_kvm()?;
let ram = GuestRam::new(ram_size, &kvm)?;
let dev_addr_start = get_base_dev_pfn(ram_size as u64) * 4096;
let dev_addr_size = u64::max_value() - dev_addr_start;
let allocator = SystemAllocator::new(AddressRange::new(dev_addr_start,dev_addr_size as usize));
let memory = MemoryManager::new(kvm.clone(), ram, allocator);
Ok(MemoryManager::new(kvm, ram, allocator))
}
kvm.create_irqchip()?;
fn setup_virtio(config: &VmConfig, cmdline: &mut KernelCmdLine, virtio: &mut VirtioBus) -> Result<()> {
devices::VirtioSerial::create(virtio)?;
devices::VirtioRandom::create(virtio)?;
devices::VirtioWayland::create(virtio)?;
let init_path = config.get_init_path()?;
devices::VirtioP9::create(virtio, "home", "/home/user", &init_path)?;
let verbose = env::args().any(|arg| arg == "-v");
let cmdline = KernelCmdLine::new_default(verbose);
Self::setup_rootfs(config, cmdline, virtio, &init_path)
}
cmdline.write_to_memory(memory.guest_ram())?;
let path = PathBuf::from(&config.kernel_path);
setup::kernel::load_pm_kernel(memory.guest_ram(), &path, cmdline.address(), cmdline.size())?;
fn setup_rootfs(config: &VmConfig, cmdline: &mut KernelCmdLine, virtio: &mut VirtioBus, init_path: &Path) -> Result<()> {
match config.rootfs() {
RootFS::SelfRoot => {
devices::VirtioP9::create(virtio, "/dev/root", "/", &init_path)?;
notify!("9p root");
cmdline.push_set_val("root", "/dev/root");
cmdline.push("ro");
cmdline.push_set_val("rootfstype", "9p");
cmdline.push_set_val("rootflags", "trans=virtio,version=9p2000.L,cache=loose");
cmdline.push_set_val("init", "/phinit");
},
RootFS::RealmFSImage(ref path) => {
let disk = disk::RealmFSImage::open(path, false)
.map_err(ErrorKind::DiskImageOpen)?;
devices::VirtioBlock::create(virtio, disk)?;
cmdline.push_set_val("root", "/dev/vda");
cmdline.push("rw");
cmdline.push_set_val("init", "/usr/bin/ph-init");
},
RootFS::RawImage(ref path) => {
let disk = disk::RawDiskImage::open(path, OpenType::MemoryOverlay).map_err(ErrorKind::DiskImageOpen)?;
devices::VirtioBlock::create(virtio, disk)?;
cmdline.push_set_val("root", "/dev/vda");
cmdline.push("rw");
},
RootFS::RawOffset(path, offset) => {
let disk = disk::RawDiskImage::open_with_offset(path, OpenType::ReadWrite, *offset)
.map_err(ErrorKind::DiskImageOpen)?;
devices::VirtioBlock::create(virtio, disk)?;
cmdline.push_set_val("root", "/dev/vda");
cmdline.push("rw");
cmdline.push_set_val("init", "/usr/bin/ph-init");
}
};
Ok(())
}
pub fn open(config: VmConfig) -> Result<Vm> {
let kernel_path = config.get_kernel_path()?;
let mut memory = Self::create_memory_manager(config.ram_size())?;
let mut cmdline = KernelCmdLine::new_default();
setup::kernel::load_pm_kernel(memory.guest_ram(), &kernel_path, cmdline.address(), cmdline.size())?;
let io_dispatch = IoDispatcher::new();
kvm.create_vcpus(config.ncpus)?;
memory.kvm_mut().create_vcpus(config.ncpus())?;
devices::rtc::Rtc::register(io_dispatch.clone());
if verbose {
devices::serial::SerialDevice::register(kvm.clone(),io_dispatch.clone(), 0);
if config.verbose() {
cmdline.push("earlyprintk=serial");
devices::serial::SerialDevice::register(memory.kvm().clone(),io_dispatch.clone(), 0);
} else {
cmdline.push("quiet");
}
let mut virtio = VirtioBus::new(memory.clone(), io_dispatch.clone(), kvm.clone());
devices::VirtioSerial::create(&mut virtio)?;
devices::VirtioRandom::create(&mut virtio)?;
devices::VirtioP9::create(&mut virtio, "/dev/root", "/", &config.init_path)?;
let saved= Termios::from_fd(0)
.map_err(ErrorKind::TerminalTermios)?;
let termios = Some(saved);
setup::mptable::setup_mptable(&memory, config.ncpus, virtio.pci_irqs())?;
let mut virtio = VirtioBus::new(memory.clone(), io_dispatch.clone(), memory.kvm().clone());
Self::setup_virtio(&config, &mut cmdline, &mut virtio)?;
if config.launch_systemd() {
cmdline.push("phinit.run_systemd");
}
if let Some(init_cmd) = config.get_init_cmdline() {
cmdline.push_set_val("init", init_cmd);
}
cmdline.write_to_memory(memory.guest_ram())?;
setup::mptable::setup_mptable(memory.guest_ram(), config.ncpus(), virtio.pci_irqs())?;
Ok(Vm {
kvm,
_config: config,
memory,
io_dispatcher: io_dispatch,
_virtio: virtio,
termios,
_virtio: Arc::new(virtio),
})
}
pub fn start(&self) -> Result<()> {
let shutdown = Arc::new(AtomicBool::new(false));
let mut handles = Vec::new();
for vcpu in self.kvm.get_vcpus() {
for vcpu in self.memory.kvm().get_vcpus() {
setup::cpu::setup_protected_mode(&vcpu, KVM_KERNEL_LOAD_ADDRESS + 0x200, self.memory.guest_ram())?;
let mut run_area = KvmRunArea::new(vcpu, shutdown.clone(), self.io_dispatcher.clone())?;
let h = thread::spawn(move || run_area.run());
@ -147,6 +185,10 @@ impl Vm {
for h in handles {
h.join().expect("...");
}
if let Some(termios) = self.termios {
let _ = termios::tcsetattr(0, termios::TCSANOW, &termios)
.map_err(ErrorKind::TerminalTermios)?;
}
Ok(())
}

View File

@ -4,6 +4,7 @@ use crate::kvm::KvmVcpu;
use crate::memory::Mapping;
use super::Result;
use super::io::IoDispatcher;
use std::sync::atomic::{AtomicBool, Ordering};
const KVM_EXIT_UNKNOWN:u32 = 0;
const KVM_EXIT_IO:u32 = 2;
@ -17,6 +18,7 @@ pub struct KvmRunArea {
vcpu: KvmVcpu,
io: Arc<IoDispatcher>,
mapping: Mapping,
shutdown: Arc<AtomicBool>,
}
pub struct IoExitData {
@ -34,13 +36,14 @@ pub struct MmioExitData {
}
impl KvmRunArea {
pub fn new(vcpu: KvmVcpu, io_dispatcher: Arc<IoDispatcher>) -> Result<KvmRunArea> {
pub fn new(vcpu: KvmVcpu, shutdown: Arc<AtomicBool>, io_dispatcher: Arc<IoDispatcher>) -> Result<KvmRunArea> {
let size = vcpu.get_vcpu_mmap_size()?;
let mapping = Mapping::new_from_fd(vcpu.raw_fd(), size)?;
Ok(KvmRunArea{
vcpu,
io: io_dispatcher,
mapping,
shutdown,
})
}
@ -97,6 +100,9 @@ impl KvmRunArea {
} else {
self.handle_exit();
}
if self.shutdown.load(Ordering::Relaxed) {
return;
}
}
}
@ -106,8 +112,8 @@ impl KvmRunArea {
KVM_EXIT_IO => { self.handle_exit_io() },
KVM_EXIT_MMIO => { self.handle_exit_mmio() },
KVM_EXIT_INTR => { println!("intr")},
KVM_EXIT_SHUTDOWN => { println!("shut");
self.handle_problem();
KVM_EXIT_SHUTDOWN => {
self.handle_shutdown();
},
KVM_EXIT_SYSTEM_EVENT => { println!("event")},
KVM_EXIT_INTERNAL_ERROR => {
@ -120,7 +126,11 @@ impl KvmRunArea {
}
}
fn handle_problem(&mut self) {
fn handle_shutdown(&mut self) {
self.shutdown.store(true, Ordering::Relaxed);
}
fn _handle_problem(&mut self) {
let regs = self.vcpu.get_regs().unwrap();
let sregs = self.vcpu.get_sregs().unwrap();
println!("REGS:\n{:?}", regs);