summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--aarch64/src/lib.rs19
-rw-r--r--arch/src/lib.rs7
-rw-r--r--src/linux.rs19
-rw-r--r--src/main.rs81
-rw-r--r--src/plugin/mod.rs7
-rw-r--r--x86_64/src/lib.rs157
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(&param).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(&param).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(&param).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);
+    }
 }