Initial BIOS support.

The --bios argument is added as an alternative to the kernel positional
argument. The BIOS runs in unreal mode (16-bit cs selector set to the
end of 32-bit address space), which matches the default state KVM puts
the segment and data registers into.

Example usage:
Build u-boot with "make qemu-x86_defconfig && make"
Run crosvm with "crosvm_wrapper.sh run --bios=u-boot.rom"

This produces the following message:
"""
U-Boot 2019.01-00017-gdc76aabe6a-dirty (May 21 2019 - 12:17:02 -0700)

CPU:
DRAM:  16 MiB
unable to get online cpu number: -19
Warning: MP init failure
Model: QEMU x86 (I440FX)
Net:   No ethernet found.
error: can't find etc/table-loader
Hit any key to stop autoboot:  0
=>
"""

At this point the u-boot shell works with stdin/stdout, but virtual
disks passed with --rwdisk weren't immediately visible from running
"virtio scan" and "virtio info".

This change puts the bios loading together with the linux kernel loading
code since there is a lot of overlap in functionality.

Bug: b/133358982
Test: ./crosvm_wrapper.sh run --mem=4097 --bios=u-boot.rom
Change-Id: I65b0e1044233af662a642c592d35b106217f3c13
Reviewed-on: https://chromium-review.googlesource.com/1622648
Commit-Ready: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
Legacy-Commit-Queue: Commit Bot <commit-bot@chromium.org>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
This commit is contained in:
Cody Schuffelen 2019-05-21 12:12:38 -07:00 committed by chrome-bot
parent 580d418656
commit 6d1ab50943
6 changed files with 198 additions and 92 deletions

View file

@ -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,

View file

@ -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>,

View file

@ -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()

View file

@ -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(())

View file

@ -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)?;

View file

@ -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)?;
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,
)?;
}
}
// 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,
)?;
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);
}
}