diff --git a/Cargo.lock b/Cargo.lock index 9e15b85403..a911969e40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,3 +1,15 @@ +[[package]] +name = "arch" +version = "0.1.0" +dependencies = [ + "device_manager 0.1.0", + "devices 0.1.0", + "kernel_cmdline 0.1.0", + "kvm 0.1.0", + "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", + "sys_util 0.1.0", +] + [[package]] name = "bitflags" version = "1.0.1" @@ -17,6 +29,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "crosvm" version = "0.1.0" dependencies = [ + "arch 0.1.0", "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "crosvm_plugin 0.12.0", "data_model 0.1.0", @@ -289,6 +302,7 @@ dependencies = [ name = "x86_64" version = "0.1.0" dependencies = [ + "arch 0.1.0", "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "data_model 0.1.0", "device_manager 0.1.0", diff --git a/Cargo.toml b/Cargo.toml index c0d1a1ec80..e720a57682 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ panic = 'abort' plugin = ["plugin_proto", "crosvm_plugin", "protobuf"] [dependencies] +arch = { path = "arch" } devices = { path = "devices" } device_manager = { path = "device_manager" } io_jail = { path = "io_jail" } diff --git a/arch/Cargo.toml b/arch/Cargo.toml new file mode 100644 index 0000000000..2b2aa3890f --- /dev/null +++ b/arch/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "arch" +version = "0.1.0" +authors = ["The Chromium OS Authors"] + +[dependencies] +devices = { path = "../devices" } +device_manager = { path = "../device_manager" } +kvm = { path = "../kvm" } +sys_util = { path = "../sys_util" } +kernel_cmdline = { path = "../kernel_cmdline" } +libc = "*" diff --git a/arch/src/lib.rs b/arch/src/lib.rs new file mode 100644 index 0000000000..1fece03635 --- /dev/null +++ b/arch/src/lib.rs @@ -0,0 +1,114 @@ +// Copyright 2018 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +extern crate sys_util; +extern crate kernel_cmdline; +extern crate kvm; +extern crate libc; +extern crate device_manager; +extern crate devices; + +use std::ffi::CStr; +use std::fs::File; +use std::result; +use std::sync::{Arc, Mutex}; + +use kvm::{Kvm, Vm, Vcpu}; +use sys_util::{EventFd, GuestMemory}; + +pub type Result = result::Result>; + +/// Trait which is implemented for each Linux Architecture in order to +/// set up the memory, cpus, and system devices and to boot the kernel. +pub trait LinuxArch { + /// Loads the kernel from an open file. + /// + /// # Arguments + /// + /// * `mem` - The memory to be used by the guest. + /// * `kernel_image` - the File object for the specified kernel. + fn load_kernel(mem: &GuestMemory, kernel_image: &mut File) -> Result<()>; + + /// Configures the system memory space should be called once per vm before + /// starting vcpu threads. + /// + /// # Arguments + /// + /// * `mem` - The memory to be used by the guest + /// * `mem_size` - The size in bytes of system memory + /// * `vcpu_count` - Number of virtual CPUs the guest will have + /// * `cmdline` - the kernel commandline + fn setup_system_memory(mem: &GuestMemory, + mem_size: u64, + vcpu_count: u32, + cmdline: &CStr) -> Result<()>; + + /// Creates a new VM object and initializes architecture specific devices + /// + /// # Arguments + /// + /// * `kvm` - The opened /dev/kvm object. + /// * `mem` - The memory to be used by the guest. + fn create_vm(kvm: &Kvm, mem: GuestMemory) -> Result; + + /// This creates a GuestMemory object for this VM + /// + /// * `mem_size` - Desired physical memory size in bytes for this VM + fn setup_memory(mem_size: u64) -> Result; + + /// The creates the interrupt controller device and optionally returns the fd for it. + /// Some architectures may not have a separate descriptor for the interrupt + /// controller, so they would return None even on success. + /// + /// # Arguments + /// + /// * `vm` - the vm object + fn create_irq_chip(vm: &kvm::Vm) -> Result>; + + /// This returns the first page frame number for use by the balloon driver. + /// + /// # Arguments + /// + /// * `mem_size` - the size in bytes of physical ram for the guest + fn get_base_dev_pfn(mem_size: u64) -> u64; + + /// This returns a minimal kernel command for this architecture. + fn get_base_linux_cmdline() -> kernel_cmdline::Cmdline; + + /// This creates and returns a device_manager object for this vm. + /// + /// # Arguments + /// + /// * `vm` - the vm object + /// * `mem` - A copy of the GuestMemory object for this VM. + fn get_device_manager(vm: &mut Vm, mem: GuestMemory) + -> Result; + + /// Sets up the IO bus for this platform + /// + /// # Arguments + /// + /// * - `vm` the vm object + /// * - `exit_evt` - the event fd object which should receive exit events + fn setup_io_bus(vm: &mut Vm, exit_evt: EventFd) + -> Result<(devices::Bus, Arc>)>; + + /// Configures the vcpu and should be called once per vcpu from the vcpu's thread. + /// + /// # Arguments + /// + /// * `guest_mem` - The memory to be used by the guest. + /// * `kernel_load_offset` - Offset in bytes from `guest_mem` at which the + /// kernel starts. + /// * `kvm` - The /dev/kvm object that created vcpu. + /// * `vcpu` - The VCPU object to configure. + /// * `cpu_id` - The id of the given `vcpu`. + /// * `num_cpus` - Number of virtual CPUs the guest will have. + fn configure_vcpu(guest_mem: &GuestMemory, + kvm: &Kvm, + vcpu: &Vcpu, + cpu_id: u64, + num_cpus: u64) + -> Result<()>; +} diff --git a/src/linux.rs b/src/linux.rs index 4c14c68da4..e599740cc9 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -5,8 +5,11 @@ use std; use std::ffi::{CString, CStr}; use std::fmt; +use std::error; use std::fs::{File, OpenOptions, remove_file}; -use std::io::{self, stdin, stdout}; +use std::io::{self, stdin}; +#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] +use std::io::stdout; use std::os::unix::net::UnixDatagram; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -32,7 +35,10 @@ use Config; use DiskType; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -use x86_64; +use arch::LinuxArch; + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +use x86_64::X8664arch as Arch; pub enum Error { BalloonDeviceNew(devices::virtio::BalloonError), @@ -41,14 +47,13 @@ pub enum Error { CloneEventFd(sys_util::Error), Cmdline(kernel_cmdline::Error), CreateEventFd(sys_util::Error), - CreateGuestMemory(sys_util::GuestMemoryError), - CreateIrqChip(sys_util::Error), + CreateGuestMemory(Box), + CreateIrqChip(Box), CreateKvm(sys_util::Error), - CreatePit(sys_util::Error), CreateSignalFd(sys_util::SignalFdError), CreateSocket(io::Error), CreateVcpu(sys_util::Error), - CreateVm(sys_util::Error), + CreateVm(Box), DeviceJail(io_jail::Error), DevicePivotRoot(io_jail::Error), Disk(io::Error), @@ -76,13 +81,15 @@ pub enum Error { WaylandDeviceNew(sys_util::Error), WaylandTempDir(sys_util::Error), #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - SetupSystemMemory(x86_64::Error), + SetupSystemMemory(Box), #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - ConfigureVcpu(x86_64::Error), + ConfigureVcpu(Box), #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - LoadKernel(x86_64::Error), + LoadKernel(Box), #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - SetupIoBus(x86_64::Error), + SetupIoBus(Box), + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + SetupMMIOBus(Box), } impl fmt::Display for Error { @@ -101,7 +108,6 @@ impl fmt::Display for Error { write!(f, "failed to create in-kernel IRQ chip: {:?}", e) } &Error::CreateKvm(ref e) => write!(f, "failed to open /dev/kvm: {:?}", e), - &Error::CreatePit(ref e) => write!(f, "failed to create in-kernel PIT: {:?}", e), &Error::CreateSignalFd(ref e) => write!(f, "failed to create signalfd: {:?}", e), &Error::CreateSocket(ref e) => write!(f, "failed to create socket: {}", e), &Error::CreateVcpu(ref e) => write!(f, "failed to create VCPU: {:?}", e), @@ -151,14 +157,15 @@ impl fmt::Display for Error { write!(f, "failed to create wayland device jail directroy: {:?}", e) } #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - &Error::SetupSystemMemory(ref e) => write!(f, "error setting up system memory: {:?}", e), + &Error::SetupSystemMemory(ref e) => write!(f, "error setting up system memory: {}", e), #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - &Error::ConfigureVcpu(ref e) => write!(f, "failed to configure vcpu: {:?}", e), + &Error::ConfigureVcpu(ref e) => write!(f, "failed to configure vcpu: {}", e), #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - &Error::LoadKernel(ref e) => write!(f, "failed to load kernel: {:?}", e), + &Error::LoadKernel(ref e) => write!(f, "failed to load kernel: {}", e), #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - &Error::SetupIoBus(ref e) => write!(f, "failed to setup iobus: {:?}", e), - + &Error::SetupIoBus(ref e) => write!(f, "failed to setup iobus: {}", e), + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + &Error::SetupMMIOBus(ref e) => write!(f, "failed to setup mmio bus: {}", e), } } } @@ -220,9 +227,12 @@ fn setup_mmio_bus(cfg: &Config, -> Result { static DEFAULT_PIVOT_ROOT: &'static str = "/var/empty"; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - let mut device_manager = x86_64::get_device_manager(vm, mem.clone()); + let mut device_manager = Arch::get_device_manager(vm, mem.clone()). + map_err(|e| Error::SetupMMIOBus(e))?; #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] - let mut device_manager = device_manager::DeviceManager::new(vm, mem.clone(), 0, 0, 0); + let mut device_manager = device_manager::DeviceManager::new(vm, + mem.clone(), + 0, 0, 0); // An empty directory for jailed device's pivot root. let empty_root_path = Path::new(DEFAULT_PIVOT_ROOT); @@ -416,12 +426,8 @@ fn setup_vcpu(kvm: &Kvm, let vcpu = Vcpu::new(cpu_id as libc::c_ulong, &kvm, &vm) .map_err(Error::CreateVcpu)?; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - x86_64::configure_vcpu(vm.get_memory(), - &kvm, - &vcpu, - cpu_id as u64, - vcpu_count as u64) - .map_err(Error::ConfigureVcpu)?; + Arch::configure_vcpu(vm.get_memory(), &kvm, &vcpu, cpu_id as u64, vcpu_count as u64). + map_err(Error::ConfigureVcpu)?; Ok(vcpu) } @@ -494,7 +500,8 @@ fn run_control(vm: &mut Vm, sigchld_fd: SignalFd, kill_signaled: Arc, vcpu_handles: Vec>, - balloon_host_socket: UnixDatagram) + balloon_host_socket: UnixDatagram, + _irqchip_fd: Option) -> Result<()> { const MAX_VM_FD_RECV: usize = 1; @@ -641,52 +648,53 @@ pub fn run_config(cfg: Config) -> Result<()> { let mem_size = cfg.memory.unwrap_or(256) << 20; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - let mem = x86_64::setup_memory(mem_size).map_err(|e| Error::CreateGuestMemory(e))?; + let mem = Arch::setup_memory(mem_size as u64).map_err(|e| Error::CreateGuestMemory(e))?; #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] let mem = GuestMemory::new(&vec![(GuestAddress(0), mem_size as u64)]). - map_err(|e| Error::CreateGuestMemory(e))?; + map_err(|e| Error::CreateGuestMemory(Box::new(e)))?; let kvm = Kvm::new().map_err(Error::CreateKvm)?; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - let mut vm = x86_64::create_vm(&kvm, mem.clone()).map_err(|e| Error::CreateVm(e))?; + let mut vm = Arch::create_vm(&kvm, mem.clone()).map_err(|e| Error::CreateVm(e))?; #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] - let mut vm = Vm::new(&kvm, mem.clone()).map_err(|e| Error::CreateVm(e))?; + let mut vm = Vm::new(&kvm, mem.clone()).map_err(|e| Error::CreateVm(Box::new(e)))?; let vcpu_count = cfg.vcpu_count.unwrap_or(1); let mut vcpu_handles = Vec::with_capacity(vcpu_count as usize); let vcpu_thread_barrier = Arc::new(Barrier::new((vcpu_count + 1) as usize)); let mut vcpus = Vec::with_capacity(vcpu_count as usize); for cpu_id in 0..vcpu_count { - let vcpu = setup_vcpu(&kvm, - &vm, - cpu_id, - vcpu_count)?; + let vcpu = setup_vcpu(&kvm, &vm, cpu_id, vcpu_count)?; vcpus.push(vcpu); } #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - let mut cmdline = x86_64::get_base_linux_cmdline(); + let irq_chip = Arch::create_irq_chip(&vm).map_err(|e| Error::CreateIrqChip(e))?; + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + let irq_chip = None; + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + let mut cmdline = Arch::get_base_linux_cmdline(); #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] let mut cmdline = kernel_cmdline::Cmdline::new(128); #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - let mut next_dev_pfn = x86_64::get_base_dev_pfn(mem_size as u64); + let mut next_dev_pfn = Arch::get_base_dev_pfn(mem_size as u64); #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] let mut next_dev_pfn = 0; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - let (io_bus, stdio_serial) = x86_64::setup_io_bus(&mut vm, - exit_evt.try_clone(). - map_err(Error::CloneEventFd)?). + let (io_bus, stdio_serial) = Arch::setup_io_bus(&mut vm, + exit_evt.try_clone(). + map_err(Error::CloneEventFd)?). map_err(|e| Error::SetupIoBus(e))?; // The non x86 case is kind of bogus using the exit_evt as an fd for serial // It's purpose is just to make the build happy since it doesn't actually run anyway #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] let (io_bus, stdio_serial) = (devices::Bus::new(), - Arc::new(Mutex::new( - devices::Serial::new_out(exit_evt.try_clone(). - map_err(Error::CloneEventFd)?, - Box::new(stdout()))))); + Arc::new(Mutex::new(devices::Serial::new_out( + exit_evt.try_clone().map_err(Error::CloneEventFd)?, + Box::new(stdout()))))); let (balloon_host_socket, balloon_device_socket) = UnixDatagram::pair() .map_err(Error::CreateSocket)?; @@ -701,15 +709,16 @@ pub fn run_config(cfg: Config) -> Result<()> { cmdline.insert_str(¶m).map_err(Error::Cmdline)?; } - let kernel_image = File::open(cfg.kernel_path.as_path()) + let mut kernel_image = File::open(cfg.kernel_path.as_path()) .map_err(|e| Error::OpenKernel(cfg.kernel_path.clone(), e))?; // separate out load_kernel from other setup to get a specific error for // kernel loading #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - x86_64::load_kernel(&mem, kernel_image).map_err(|e| Error::LoadKernel(e))?; + Arch::load_kernel(&mem, &mut kernel_image).map_err(|e| Error::LoadKernel(e))?; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - x86_64::setup_system_memory(&mem, vcpu_count, &CString::new(cmdline).unwrap()). + Arch::setup_system_memory(&mem, mem_size as u64, vcpu_count, + &CString::new(cmdline).unwrap()). map_err(|e| Error::SetupSystemMemory(e))?; for (cpu_id, vcpu) in vcpus.into_iter().enumerate() { @@ -732,5 +741,6 @@ pub fn run_config(cfg: Config) -> Result<()> { sigchld_fd, kill_signaled, vcpu_handles, - balloon_host_socket) + balloon_host_socket, + irq_chip) } diff --git a/src/main.rs b/src/main.rs index 110435e860..ea161cfc0e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ //! Runs a virtual machine under KVM +extern crate arch; extern crate devices; extern crate device_manager; extern crate libc; diff --git a/x86_64/Cargo.toml b/x86_64/Cargo.toml index edad6b6f4d..3a88f98a84 100644 --- a/x86_64/Cargo.toml +++ b/x86_64/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["The Chromium OS Authors"] [dependencies] +arch = { path = "../arch" } data_model = { path = "../data_model" } devices = { path = "../devices" } device_manager = { path = "../device_manager" } diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index 3eae049350..9a16776a17 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +extern crate arch; extern crate byteorder; extern crate data_model; extern crate devices; @@ -71,36 +72,20 @@ use bootparam::E820_RAM; use sys_util::{EventFd, GuestAddress, GuestMemory}; use kvm::*; -pub use regs::Error as RegError; -pub use interrupts::Error as IntError; -pub use mptable::Error as MpTableError; - #[derive(Debug)] pub enum Error { /// Error configuring the system ConfigureSystem, - /// Error configuring the VCPU. - CpuSetup(cpuid::Error), /// Unable to clone an EventFd CloneEventFd(sys_util::Error), /// Unable to make an EventFd CreateEventFd(sys_util::Error), /// The kernel extends past the end of RAM KernelOffsetPastEnd, - /// Error configuring the VCPU registers. - RegisterConfiguration(RegError), - /// Error configuring the VCPU floating point registers. - FpuRegisterConfiguration(RegError), /// Error registering an IrqFd RegisterIrqfd(sys_util::Error), - /// Error configuring the VCPU segment registers. - SegmentRegisterConfiguration(RegError), LoadCmdline(kernel_loader::Error), LoadKernel(kernel_loader::Error), - /// Error configuring the VCPU local interrupt. - LocalIntConfiguration(IntError), - /// Error writing MP table to memory. - MpTableSetup(MpTableError), /// Error writing the zero page of guest memory. ZeroPageSetup, /// The zero page extends past the end of guest_mem. @@ -113,24 +98,13 @@ impl error::Error for Error { fn description(&self) -> &str { match self { &Error::ConfigureSystem => "Error configuring the system", - &Error::CpuSetup(_) => "Error configuring the VCPU", &Error::CloneEventFd(_) => "Unable to clone an EventFd", &Error::CreateEventFd(_) => "Unable to make an EventFd", &Error::KernelOffsetPastEnd => "The kernel extends past the end of RAM", - &Error::RegisterConfiguration(_) => - "Error configuring the VCPU registers", - &Error::FpuRegisterConfiguration(_) => - "Error configuring the VCPU floating point registers", &Error::RegisterIrqfd(_) => "Error registering an IrqFd", - &Error::SegmentRegisterConfiguration(_) => - "Error configuring the VCPU segment registers", &Error::LoadCmdline(_) => "Error Loading command line", &Error::LoadKernel(_) => "Error Loading Kernel", - &Error::LocalIntConfiguration(_) => - "Error configuring the VCPU local interrupt", - &Error::MpTableSetup(_) => - "Error writing MP table to memory", &Error::ZeroPageSetup => "Error writing the zero page of guest memory", &Error::ZeroPagePastRamEnd => @@ -146,7 +120,8 @@ impl Display for Error { } } -pub type Result = result::Result; +pub struct X8664arch; +pub type Result = result::Result>; const BOOT_STACK_POINTER: u64 = 0x8000; const MEM_32BIT_GAP_SIZE: u64 = (768 << 20); @@ -158,207 +133,6 @@ const KERNEL_START_OFFSET: u64 = 0x200000; const CMDLINE_OFFSET: u64 = 0x20000; const CMDLINE_MAX_SIZE: u64 = KERNEL_START_OFFSET - CMDLINE_OFFSET; -/// Loads the kernel from an open file. -/// -/// # Arguments -/// -/// * `mem` - The memory to be used by the guest. -/// * `kernel_image` - the File object for the specified kernel. -pub fn load_kernel(mem: &GuestMemory, mut kernel_image: File) -> Result<()> { - kernel_loader::load_kernel(mem, GuestAddress(KERNEL_START_OFFSET), &mut kernel_image) - .map_err(|e| Error::LoadKernel(e))?; - Ok(()) -} - -/// Configures the system memory space should be called once per vm before -/// starting vcpu threads. -/// -/// # Arguments -/// -/// * `mem` - The memory to be used by the guest. -/// * `vcpu_count` - Number of virtual CPUs the guest will have. -/// * `cmdline` - the kernel commandline -pub fn setup_system_memory(mem: &GuestMemory, vcpu_count: u32, cmdline: &CStr) -> Result<()> { - kernel_loader::load_cmdline(mem, GuestAddress(CMDLINE_OFFSET), cmdline) - .map_err(|e| Error::LoadCmdline(e))?; - configure_system(mem, GuestAddress(KERNEL_START_OFFSET), GuestAddress(CMDLINE_OFFSET), - cmdline.to_bytes().len() + 1, vcpu_count as u8) - .map_err(|_| Error::ConfigureSystem)?; - Ok(()) -} - -/// Creates a new VM object and initializes architecture specific devices -/// -/// # Arguments -/// -/// * `kvm` - The opened /dev/kvm object. -/// * `mem` - The memory to be used by the guest. -pub fn create_vm(kvm: &Kvm, mem: GuestMemory) -> result::Result { - let vm = Vm::new(&kvm, mem)?; - let tss_addr = GuestAddress(0xfffbd000); - vm.set_tss_addr(tss_addr).expect("set tss addr failed"); - vm.create_pit().expect("create pit failed"); - vm.create_irq_chip()?; - Ok(vm) -} - -/// This creates a GuestMemory object for this VM -/// -/// * `mem_size` - Desired physical memory size for this VM -pub fn setup_memory(mem_size: usize) -> result::Result { - let arch_mem_regions = arch_memory_regions(mem_size as u64); - GuestMemory::new(&arch_mem_regions) -} - -/// This returns the first page frame number for use by the balloon driver. -pub fn get_base_dev_pfn(mem_size: u64) -> u64 { - // Put device memory at nearest 2MB boundary after physical memory - const MB: u64 = 1024 * 1024; - let mem_size_round_2mb = (mem_size + 2*MB - 1) / (2*MB) * (2*MB); - mem_size_round_2mb / sys_util::pagesize() as u64 -} - -/// This returns a base part of the kernel command for this architecture -pub fn get_base_linux_cmdline() -> kernel_cmdline::Cmdline { - let mut cmdline = kernel_cmdline::Cmdline::new(CMDLINE_MAX_SIZE as usize); - cmdline.insert_str("console=ttyS0 noacpi reboot=k panic=1 pci=off"). - unwrap(); - cmdline -} - -/// This creates and returns a device_manager object for this vm. -/// -/// # Arguments -/// -/// * `vm` - the vm object -/// * `mem` - A copy of the GuestMemory object for this VM. -pub fn get_device_manager(vm: &mut Vm, mem: GuestMemory) -> device_manager::DeviceManager { - const MMIO_BASE: u64 = 0xd0000000; - const MMIO_LEN: u64 = 0x1000; - const IRQ_BASE: u32 = 5; - - device_manager::DeviceManager::new(vm, mem, MMIO_LEN, MMIO_BASE, IRQ_BASE) -} - -/// Sets up the IO bus for this platform -/// -/// # Arguments -/// -/// * - `vm` the vm object -/// * - `exit_evt` - the event fd object which should receive exit events -pub fn setup_io_bus(vm: &mut Vm, exit_evt: EventFd) - -> Result<(devices::Bus, Arc>)> { - struct NoDevice; - impl devices::BusDevice for NoDevice {} - - let mut io_bus = devices::Bus::new(); - - let com_evt_1_3 = EventFd::new().map_err(Error::CreateEventFd)?; - let com_evt_2_4 = EventFd::new().map_err(Error::CreateEventFd)?; - let stdio_serial = - Arc::new(Mutex::new(devices::Serial::new_out(com_evt_1_3 - .try_clone() - .map_err(Error::CloneEventFd)?, - Box::new(stdout())))); - let nul_device = Arc::new(Mutex::new(NoDevice)); - io_bus.insert(stdio_serial.clone(), 0x3f8, 0x8).unwrap(); - io_bus - .insert(Arc::new(Mutex::new(devices::Serial::new_sink(com_evt_2_4 - .try_clone() - .map_err(Error::CloneEventFd)?))), - 0x2f8, - 0x8) - .unwrap(); - io_bus - .insert(Arc::new(Mutex::new(devices::Serial::new_sink(com_evt_1_3 - .try_clone() - .map_err(Error::CloneEventFd)?))), - 0x3e8, - 0x8) - .unwrap(); - io_bus - .insert(Arc::new(Mutex::new(devices::Serial::new_sink(com_evt_2_4 - .try_clone() - .map_err(Error::CloneEventFd)?))), - 0x2e8, - 0x8) - .unwrap(); - io_bus - .insert(Arc::new(Mutex::new(devices::Cmos::new())), 0x70, 0x2) - .unwrap(); - io_bus - .insert(Arc::new(Mutex::new(devices::I8042Device::new(exit_evt - .try_clone() - .map_err(Error::CloneEventFd)?))), - 0x061, - 0x4) - .unwrap(); - io_bus.insert(nul_device.clone(), 0x040, 0x8).unwrap(); // ignore pit - io_bus.insert(nul_device.clone(), 0x0ed, 0x1).unwrap(); // most likely this one does nothing - io_bus.insert(nul_device.clone(), 0x0f0, 0x2).unwrap(); // ignore fpu - io_bus.insert(nul_device.clone(), 0xcf8, 0x8).unwrap(); // ignore pci - - vm.register_irqfd(&com_evt_1_3, 4) - .map_err(Error::RegisterIrqfd)?; - vm.register_irqfd(&com_evt_2_4, 3) - .map_err(Error::RegisterIrqfd)?; - - Ok((io_bus, stdio_serial)) -} - -/// Returns a Vec of the valid memory addresses. -/// These should be used to configure the GuestMemory structure for the platfrom. -/// For x86_64 all addresses are valid from the start of the kenel except a -/// carve out at the end of 32bit address space. -fn arch_memory_regions(size: u64) -> Vec<(GuestAddress, u64)> { - let mem_end = GuestAddress(size); - let first_addr_past_32bits = GuestAddress(FIRST_ADDR_PAST_32BITS); - let end_32bit_gap_start = GuestAddress(FIRST_ADDR_PAST_32BITS - MEM_32BIT_GAP_SIZE); - - let mut regions = Vec::new(); - if mem_end < end_32bit_gap_start { - regions.push((GuestAddress(0), size)); - } else { - regions.push((GuestAddress(0), end_32bit_gap_start.offset())); - if mem_end > first_addr_past_32bits { - regions.push((first_addr_past_32bits, mem_end.offset_from(first_addr_past_32bits))); - } - } - - regions -} - -/// Configures the vcpu and should be called once per vcpu from the vcpu's thread. -/// -/// # Arguments -/// -/// * `guest_mem` - The memory to be used by the guest. -/// * `kernel_load_offset` - Offset from `guest_mem` at which the kernel starts. -/// * `kvm` - The /dev/kvm object that created vcpu. -/// * `vcpu` - The VCPU object to configure. -/// * `cpu_id` - The id of the given `vcpu`. -/// * `num_cpus` - Number of virtual CPUs the guest will have. -pub fn configure_vcpu(guest_mem: &GuestMemory, - kvm: &kvm::Kvm, - vcpu: &kvm::Vcpu, - cpu_id: u64, - num_cpus: u64) - -> Result<()> { - let kernel_load_addr = GuestAddress(KERNEL_START_OFFSET); - cpuid::setup_cpuid(kvm, vcpu, cpu_id, num_cpus).map_err(Error::CpuSetup)?; - regs::setup_msrs(vcpu).map_err(Error::RegisterConfiguration)?; - let kernel_end = guest_mem.checked_offset(kernel_load_addr, KERNEL_64BIT_ENTRY_OFFSET) - .ok_or(Error::KernelOffsetPastEnd)?; - regs::setup_regs(vcpu, - (kernel_end).offset() as u64, - BOOT_STACK_POINTER as u64, - ZERO_PAGE_OFFSET as u64).map_err(Error::RegisterConfiguration)?; - regs::setup_fpu(vcpu).map_err(Error::FpuRegisterConfiguration)?; - regs::setup_sregs(guest_mem, vcpu).map_err(Error::SegmentRegisterConfiguration)?; - interrupts::set_lint(vcpu).map_err(Error::LocalIntConfiguration)?; - Ok(()) -} - fn configure_system(guest_mem: &GuestMemory, kernel_addr: GuestAddress, cmdline_addr: GuestAddress, @@ -374,7 +148,7 @@ fn configure_system(guest_mem: &GuestMemory, let end_32bit_gap_start = GuestAddress(FIRST_ADDR_PAST_32BITS - MEM_32BIT_GAP_SIZE); // Note that this puts the mptable at 0x0 in guest physical memory. - mptable::setup_mptable(guest_mem, num_cpus).map_err(Error::MpTableSetup)?; + mptable::setup_mptable(guest_mem, num_cpus)?; let mut params: boot_params = Default::default(); @@ -419,7 +193,7 @@ fn configure_system(guest_mem: &GuestMemory, /// Returns Ok(()) if successful, or an error if there is no space left in the map. fn add_e820_entry(params: &mut boot_params, addr: u64, size: u64, mem_type: u32) -> Result<()> { if params.e820_entries >= params.e820_map.len() as u8 { - return Err(Error::E820Configuration); + return Err(Box::new(Error::E820Configuration)); } params.e820_map[params.e820_entries as usize].addr = addr; @@ -430,6 +204,223 @@ fn add_e820_entry(params: &mut boot_params, addr: u64, size: u64, mem_type: u32) Ok(()) } +/// Returns a Vec of the valid memory addresses. +/// These should be used to configure the GuestMemory structure for the platfrom. +/// For x86_64 all addresses are valid from the start of the kenel except a +/// carve out at the end of 32bit address space. +fn arch_memory_regions(size: u64) -> Vec<(GuestAddress, u64)> { + let mem_end = GuestAddress(size); + let first_addr_past_32bits = GuestAddress(FIRST_ADDR_PAST_32BITS); + let end_32bit_gap_start = GuestAddress(FIRST_ADDR_PAST_32BITS - MEM_32BIT_GAP_SIZE); + + let mut regions = Vec::new(); + if mem_end < end_32bit_gap_start { + regions.push((GuestAddress(0), size)); + } else { + regions.push((GuestAddress(0), end_32bit_gap_start.offset())); + if mem_end > first_addr_past_32bits { + regions.push((first_addr_past_32bits, mem_end.offset_from(first_addr_past_32bits))); + } + } + + regions +} + +impl arch::LinuxArch for X8664arch { + /// Loads the kernel from an open file. + /// + /// # Arguments + /// + /// * `mem` - The memory to be used by the guest. + /// * `kernel_image` - the File object for the specified kernel. + fn load_kernel(mem: &GuestMemory, mut kernel_image: &mut File) -> Result<()> { + kernel_loader::load_kernel(mem, GuestAddress(KERNEL_START_OFFSET), + &mut kernel_image)?; + Ok(()) + } + + /// Configures the system memory space should be called once per vm before + /// starting vcpu threads. + /// + /// # Arguments + /// + /// * `mem` - The memory to be used by the guest. + /// * `vcpu_count` - Number of virtual CPUs the guest will have. + /// * `cmdline` - the kernel commandline + fn setup_system_memory(mem: &GuestMemory, _mem_size: u64, + vcpu_count: u32, cmdline: &CStr) -> Result<()> { + kernel_loader::load_cmdline(mem, GuestAddress(CMDLINE_OFFSET), cmdline)?; + configure_system(mem, GuestAddress(KERNEL_START_OFFSET), + GuestAddress(CMDLINE_OFFSET), + cmdline.to_bytes().len() + 1, vcpu_count as u8)?; + Ok(()) + } + + /// Creates a new VM object and initializes architecture specific devices + /// + /// # Arguments + /// + /// * `kvm` - The opened /dev/kvm object. + /// * `mem` - The memory to be used by the guest. + fn create_vm(kvm: &Kvm, mem: GuestMemory) -> Result { + let vm = Vm::new(&kvm, mem)?; + let tss_addr = GuestAddress(0xfffbd000); + vm.set_tss_addr(tss_addr).expect("set tss addr failed"); + vm.create_pit().expect("create pit failed"); + vm.create_irq_chip()?; + Ok(vm) + } + + /// This creates a GuestMemory object for this VM + /// + /// * `mem_size` - Desired physical memory size in bytes for this VM + fn setup_memory(mem_size: u64) -> Result { + let arch_mem_regions = arch_memory_regions(mem_size); + let mem = GuestMemory::new(&arch_mem_regions)?; + Ok(mem) + } + + /// The creates the interrupt controller device and optionally returns the fd for it. + /// Some architectures may not have a separate descriptor for the interrupt + /// controller, so they would return None even on success. + /// + /// # Arguments + /// + /// * `vm` - the vm object + fn create_irq_chip(_vm: &kvm::Vm) -> Result> { + // Unfortunately X86 and ARM have to do this in completely different order + // X86 needs to create the irq chip before creating cpus and + // ARM needs to do it afterwards. + Ok(None) + } + + /// This returns the first page frame number for use by the balloon driver. + /// + /// # Arguments + /// + /// * `mem_size` - the size in bytes of physical ram for the guest + fn get_base_dev_pfn(mem_size: u64) -> u64 { + // Put device memory at nearest 2MB boundary after physical memory + const MB: u64 = 1024 * 1024; + let mem_size_round_2mb = (mem_size + 2*MB - 1) / (2*MB) * (2*MB); + mem_size_round_2mb / sys_util::pagesize() as u64 + } + + /// This returns a minimal kernel command for this architecture + fn get_base_linux_cmdline() -> kernel_cmdline::Cmdline { + let mut cmdline = kernel_cmdline::Cmdline::new(CMDLINE_MAX_SIZE as usize); + cmdline.insert_str("console=ttyS0 noacpi reboot=k panic=1 pci=off"). + unwrap(); + cmdline + } + + /// This creates and returns a device_manager object for this vm. + /// + /// # Arguments + /// + /// * `vm` - the vm object + /// * `mem` - A copy of the GuestMemory object for this VM. + fn get_device_manager(vm: &mut Vm, mem: GuestMemory) -> + Result { + const MMIO_BASE: u64 = 0xd0000000; + const MMIO_LEN: u64 = 0x1000; + const IRQ_BASE: u32 = 5; + + Ok(device_manager::DeviceManager::new(vm, mem, MMIO_LEN, MMIO_BASE, IRQ_BASE)) + } + + /// Sets up the IO bus for this platform + /// + /// # Arguments + /// + /// * - `vm` the vm object + /// * - `exit_evt` - the event fd object which should receive exit events + fn setup_io_bus(vm: &mut Vm, exit_evt: EventFd) + -> Result<(devices::Bus, Arc>)> { + struct NoDevice; + impl devices::BusDevice for NoDevice {} + + let mut io_bus = devices::Bus::new(); + + let com_evt_1_3 = EventFd::new().map_err(|e| Error::CreateEventFd(e))?; + let com_evt_2_4 = EventFd::new().map_err(|e| Error::CreateEventFd(e))?; + let stdio_serial = + Arc::new(Mutex::new( + devices::Serial::new_out(com_evt_1_3.try_clone(). + map_err(|e| Error::CloneEventFd(e))?, + Box::new(stdout())))); + let nul_device = Arc::new(Mutex::new(NoDevice)); + io_bus.insert(stdio_serial.clone(), 0x3f8, 0x8).unwrap(); + io_bus.insert(Arc::new(Mutex::new( + devices::Serial::new_sink(com_evt_2_4.try_clone(). + map_err(|e| Error::CloneEventFd(e))?))), + 0x2f8, + 0x8) + .unwrap(); + io_bus.insert(Arc::new(Mutex::new( + devices::Serial::new_sink(com_evt_1_3.try_clone(). + map_err(|e| Error::CloneEventFd(e))?))), + 0x3e8, + 0x8) + .unwrap(); + io_bus.insert(Arc::new(Mutex::new( + devices::Serial::new_sink(com_evt_2_4.try_clone(). + map_err(|e| Error::CloneEventFd(e))?))), + 0x2e8, + 0x8) + .unwrap(); + io_bus.insert(Arc::new(Mutex::new(devices::Cmos::new())), 0x70, 0x2) + .unwrap(); + io_bus.insert(Arc::new(Mutex::new( + devices::I8042Device::new(exit_evt.try_clone(). + map_err(|e| Error::CloneEventFd(e))?))), + 0x061, + 0x4) + .unwrap(); + io_bus.insert(nul_device.clone(), 0x040, 0x8).unwrap(); // ignore pit + io_bus.insert(nul_device.clone(), 0x0ed, 0x1).unwrap(); // most likely this one does nothing + io_bus.insert(nul_device.clone(), 0x0f0, 0x2).unwrap(); // ignore fpu + io_bus.insert(nul_device.clone(), 0xcf8, 0x8).unwrap(); // ignore pci + + vm.register_irqfd(&com_evt_1_3, 4).map_err(Error::RegisterIrqfd)?; + vm.register_irqfd(&com_evt_2_4, 3).map_err(Error::RegisterIrqfd)?; + + Ok((io_bus, stdio_serial)) + } + + /// Configures the vcpu and should be called once per vcpu from the vcpu's thread. + /// + /// # Arguments + /// + /// * `guest_mem` - The memory to be used by the guest. + /// * `kernel_load_offset` - Offset in bytes from `guest_mem` at which the + /// kernel starts. + /// * `kvm` - The /dev/kvm object that created vcpu. + /// * `vcpu` - The VCPU object to configure. + /// * `cpu_id` - The id of the given `vcpu`. + /// * `num_cpus` - Number of virtual CPUs the guest will have. + fn configure_vcpu(guest_mem: &GuestMemory, + kvm: &Kvm, + vcpu: &Vcpu, + cpu_id: u64, + num_cpus: u64) + -> Result<()> { + let kernel_load_addr = GuestAddress(KERNEL_START_OFFSET); + cpuid::setup_cpuid(kvm, vcpu, cpu_id, num_cpus)?; + regs::setup_msrs(vcpu)?; + let kernel_end = guest_mem.checked_offset(kernel_load_addr, KERNEL_64BIT_ENTRY_OFFSET) + .ok_or(Error::KernelOffsetPastEnd)?; + regs::setup_regs(vcpu, + (kernel_end).offset() as u64, + BOOT_STACK_POINTER as u64, + ZERO_PAGE_OFFSET as u64)?; + regs::setup_fpu(vcpu)?; + regs::setup_sregs(guest_mem, vcpu)?; + interrupts::set_lint(vcpu)?; + Ok(()) + } + +} #[cfg(test)] mod tests { use super::*;