vmm: Add support for booting raw binary (e.g. firmware) on x86-64

If the provided binary isn't an ELF binary assume that it is a firmware
to be loaded in directly. In this case we shouldn't program any of the
registers as KVM starts in that state.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
This commit is contained in:
Rob Bradford 2021-11-29 16:44:32 +00:00 committed by Sebastien Boeuf
parent bc827ee3be
commit 82d06c0efa
2 changed files with 59 additions and 13 deletions

View File

@ -57,7 +57,7 @@ const KVM_FEATURE_STEAL_TIME_BIT: u8 = 5;
/// is to be used to configure the guest initial state. /// is to be used to configure the guest initial state.
pub struct EntryPoint { pub struct EntryPoint {
/// Address in guest memory where the guest must start execution /// Address in guest memory where the guest must start execution
pub entry_addr: GuestAddress, pub entry_addr: Option<GuestAddress>,
} }
const E820_RAM: u32 = 1; const E820_RAM: u32 = 1;
@ -741,12 +741,13 @@ pub fn configure_vcpu(
regs::setup_msrs(fd).map_err(Error::MsrsConfiguration)?; regs::setup_msrs(fd).map_err(Error::MsrsConfiguration)?;
if let Some(kernel_entry_point) = kernel_entry_point { if let Some(kernel_entry_point) = kernel_entry_point {
if let Some(entry_addr) = kernel_entry_point.entry_addr {
// Safe to unwrap because this method is called after the VM is configured // Safe to unwrap because this method is called after the VM is configured
regs::setup_regs(fd, kernel_entry_point.entry_addr.raw_value()) regs::setup_regs(fd, entry_addr.raw_value()).map_err(Error::RegsConfiguration)?;
.map_err(Error::RegsConfiguration)?;
regs::setup_fpu(fd).map_err(Error::FpuConfiguration)?; regs::setup_fpu(fd).map_err(Error::FpuConfiguration)?;
regs::setup_sregs(&vm_memory.memory(), fd).map_err(Error::SregsConfiguration)?; regs::setup_sregs(&vm_memory.memory(), fd).map_err(Error::SregsConfiguration)?;
} }
}
interrupts::set_lint(fd).map_err(|e| Error::LocalIntConfiguration(e.into()))?; interrupts::set_lint(fd).map_err(|e| Error::LocalIntConfiguration(e.into()))?;
Ok(()) Ok(())
} }

View File

@ -70,9 +70,7 @@ use std::{result, str, thread};
use vm_device::Bus; use vm_device::Bus;
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
use vm_device::BusDevice; use vm_device::BusDevice;
#[cfg(any(target_arch = "aarch64", feature = "tdx"))] use vm_memory::{Address, Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic};
use vm_memory::Address;
use vm_memory::{Bytes, GuestAddress, GuestAddressSpace, GuestMemoryAtomic};
#[cfg(feature = "tdx")] #[cfg(feature = "tdx")]
use vm_memory::{GuestMemory, GuestMemoryRegion}; use vm_memory::{GuestMemory, GuestMemoryRegion};
use vm_migration::{ use vm_migration::{
@ -238,6 +236,18 @@ pub enum Error {
/// Kernel lacks PVH header /// Kernel lacks PVH header
KernelMissingPvhHeader, KernelMissingPvhHeader,
/// Failed to allocate firmware RAM
AllocateFirmwareMemory(MemoryManagerError),
/// Error manipulating firmware file
FirmwareFile(std::io::Error),
/// Firmware too big
FirmwareTooLarge,
// Failed to copy to memory
FirmwareLoad(vm_memory::GuestMemoryError),
/// Error doing I/O on TDX firmware file /// Error doing I/O on TDX firmware file
#[cfg(feature = "tdx")] #[cfg(feature = "tdx")]
LoadTdvf(std::io::Error), LoadTdvf(std::io::Error),
@ -961,6 +971,7 @@ impl Vm {
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
fn load_kernel(&mut self) -> Result<EntryPoint> { fn load_kernel(&mut self) -> Result<EntryPoint> {
use linux_loader::loader::{elf::Error::InvalidElfMagicNumber, Error::Elf};
info!("Loading kernel"); info!("Loading kernel");
let cmdline = self.get_cmdline()?; let cmdline = self.get_cmdline()?;
let guest_memory = self.memory_manager.lock().as_ref().unwrap().guest_memory(); let guest_memory = self.memory_manager.lock().as_ref().unwrap().guest_memory();
@ -973,9 +984,41 @@ impl Vm {
Some(arch::layout::HIGH_RAM_START), Some(arch::layout::HIGH_RAM_START),
) { ) {
Ok(entry_addr) => entry_addr, Ok(entry_addr) => entry_addr,
Err(e) => { Err(e) => match e {
Elf(InvalidElfMagicNumber) => {
// Not an ELF header - assume raw binary data / firmware
let size = kernel.seek(SeekFrom::End(0)).map_err(Error::FirmwareFile)?;
// The OVMF firmware is as big as you might expect and it's 4MiB so limit to that
if size > 4 << 20 {
return Err(Error::FirmwareTooLarge);
}
// Loaded at the end of the 4GiB
let load_address = GuestAddress(4 << 30)
.checked_sub(size)
.ok_or(Error::FirmwareTooLarge)?;
self.memory_manager
.lock()
.unwrap()
.add_ram_region(load_address, size as usize)
.map_err(Error::AllocateFirmwareMemory)?;
kernel
.seek(SeekFrom::Start(0))
.map_err(Error::FirmwareFile)?;
guest_memory
.memory()
.read_exact_from(load_address, &mut kernel, size as usize)
.map_err(Error::FirmwareLoad)?;
return Ok(EntryPoint { entry_addr: None });
}
_ => {
return Err(Error::KernelLoad(e)); return Err(Error::KernelLoad(e));
} }
},
}; };
linux_loader::loader::load_cmdline(mem.deref(), arch::layout::CMDLINE_START, &cmdline) linux_loader::loader::load_cmdline(mem.deref(), arch::layout::CMDLINE_START, &cmdline)
@ -984,7 +1027,9 @@ impl Vm {
if let PvhEntryPresent(entry_addr) = entry_addr.pvh_boot_cap { if let PvhEntryPresent(entry_addr) = entry_addr.pvh_boot_cap {
// Use the PVH kernel entry point to boot the guest // Use the PVH kernel entry point to boot the guest
info!("Kernel loaded: entry_addr = 0x{:x}", entry_addr.0); info!("Kernel loaded: entry_addr = 0x{:x}", entry_addr.0);
Ok(EntryPoint { entry_addr }) Ok(EntryPoint {
entry_addr: Some(entry_addr),
})
} else { } else {
Err(Error::KernelMissingPvhHeader) Err(Error::KernelMissingPvhHeader)
} }