diff options
-rw-r--r-- | aarch64/src/lib.rs | 19 | ||||
-rw-r--r-- | arch/src/lib.rs | 7 | ||||
-rw-r--r-- | src/linux.rs | 19 | ||||
-rw-r--r-- | src/main.rs | 81 | ||||
-rw-r--r-- | src/plugin/mod.rs | 7 | ||||
-rw-r--r-- | x86_64/src/lib.rs | 157 |
6 files changed, 198 insertions, 92 deletions
diff --git a/aarch64/src/lib.rs b/aarch64/src/lib.rs index 64dfb1f..4d226c0 100644 --- a/aarch64/src/lib.rs +++ b/aarch64/src/lib.rs @@ -11,7 +11,7 @@ use std::io; use std::os::unix::io::FromRawFd; use std::sync::Arc; -use arch::{RunnableLinuxVm, VmComponents}; +use arch::{RunnableLinuxVm, VmComponents, VmImage}; use devices::{ get_serial_tty_string, Bus, BusError, PciConfigMmio, PciDevice, PciInterruptPin, SerialParameters, @@ -124,6 +124,7 @@ pub enum Error { CreateVm(sys_util::Error), InitrdLoadFailure(arch::LoadImageError), KernelLoadFailure(arch::LoadImageError), + KernelMissing, ReadPreferredTarget(sys_util::Error), RegisterIrqfd(sys_util::Error), RegisterPci(BusError), @@ -155,6 +156,7 @@ impl Display for Error { CreateVm(e) => write!(f, "failed to create vm: {}", e), InitrdLoadFailure(e) => write!(f, "initrd cound not be loaded: {}", e), KernelLoadFailure(e) => write!(f, "kernel cound not be loaded: {}", e), + KernelMissing => write!(f, "aarch64 requires a kernel"), ReadPreferredTarget(e) => write!(f, "failed to read preferred target: {}", e), RegisterIrqfd(e) => write!(f, "failed to register irq fd: {}", e), RegisterPci(e) => write!(f, "error registering PCI bus: {}", e), @@ -272,15 +274,16 @@ impl arch::LinuxArch for AArch64 { cmdline.insert_str(¶m).map_err(Error::Cmdline)?; } + let kernel_image = if let VmImage::Kernel(ref mut img) = components.vm_image { + img + } else { + return Err(Error::KernelMissing); + }; + // separate out kernel loading from other setup to get a specific error for // kernel loading - let kernel_size = arch::load_image( - &mem, - &mut components.kernel_image, - get_kernel_addr(), - u64::max_value(), - ) - .map_err(Error::KernelLoadFailure)?; + let kernel_size = arch::load_image(&mem, kernel_image, get_kernel_addr(), u64::max_value()) + .map_err(Error::KernelLoadFailure)?; let kernel_end = get_kernel_addr().offset() + kernel_size as u64; Self::setup_system_memory( &mem, diff --git a/arch/src/lib.rs b/arch/src/lib.rs index 4c5fbed..1f26cb4 100644 --- a/arch/src/lib.rs +++ b/arch/src/lib.rs @@ -24,13 +24,18 @@ use resources::SystemAllocator; use sync::Mutex; use sys_util::{syslog, EventFd, GuestAddress, GuestMemory, GuestMemoryError}; +pub enum VmImage { + Kernel(File), + Bios(File), +} + /// Holds the pieces needed to build a VM. Passed to `build_vm` in the `LinuxArch` trait below to /// create a `RunnableLinuxVm`. pub struct VmComponents { pub memory_size: u64, pub vcpu_count: u32, pub vcpu_affinity: Vec<usize>, - pub kernel_image: File, + pub vm_image: VmImage, pub android_fstab: Option<File>, pub initrd_image: Option<File>, pub extra_kernel_params: Vec<String>, diff --git a/src/linux.rs b/src/linux.rs index c29b5d1..9f158ed 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -52,9 +52,9 @@ use vm_control::{ VmMemoryControlResponseSocket, VmMemoryRequest, VmMemoryResponse, VmRunMode, }; -use crate::{Config, DiskOption, TouchDeviceOption}; +use crate::{Config, DiskOption, Executable, TouchDeviceOption}; -use arch::{self, LinuxArch, RunnableLinuxVm, VirtioDeviceStub, VmComponents}; +use arch::{self, LinuxArch, RunnableLinuxVm, VirtioDeviceStub, VmComponents, VmImage}; #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] use aarch64::AArch64 as Arch; @@ -100,6 +100,7 @@ pub enum Error { LoadKernel(Box<dyn StdError>), NetDeviceNew(virtio::NetError), OpenAndroidFstab(PathBuf, io::Error), + OpenBios(PathBuf, io::Error), OpenInitrd(PathBuf, io::Error), OpenKernel(PathBuf, io::Error), OpenVinput(PathBuf, io::Error), @@ -179,6 +180,7 @@ impl Display for Error { p.display(), e ), + OpenBios(p, e) => write!(f, "failed to open bios {}: {}", p.display(), e), OpenInitrd(p, e) => write!(f, "failed to open initrd {}: {}", p.display(), e), OpenKernel(p, e) => write!(f, "failed to open kernel image {}: {}", p.display(), e), OpenVinput(p, e) => write!(f, "failed to open vinput device {}: {}", p.display(), e), @@ -1147,12 +1149,21 @@ pub fn run_config(cfg: Config) -> Result<()> { None }; + let vm_image = match cfg.executable_path { + Some(Executable::Kernel(ref kernel_path)) => VmImage::Kernel( + File::open(kernel_path).map_err(|e| Error::OpenKernel(kernel_path.to_path_buf(), e))?, + ), + Some(Executable::Bios(ref bios_path)) => VmImage::Bios( + File::open(bios_path).map_err(|e| Error::OpenBios(bios_path.to_path_buf(), e))?, + ), + _ => panic!("Did not receive a bios or kernel, should be impossible."), + }; + let components = VmComponents { memory_size: (cfg.memory.unwrap_or(256) << 20) as u64, vcpu_count: cfg.vcpu_count.unwrap_or(1), vcpu_affinity: cfg.vcpu_affinity.clone(), - kernel_image: File::open(&cfg.kernel_path) - .map_err(|e| Error::OpenKernel(cfg.kernel_path.clone(), e))?, + vm_image, android_fstab: cfg .android_fstab .as_ref() diff --git a/src/main.rs b/src/main.rs index 4c53760..530d119 100644 --- a/src/main.rs +++ b/src/main.rs @@ -75,16 +75,29 @@ impl TouchDeviceOption { } } +#[derive(Debug)] +pub enum Executable { + Bios(PathBuf), + Kernel(PathBuf), + Plugin(PathBuf), +} + +fn executable_is_plugin(executable: &Option<Executable>) -> bool { + match executable { + Some(Executable::Plugin(_)) => true, + _ => false, + } +} + pub struct Config { vcpu_count: Option<u32>, vcpu_affinity: Vec<usize>, memory: Option<usize>, - kernel_path: PathBuf, + executable_path: Option<Executable>, android_fstab: Option<PathBuf>, initrd_path: Option<PathBuf>, params: Vec<String>, socket_path: Option<PathBuf>, - plugin: Option<PathBuf>, plugin_root: Option<PathBuf>, plugin_mounts: Vec<BindMount>, plugin_gid_maps: Vec<GidMap>, @@ -121,12 +134,11 @@ impl Default for Config { vcpu_count: None, vcpu_affinity: Vec::new(), memory: None, - kernel_path: PathBuf::default(), + executable_path: None, android_fstab: None, initrd_path: None, params: Vec::new(), socket_path: None, - plugin: None, plugin_root: None, plugin_mounts: Vec::new(), plugin_gid_maps: Vec::new(), @@ -285,24 +297,20 @@ fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> { fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::Result<()> { match name { "" => { - if cfg.plugin.is_some() { - return Err(argument::Error::TooManyArguments( - "`plugin` can not be used with kernel".to_owned(), - )); - } else if !cfg.kernel_path.as_os_str().is_empty() { - return Err(argument::Error::TooManyArguments( - "expected exactly one kernel path".to_owned(), - )); - } else { - let kernel_path = PathBuf::from(value.unwrap()); - if !kernel_path.exists() { - return Err(argument::Error::InvalidValue { - value: value.unwrap().to_owned(), - expected: "this kernel path does not exist", - }); - } - cfg.kernel_path = kernel_path; + if cfg.executable_path.is_some() { + return Err(argument::Error::TooManyArguments(format!( + "A VM executable was already specified: {:?}", + cfg.executable_path + ))); + } + let kernel_path = PathBuf::from(value.unwrap()); + if !kernel_path.exists() { + return Err(argument::Error::InvalidValue { + value: value.unwrap().to_owned(), + expected: "this kernel path does not exist", + }); } + cfg.executable_path = Some(Executable::Kernel(kernel_path)); } "android-fstab" => { if cfg.android_fstab.is_some() @@ -572,14 +580,11 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: cfg.seccomp_policy_dir = PathBuf::from(value.unwrap()); } "plugin" => { - if !cfg.kernel_path.as_os_str().is_empty() { - return Err(argument::Error::TooManyArguments( - "`plugin` can not be used with kernel".to_owned(), - )); - } else if cfg.plugin.is_some() { - return Err(argument::Error::TooManyArguments( - "`plugin` already given".to_owned(), - )); + if cfg.executable_path.is_some() { + return Err(argument::Error::TooManyArguments(format!( + "A VM executable was already specified: {:?}", + cfg.executable_path + ))); } let plugin = PathBuf::from(value.unwrap().to_owned()); if plugin.is_relative() { @@ -588,7 +593,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: expected: "the plugin path must be an absolute path", }); } - cfg.plugin = Some(plugin); + cfg.executable_path = Some(Executable::Plugin(plugin)); } "plugin-root" => { cfg.plugin_root = Some(PathBuf::from(value.unwrap().to_owned())); @@ -762,6 +767,15 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument:: "initrd" => { cfg.initrd_path = Some(PathBuf::from(value.unwrap().to_owned())); } + "bios" => { + if cfg.executable_path.is_some() { + return Err(argument::Error::TooManyArguments(format!( + "A VM executable was already specified: {:?}", + cfg.executable_path + ))); + } + cfg.executable_path = Some(Executable::Bios(PathBuf::from(value.unwrap().to_owned()))); + } "help" => return Err(argument::Error::PrintHelp), _ => unreachable!(), } @@ -844,6 +858,7 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> { Argument::value("keyboard", "PATH", "Path to a socket from where to read keyboard input events and write status updates to."), #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] Argument::flag("split-irqchip", "(EXPERIMENTAL) enable split-irqchip support"), + Argument::value("bios", "PATH", "Path to BIOS/firmware ROM"), Argument::short_flag('h', "help", "Print help message.")]; let mut cfg = Config::default(); @@ -851,7 +866,7 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> { set_argument(&mut cfg, name, value) }) .and_then(|_| { - if cfg.kernel_path.as_os_str().is_empty() && cfg.plugin.is_none() { + if cfg.executable_path.is_none() { return Err(argument::Error::ExpectedArgument("`KERNEL`".to_owned())); } if cfg.host_ip.is_some() || cfg.netmask.is_some() || cfg.mac_address.is_some() { @@ -871,7 +886,7 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> { )); } } - if cfg.plugin_root.is_some() && cfg.plugin.is_none() { + if cfg.plugin_root.is_some() && !executable_is_plugin(&cfg.executable_path) { return Err(argument::Error::ExpectedArgument( "`plugin-root` requires `plugin`".to_owned(), )); @@ -881,7 +896,7 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> { match match_res { #[cfg(feature = "plugin")] - Ok(()) if cfg.plugin.is_some() => match plugin::run_config(cfg) { + Ok(()) if executable_is_plugin(&cfg.executable_path) => match plugin::run_config(cfg) { Ok(_) => { info!("crosvm and plugin have exited normally"); Ok(()) diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs index 6fbb9e7..1cb3e57 100644 --- a/src/plugin/mod.rs +++ b/src/plugin/mod.rs @@ -38,7 +38,7 @@ use sys_util::{ use self::process::*; use self::vcpu::*; -use crate::Config; +use crate::{Config, Executable}; const MAX_DATAGRAM_SIZE: usize = 4096; const MAX_VCPU_DATAGRAM_SIZE: usize = 0x40000; @@ -598,7 +598,10 @@ pub fn run_config(cfg: Config) -> Result<()> { let plugin_args: Vec<&str> = cfg.params.iter().map(|s| &s[..]).collect(); - let plugin_path = cfg.plugin.as_ref().unwrap().as_path(); + let plugin_path = match cfg.executable_path { + Some(Executable::Plugin(ref plugin_path)) => plugin_path.as_path(), + _ => panic!("Executable was not a plugin"), + }; let vcpu_count = cfg.vcpu_count.unwrap_or(1); let mem = GuestMemory::new(&[]).unwrap(); let kvm = Kvm::new().map_err(Error::CreateKvm)?; diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index ab5e3a8..837dac4 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -48,12 +48,12 @@ use std::error::Error as StdError; use std::ffi::{CStr, CString}; use std::fmt::{self, Display}; use std::fs::File; -use std::io; +use std::io::{self, Seek}; use std::mem; use std::sync::Arc; use crate::bootparam::boot_params; -use arch::{RunnableLinuxVm, VmComponents}; +use arch::{RunnableLinuxVm, VmComponents, VmImage}; use devices::{get_serial_tty_string, PciConfigIo, PciDevice, PciInterruptPin, SerialParameters}; use io_jail::Minijail; use kvm::*; @@ -82,6 +82,7 @@ pub enum Error { CreateVm(sys_util::Error), E820Configuration, KernelOffsetPastEnd, + LoadBios(io::Error), LoadBzImage(bzimage::Error), LoadCmdline(kernel_loader::Error), LoadInitrd(arch::LoadImageError), @@ -126,6 +127,7 @@ impl Display for Error { CreateVm(e) => write!(f, "failed to create VM: {}", e), E820Configuration => write!(f, "invalid e820 setup params"), KernelOffsetPastEnd => write!(f, "the kernel extends past the end of RAM"), + LoadBios(e) => write!(f, "error loading bios: {}", e), LoadBzImage(e) => write!(f, "error loading kernel bzImage: {}", e), LoadCmdline(e) => write!(f, "error loading command line: {}", e), LoadInitrd(e) => write!(f, "error loading initrd: {}", e), @@ -159,6 +161,11 @@ const MEM_32BIT_GAP_SIZE: u64 = (768 << 20); const FIRST_ADDR_PAST_32BITS: u64 = (1 << 32); const KERNEL_64BIT_ENTRY_OFFSET: u64 = 0x200; const ZERO_PAGE_OFFSET: u64 = 0x7000; +/// The x86 reset vector for i386+ and x86_64 puts the processor into an "unreal mode" where it +/// can access the last 1 MB of the 32-bit address space in 16-bit mode, and starts the instruction +/// pointer at the effective physical address 0xFFFFFFF0. +const BIOS_LEN: usize = 1 << 20; +const BIOS_START: u64 = FIRST_ADDR_PAST_32BITS - (BIOS_LEN as u64); const KERNEL_START_OFFSET: u64 = 0x200000; const CMDLINE_OFFSET: u64 = 0x20000; @@ -259,10 +266,10 @@ fn add_e820_entry(params: &mut boot_params, addr: u64, size: u64, mem_type: u32) } /// 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 +/// These should be used to configure the GuestMemory structure for the platform. +/// For x86_64 all addresses are valid from the start of the kernel except a /// carve out at the end of 32bit address space. -fn arch_memory_regions(size: u64) -> Vec<(GuestAddress, u64)> { +fn arch_memory_regions(size: u64, has_bios: bool) -> 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); @@ -270,13 +277,20 @@ fn arch_memory_regions(size: u64) -> Vec<(GuestAddress, u64)> { let mut regions = Vec::new(); if mem_end < end_32bit_gap_start { regions.push((GuestAddress(0), size)); + if has_bios { + regions.push((GuestAddress(BIOS_START), BIOS_LEN as u64)); + } } 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), - )); + let region_start = if has_bios { + GuestAddress(BIOS_START) + } else { + first_addr_past_32bits + }; + regions.push((region_start, mem_end.offset_from(first_addr_past_32bits))); + } else if has_bios { + regions.push((GuestAddress(BIOS_START), BIOS_LEN as u64)); } } @@ -301,7 +315,11 @@ impl arch::LinuxArch for X8664arch { { let mut resources = Self::get_resource_allocator(components.memory_size, components.wayland_dmabuf); - let mem = Self::setup_memory(components.memory_size)?; + let has_bios = match components.vm_image { + VmImage::Bios(_) => true, + _ => false, + }; + let mem = Self::setup_memory(components.memory_size, has_bios)?; let kvm = Kvm::new().map_err(Error::CreateKvm)?; let mut vm = Self::create_vm(&kvm, split_irqchip, mem.clone())?; @@ -309,14 +327,16 @@ impl arch::LinuxArch for X8664arch { let mut vcpus = Vec::with_capacity(vcpu_count as usize); for cpu_id in 0..vcpu_count { let vcpu = Vcpu::new(cpu_id as libc::c_ulong, &kvm, &vm).map_err(Error::CreateVcpu)?; - Self::configure_vcpu( - vm.get_memory(), - &kvm, - &vm, - &vcpu, - cpu_id as u64, - vcpu_count as u64, - )?; + if let VmImage::Kernel(_) = components.vm_image { + Self::configure_vcpu( + vm.get_memory(), + &kvm, + &vm, + &vcpu, + cpu_id as u64, + vcpu_count as u64, + )?; + } vcpus.push(vcpu); } @@ -346,27 +366,31 @@ impl arch::LinuxArch for X8664arch { let (stdio_serial_num, stdio_serial) = Self::setup_serial_devices(&mut vm, &mut io_bus, &serial_parameters)?; - let mut cmdline = Self::get_base_linux_cmdline(stdio_serial_num); - for param in components.extra_kernel_params { - cmdline.insert_str(¶m).map_err(Error::Cmdline)?; - } - - // separate out load_kernel from other setup to get a specific error for - // kernel loading - let (params, kernel_end) = Self::load_kernel(&mem, &mut components.kernel_image)?; - - Self::setup_system_memory( - &mem, - components.memory_size, - vcpu_count, - &CString::new(cmdline).unwrap(), - components.initrd_image, - pci_irqs, - components.android_fstab, - kernel_end, - params, - )?; + match components.vm_image { + VmImage::Bios(ref mut bios) => Self::load_bios(&mem, bios)?, + VmImage::Kernel(ref mut kernel_image) => { + let mut cmdline = Self::get_base_linux_cmdline(stdio_serial_num); + for param in components.extra_kernel_params { + cmdline.insert_str(¶m).map_err(Error::Cmdline)?; + } + // separate out load_kernel from other setup to get a specific error for + // kernel loading + let (params, kernel_end) = Self::load_kernel(&mem, kernel_image)?; + + Self::setup_system_memory( + &mem, + components.memory_size, + vcpu_count, + &CString::new(cmdline).unwrap(), + components.initrd_image, + pci_irqs, + components.android_fstab, + kernel_end, + params, + )?; + } + } Ok(RunnableLinuxVm { vm, kvm, @@ -384,6 +408,33 @@ impl arch::LinuxArch for X8664arch { } impl X8664arch { + /// Loads the bios from an open file. + /// + /// # Arguments + /// + /// * `mem` - The memory to be used by the guest. + /// * `bios_image` - the File object for the specified bios + fn load_bios(mem: &GuestMemory, bios_image: &mut File) -> Result<()> { + let bios_image_length = bios_image + .seek(io::SeekFrom::End(0)) + .map_err(Error::LoadBios)?; + if bios_image_length != BIOS_LEN as u64 { + return Err(Error::LoadBios(io::Error::new( + io::ErrorKind::InvalidData, + format!( + "bios was {} bytes, expected {}", + bios_image_length, BIOS_LEN + ), + ))); + } + bios_image + .seek(io::SeekFrom::Start(0)) + .map_err(Error::LoadBios)?; + mem.read_to_memory(GuestAddress(BIOS_START), bios_image, BIOS_LEN) + .map_err(Error::SetupGuestMemory)?; + Ok(()) + } + /// Loads the kernel from an open file. /// /// # Arguments @@ -507,8 +558,8 @@ impl X8664arch { /// 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<GuestMemory> { - let arch_mem_regions = arch_memory_regions(mem_size); + fn setup_memory(mem_size: u64, has_bios: bool) -> Result<GuestMemory> { + let arch_mem_regions = arch_memory_regions(mem_size, has_bios); let mem = GuestMemory::new(&arch_mem_regions).map_err(Error::SetupGuestMemory)?; Ok(mem) } @@ -722,18 +773,36 @@ mod tests { use super::*; #[test] - fn regions_lt_4gb() { - let regions = arch_memory_regions(1u64 << 29); + fn regions_lt_4gb_nobios() { + let regions = arch_memory_regions(1u64 << 29, /* has_bios */ false); assert_eq!(1, regions.len()); assert_eq!(GuestAddress(0), regions[0].0); assert_eq!(1u64 << 29, regions[0].1); } #[test] - fn regions_gt_4gb() { - let regions = arch_memory_regions((1u64 << 32) + 0x8000); + fn regions_gt_4gb_nobios() { + let regions = arch_memory_regions((1u64 << 32) + 0x8000, /* has_bios */ false); assert_eq!(2, regions.len()); assert_eq!(GuestAddress(0), regions[0].0); assert_eq!(GuestAddress(1u64 << 32), regions[1].0); } + + #[test] + fn regions_lt_4gb_bios() { + let regions = arch_memory_regions(1u64 << 29, /* has_bios */ true); + assert_eq!(2, regions.len()); + assert_eq!(GuestAddress(0), regions[0].0); + assert_eq!(1u64 << 29, regions[0].1); + assert_eq!(GuestAddress(BIOS_START), regions[1].0); + assert_eq!(BIOS_LEN as u64, regions[1].1); + } + + #[test] + fn regions_gt_4gb_bios() { + let regions = arch_memory_regions((1u64 << 32) + 0x8000, /* has_bios */ true); + assert_eq!(2, regions.len()); + assert_eq!(GuestAddress(0), regions[0].0); + assert_eq!(GuestAddress(BIOS_START), regions[1].0); + } } |