arch, vmm: Remove support for LinuxBoot

By supporting just PVH boot on x86-64 we simplify our boot path
substatially.

Fixes: #2231

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
This commit is contained in:
Rob Bradford 2021-04-29 17:11:32 +01:00 committed by Sebastien Boeuf
parent 1f4a34ffc8
commit da8136e49d
6 changed files with 62 additions and 568 deletions

View File

@ -98,8 +98,8 @@ pub mod x86_64;
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
pub use x86_64::{ pub use x86_64::{
arch_memory_regions, configure_system, configure_vcpu, get_host_cpu_phys_bits, arch_memory_regions, configure_system, configure_vcpu, get_host_cpu_phys_bits,
initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs, initramfs_load_addr, layout, layout::CMDLINE_MAX_SIZE, layout::CMDLINE_START, regs, CpuidPatch,
BootProtocol, CpuidPatch, CpuidReg, EntryPoint, CpuidReg, EntryPoint,
}; };
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`. /// Safe wrapper for `sysconf(_SC_PAGESIZE)`.

View File

@ -14,7 +14,7 @@ pub mod regs;
use crate::InitramfsConfig; use crate::InitramfsConfig;
use crate::RegionType; use crate::RegionType;
use hypervisor::{CpuId, CpuIdEntry, CPUID_FLAG_VALID_INDEX}; use hypervisor::{CpuId, CpuIdEntry, CPUID_FLAG_VALID_INDEX};
use linux_loader::loader::bootparam::{boot_params, setup_header}; use linux_loader::loader::bootparam::boot_params;
use linux_loader::loader::elf::start_info::{ use linux_loader::loader::elf::start_info::{
hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info, hvm_memmap_table_entry, hvm_modlist_entry, hvm_start_info,
}; };
@ -28,21 +28,6 @@ use std::arch::x86_64;
#[cfg(feature = "tdx")] #[cfg(feature = "tdx")]
pub mod tdx; pub mod tdx;
#[derive(Debug, Copy, Clone)]
pub enum BootProtocol {
LinuxBoot,
PvhBoot,
}
impl ::std::fmt::Display for BootProtocol {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match self {
BootProtocol::LinuxBoot => write!(f, "Linux 64-bit boot protocol"),
BootProtocol::PvhBoot => write!(f, "PVH boot protocol"),
}
}
}
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
/// Specifies the entry point address where the guest must start /// Specifies the entry point address where the guest must start
/// executing code, as well as which of the supported boot protocols /// executing code, as well as which of the supported boot protocols
@ -50,10 +35,6 @@ impl ::std::fmt::Display for BootProtocol {
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: GuestAddress,
/// Specifies which boot protocol to use
pub protocol: BootProtocol,
/// This field is used for bzImage to fill zero page
pub setup_header: Option<setup_header>,
} }
const E820_RAM: u32 = 1; const E820_RAM: u32 = 1;
@ -351,17 +332,10 @@ 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 {
// 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( regs::setup_regs(fd, kernel_entry_point.entry_addr.raw_value())
fd, .map_err(Error::RegsConfiguration)?;
kernel_entry_point.entry_addr.raw_value(),
layout::BOOT_STACK_POINTER.raw_value(),
layout::ZERO_PAGE_START.raw_value(),
kernel_entry_point.protocol,
)
.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, kernel_entry_point.protocol) regs::setup_sregs(&vm_memory.memory(), fd).map_err(Error::SregsConfiguration)?;
.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(())
@ -426,12 +400,9 @@ pub fn arch_memory_regions(size: GuestUsize) -> Vec<(GuestAddress, usize, Region
pub fn configure_system( pub fn configure_system(
guest_mem: &GuestMemoryMmap, guest_mem: &GuestMemoryMmap,
cmdline_addr: GuestAddress, cmdline_addr: GuestAddress,
cmdline_size: usize,
initramfs: &Option<InitramfsConfig>, initramfs: &Option<InitramfsConfig>,
_num_cpus: u8, _num_cpus: u8,
setup_hdr: Option<setup_header>,
rsdp_addr: Option<GuestAddress>, rsdp_addr: Option<GuestAddress>,
boot_prot: BootProtocol,
sgx_epc_region: Option<SgxEpcRegion>, sgx_epc_region: Option<SgxEpcRegion>,
) -> super::Result<()> { ) -> super::Result<()> {
let size = smbios::setup_smbios(guest_mem).map_err(Error::SmbiosSetup)?; let size = smbios::setup_smbios(guest_mem).map_err(Error::SmbiosSetup)?;
@ -448,31 +419,13 @@ pub fn configure_system(
} }
} }
match boot_prot { configure_pvh(
BootProtocol::PvhBoot => { guest_mem,
configure_pvh( cmdline_addr,
guest_mem, initramfs,
cmdline_addr, rsdp_addr,
initramfs, sgx_epc_region,
rsdp_addr, )
sgx_epc_region,
)?;
}
BootProtocol::LinuxBoot => {
error!("Using deprecated LinuxBoot protocol: Please configure your kernel with CONFIG_PVH=y and supply the `vmlinux` file to `--kernel`");
configure_64bit_boot(
guest_mem,
cmdline_addr,
cmdline_size,
initramfs,
setup_hdr,
rsdp_addr,
sgx_epc_region,
)?;
}
}
Ok(())
} }
fn configure_pvh( fn configure_pvh(
@ -615,121 +568,6 @@ fn add_memmap_entry(memmap: &mut Vec<hvm_memmap_table_entry>, addr: u64, size: u
}); });
} }
fn configure_64bit_boot(
guest_mem: &GuestMemoryMmap,
cmdline_addr: GuestAddress,
cmdline_size: usize,
initramfs: &Option<InitramfsConfig>,
setup_hdr: Option<setup_header>,
rsdp_addr: Option<GuestAddress>,
sgx_epc_region: Option<SgxEpcRegion>,
) -> super::Result<()> {
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
const KERNEL_HDR_MAGIC: u32 = 0x53726448;
const KERNEL_LOADER_OTHER: u8 = 0xff;
const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; // Must be non-zero.
let mut params: BootParamsWrapper = BootParamsWrapper(boot_params::default());
if let Some(hdr) = setup_hdr {
// We should use the header if the loader provides one (e.g. from a bzImage).
params.0.hdr = hdr;
} else {
params.0.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC;
params.0.hdr.header = KERNEL_HDR_MAGIC;
params.0.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES;
};
// Common bootparams settings
if params.0.hdr.type_of_loader == 0 {
params.0.hdr.type_of_loader = KERNEL_LOADER_OTHER;
}
params.0.hdr.cmd_line_ptr = cmdline_addr.raw_value() as u32;
params.0.hdr.cmdline_size = cmdline_size as u32;
if let Some(initramfs_config) = initramfs {
params.0.hdr.ramdisk_image = initramfs_config.address.raw_value() as u32;
params.0.hdr.ramdisk_size = initramfs_config.size as u32;
}
add_e820_entry(&mut params.0, 0, layout::EBDA_START.raw_value(), E820_RAM)?;
let mem_end = guest_mem.last_addr();
if mem_end < layout::MEM_32BIT_RESERVED_START {
add_e820_entry(
&mut params.0,
layout::HIGH_RAM_START.raw_value(),
mem_end.unchecked_offset_from(layout::HIGH_RAM_START) + 1,
E820_RAM,
)?;
} else {
add_e820_entry(
&mut params.0,
layout::HIGH_RAM_START.raw_value(),
layout::MEM_32BIT_RESERVED_START.unchecked_offset_from(layout::HIGH_RAM_START),
E820_RAM,
)?;
if mem_end > layout::RAM_64BIT_START {
add_e820_entry(
&mut params.0,
layout::RAM_64BIT_START.raw_value(),
mem_end.unchecked_offset_from(layout::RAM_64BIT_START) + 1,
E820_RAM,
)?;
}
}
add_e820_entry(
&mut params.0,
layout::PCI_MMCONFIG_START.0,
layout::PCI_MMCONFIG_SIZE,
E820_RESERVED,
)?;
if let Some(sgx_epc_region) = sgx_epc_region {
add_e820_entry(
&mut params.0,
sgx_epc_region.start().raw_value(),
sgx_epc_region.size() as u64,
E820_RESERVED,
)?;
}
if let Some(rsdp_addr) = rsdp_addr {
params.0.acpi_rsdp_addr = rsdp_addr.0;
}
let zero_page_addr = layout::ZERO_PAGE_START;
guest_mem
.checked_offset(zero_page_addr, mem::size_of::<boot_params>())
.ok_or(super::Error::ZeroPagePastRamEnd)?;
guest_mem
.write_obj(params, zero_page_addr)
.map_err(super::Error::ZeroPageSetup)?;
Ok(())
}
/// Add an e820 region to the e820 map.
/// 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<(), Error> {
if params.e820_entries >= params.e820_table.len() as u8 {
return Err(Error::E820Configuration);
}
params.e820_table[params.e820_entries as usize].addr = addr;
params.e820_table[params.e820_entries as usize].size = size;
params.e820_table[params.e820_entries as usize].type_ = mem_type;
params.e820_entries += 1;
Ok(())
}
/// Returns the memory address where the initramfs could be loaded. /// Returns the memory address where the initramfs could be loaded.
pub fn initramfs_load_addr( pub fn initramfs_load_addr(
guest_mem: &GuestMemoryMmap, guest_mem: &GuestMemoryMmap,
@ -888,7 +726,6 @@ pub fn update_cpuid_sgx(cpuid: &mut CpuId, epc_sections: Vec<SgxEpcSection>) ->
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use linux_loader::loader::bootparam::boot_e820_entry;
#[test] #[test]
fn regions_lt_4gb() { fn regions_lt_4gb() {
@ -913,12 +750,9 @@ mod tests {
let config_err = configure_system( let config_err = configure_system(
&gm, &gm,
GuestAddress(0), GuestAddress(0),
0,
&None, &None,
1, 1,
None,
Some(layout::RSDP_POINTER), Some(layout::RSDP_POINTER),
BootProtocol::LinuxBoot,
None, None,
); );
assert!(config_err.is_err()); assert!(config_err.is_err());
@ -932,31 +766,8 @@ mod tests {
.map(|r| (r.0, r.1)) .map(|r| (r.0, r.1))
.collect(); .collect();
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap(); let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
configure_system(
&gm,
GuestAddress(0),
0,
&None,
no_vcpus,
None,
None,
BootProtocol::LinuxBoot,
None,
)
.unwrap();
configure_system( configure_system(&gm, GuestAddress(0), &None, no_vcpus, None, None).unwrap();
&gm,
GuestAddress(0),
0,
&None,
no_vcpus,
None,
None,
BootProtocol::PvhBoot,
None,
)
.unwrap();
// Now assigning some memory that is equal to the start of the 32bit memory hole. // Now assigning some memory that is equal to the start of the 32bit memory hole.
let mem_size = 3328 << 20; let mem_size = 3328 << 20;
@ -967,31 +778,9 @@ mod tests {
.map(|r| (r.0, r.1)) .map(|r| (r.0, r.1))
.collect(); .collect();
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap(); let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
configure_system( configure_system(&gm, GuestAddress(0), &None, no_vcpus, None, None).unwrap();
&gm,
GuestAddress(0),
0,
&None,
no_vcpus,
None,
None,
BootProtocol::LinuxBoot,
None,
)
.unwrap();
configure_system( configure_system(&gm, GuestAddress(0), &None, no_vcpus, None, None).unwrap();
&gm,
GuestAddress(0),
0,
&None,
no_vcpus,
None,
None,
BootProtocol::PvhBoot,
None,
)
.unwrap();
// Now assigning some memory that falls after the 32bit memory hole. // Now assigning some memory that falls after the 32bit memory hole.
let mem_size = 3330 << 20; let mem_size = 3330 << 20;
@ -1002,71 +791,9 @@ mod tests {
.map(|r| (r.0, r.1)) .map(|r| (r.0, r.1))
.collect(); .collect();
let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap(); let gm = GuestMemoryMmap::from_ranges(&ram_regions).unwrap();
configure_system( configure_system(&gm, GuestAddress(0), &None, no_vcpus, None, None).unwrap();
&gm,
GuestAddress(0),
0,
&None,
no_vcpus,
None,
None,
BootProtocol::LinuxBoot,
None,
)
.unwrap();
configure_system( configure_system(&gm, GuestAddress(0), &None, no_vcpus, None, None).unwrap();
&gm,
GuestAddress(0),
0,
&None,
no_vcpus,
None,
None,
BootProtocol::PvhBoot,
None,
)
.unwrap();
}
#[test]
fn test_add_e820_entry() {
let e820_table = [(boot_e820_entry {
addr: 0x1,
size: 4,
type_: 1,
}); 128];
let expected_params = boot_params {
e820_table,
e820_entries: 1,
..Default::default()
};
let mut params: boot_params = Default::default();
add_e820_entry(
&mut params,
e820_table[0].addr,
e820_table[0].size,
e820_table[0].type_,
)
.unwrap();
assert_eq!(
format!("{:?}", params.e820_table[0]),
format!("{:?}", expected_params.e820_table[0])
);
assert_eq!(params.e820_entries, expected_params.e820_entries);
// Exercise the scenario where the field storing the length of the e820 entry table is
// is bigger than the allocated memory.
params.e820_entries = params.e820_table.len() as u8 + 1;
assert!(add_e820_entry(
&mut params,
e820_table[0].addr,
e820_table[0].size,
e820_table[0].type_
)
.is_err());
} }
#[test] #[test]

View File

@ -9,13 +9,10 @@
use std::sync::Arc; use std::sync::Arc;
use std::{mem, result}; use std::{mem, result};
use super::BootProtocol;
use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt}; use hypervisor::arch::x86::gdt::{gdt_entry, segment_from_gdt};
use hypervisor::arch::x86::regs::*; use hypervisor::arch::x86::regs::*;
use hypervisor::x86_64::{FpuState, SpecialRegisters, StandardRegisters}; use hypervisor::x86_64::{FpuState, SpecialRegisters, StandardRegisters};
use layout::{ use layout::{BOOT_GDT_START, BOOT_IDT_START, PVH_INFO_START};
BOOT_GDT_START, BOOT_IDT_START, PDE_START, PDPTE_START, PML4_START, PML5_START, PVH_INFO_START,
};
use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap}; use vm_memory::{Address, Bytes, GuestMemory, GuestMemoryError, GuestMemoryMmap};
#[derive(Debug)] #[derive(Debug)]
@ -81,32 +78,12 @@ pub fn setup_msrs(vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
/// ///
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd. /// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
/// * `boot_ip` - Starting instruction pointer. /// * `boot_ip` - Starting instruction pointer.
/// * `boot_sp` - Starting stack pointer. pub fn setup_regs(vcpu: &Arc<dyn hypervisor::Vcpu>, boot_ip: u64) -> Result<()> {
/// * `boot_si` - Must point to zero page address per Linux ABI. let regs = StandardRegisters {
pub fn setup_regs( rflags: 0x0000000000000002u64,
vcpu: &Arc<dyn hypervisor::Vcpu>, rbx: PVH_INFO_START.raw_value(),
boot_ip: u64, rip: boot_ip,
boot_sp: u64, ..Default::default()
boot_si: u64,
boot_prot: BootProtocol,
) -> Result<()> {
let regs: StandardRegisters = match boot_prot {
// Configure regs as required by PVH boot protocol.
BootProtocol::PvhBoot => StandardRegisters {
rflags: 0x0000000000000002u64,
rbx: PVH_INFO_START.raw_value(),
rip: boot_ip,
..Default::default()
},
// Configure regs as required by Linux 64-bit boot protocol.
BootProtocol::LinuxBoot => StandardRegisters {
rflags: 0x0000000000000002u64,
rip: boot_ip,
rsp: boot_sp,
rbp: boot_sp,
rsi: boot_si,
..Default::default()
},
}; };
vcpu.set_regs(&regs).map_err(Error::SetBaseRegisters) vcpu.set_regs(&regs).map_err(Error::SetBaseRegisters)
} }
@ -117,19 +94,9 @@ pub fn setup_regs(
/// ///
/// * `mem` - The memory that will be passed to the guest. /// * `mem` - The memory that will be passed to the guest.
/// * `vcpu` - Structure for the VCPU that holds the VCPU's fd. /// * `vcpu` - Structure for the VCPU that holds the VCPU's fd.
pub fn setup_sregs( pub fn setup_sregs(mem: &GuestMemoryMmap, vcpu: &Arc<dyn hypervisor::Vcpu>) -> Result<()> {
mem: &GuestMemoryMmap,
vcpu: &Arc<dyn hypervisor::Vcpu>,
boot_prot: BootProtocol,
) -> Result<()> {
let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?; let mut sregs: SpecialRegisters = vcpu.get_sregs().map_err(Error::GetStatusRegisters)?;
configure_segments_and_sregs(mem, &mut sregs)?;
configure_segments_and_sregs(mem, &mut sregs, boot_prot)?;
if let BootProtocol::LinuxBoot = boot_prot {
setup_page_tables(mem, &mut sregs)?; // TODO(dgreid) - Can this be done once per system instead?
}
vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters) vcpu.set_sregs(&sregs).map_err(Error::SetStatusRegisters)
} }
@ -156,27 +123,15 @@ fn write_idt_value(val: u64, guest_mem: &GuestMemoryMmap) -> Result<()> {
pub fn configure_segments_and_sregs( pub fn configure_segments_and_sregs(
mem: &GuestMemoryMmap, mem: &GuestMemoryMmap,
sregs: &mut SpecialRegisters, sregs: &mut SpecialRegisters,
boot_prot: BootProtocol,
) -> Result<()> { ) -> Result<()> {
let gdt_table: [u64; BOOT_GDT_MAX as usize] = match boot_prot { let gdt_table: [u64; BOOT_GDT_MAX as usize] = {
BootProtocol::PvhBoot => { // Configure GDT entries as specified by PVH boot protocol
// Configure GDT entries as specified by PVH boot protocol [
[ gdt_entry(0, 0, 0), // NULL
gdt_entry(0, 0, 0), // NULL gdt_entry(0xc09b, 0, 0xffffffff), // CODE
gdt_entry(0xc09b, 0, 0xffffffff), // CODE gdt_entry(0xc093, 0, 0xffffffff), // DATA
gdt_entry(0xc093, 0, 0xffffffff), // DATA gdt_entry(0x008b, 0, 0x67), // TSS
gdt_entry(0x008b, 0, 0x67), // TSS ]
]
}
BootProtocol::LinuxBoot => {
// Configure GDT entries as specified by Linux 64bit boot protocol
[
gdt_entry(0, 0, 0), // NULL
gdt_entry(0xa09b, 0, 0xfffff), // CODE
gdt_entry(0xc093, 0, 0xfffff), // DATA
gdt_entry(0x808b, 0, 0xfffff), // TSS
]
}
}; };
let code_seg = segment_from_gdt(gdt_table[1], 1); let code_seg = segment_from_gdt(gdt_table[1], 1);
@ -200,51 +155,8 @@ pub fn configure_segments_and_sregs(
sregs.ss = data_seg; sregs.ss = data_seg;
sregs.tr = tss_seg; sregs.tr = tss_seg;
match boot_prot { sregs.cr0 = CR0_PE;
BootProtocol::PvhBoot => { sregs.cr4 = 0;
sregs.cr0 = CR0_PE;
sregs.cr4 = 0;
}
BootProtocol::LinuxBoot => {
/* 64-bit protected mode */
sregs.cr0 |= CR0_PE;
sregs.efer |= EFER_LME | EFER_LMA;
}
}
Ok(())
}
pub fn setup_page_tables(mem: &GuestMemoryMmap, sregs: &mut SpecialRegisters) -> Result<()> {
// Puts PML5 or PML4 right after zero page but aligned to 4k.
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
// Entry covering VA [0..256TB)
mem.write_obj(PML4_START.raw_value() | 0x03, PML5_START)
.map_err(Error::WritePml5Address)?;
sregs.cr3 = PML5_START.raw_value();
sregs.cr4 |= CR4_LA57;
} else {
sregs.cr3 = PML4_START.raw_value();
}
// Entry covering VA [0..512GB)
mem.write_obj(PDPTE_START.raw_value() | 0x03, PML4_START)
.map_err(Error::WritePml4Address)?;
// Entry covering VA [0..1GB)
mem.write_obj(PDE_START.raw_value() | 0x03, PDPTE_START)
.map_err(Error::WritePdpteAddress)?;
// 512 2MB entries together covering VA [0..1GB). Note we are assuming
// CPU supports 2MB pages (/proc/cpuinfo has 'pse'). All modern CPUs do.
for i in 0..512 {
mem.write_obj((i << 21) + 0x83u64, PDE_START.unchecked_add(i * 8))
.map_err(Error::WritePdeAddress)?;
}
sregs.cr4 |= CR4_PAE;
sregs.cr0 |= CR0_PG;
Ok(()) Ok(())
} }
@ -268,36 +180,7 @@ mod tests {
fn segments_and_sregs() { fn segments_and_sregs() {
let mut sregs: SpecialRegisters = Default::default(); let mut sregs: SpecialRegisters = Default::default();
let gm = create_guest_mem(); let gm = create_guest_mem();
configure_segments_and_sregs(&gm, &mut sregs, BootProtocol::LinuxBoot).unwrap(); configure_segments_and_sregs(&gm, &mut sregs).unwrap();
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
assert_eq!(
0xaf9b000000ffff,
read_u64(&gm, BOOT_GDT_START.unchecked_add(8))
);
assert_eq!(
0xcf93000000ffff,
read_u64(&gm, BOOT_GDT_START.unchecked_add(16))
);
assert_eq!(
0x8f8b000000ffff,
read_u64(&gm, BOOT_GDT_START.unchecked_add(24))
);
assert_eq!(0x0, read_u64(&gm, BOOT_IDT_START));
assert_eq!(0, sregs.cs.base);
assert_eq!(0xffffffff, sregs.ds.limit);
assert_eq!(0x10, sregs.es.selector);
assert_eq!(1, sregs.fs.present);
assert_eq!(1, sregs.gs.g);
assert_eq!(0, sregs.ss.avl);
assert_eq!(0, sregs.tr.base);
assert_eq!(0xffffffff, sregs.tr.limit);
assert_eq!(0, sregs.tr.avl);
assert_eq!(CR0_PE, sregs.cr0);
assert_eq!(EFER_LME | EFER_LMA, sregs.efer);
configure_segments_and_sregs(&gm, &mut sregs, BootProtocol::PvhBoot).unwrap();
assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START)); assert_eq!(0x0, read_u64(&gm, BOOT_GDT_START));
assert_eq!( assert_eq!(
0xcf9b000000ffff, 0xcf9b000000ffff,
@ -327,31 +210,4 @@ mod tests {
assert_eq!(CR0_PE, sregs.cr0); assert_eq!(CR0_PE, sregs.cr0);
assert_eq!(0, sregs.cr4); assert_eq!(0, sregs.cr4);
} }
#[test]
fn page_tables() {
let mut sregs: SpecialRegisters = Default::default();
let gm = create_guest_mem();
setup_page_tables(&gm, &mut sregs).unwrap();
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
assert_eq!(0xa003, read_u64(&gm, PML5_START));
}
assert_eq!(0xb003, read_u64(&gm, PML4_START));
assert_eq!(0xc003, read_u64(&gm, PDPTE_START));
for i in 0..512 {
assert_eq!(
(i << 21) + 0x83u64,
read_u64(&gm, PDE_START.unchecked_add(i * 8))
);
}
if unsafe { std::arch::x86_64::__cpuid(7).ecx } & (1 << 16) != 0 {
assert_eq!(PML5_START.raw_value(), sregs.cr3);
} else {
assert_eq!(PML4_START.raw_value(), sregs.cr3);
}
assert_eq!(CR4_PAE, sregs.cr4);
assert_eq!(CR0_PG, sregs.cr0);
}
} }

View File

@ -194,7 +194,7 @@ fn create_app<'a, 'b>(
.long("kernel") .long("kernel")
.help( .help(
"Path to loaded kernel. This may be a kernel or firmware that supports a PVH \ "Path to loaded kernel. This may be a kernel or firmware that supports a PVH \
entry point, a vmlinux ELF file or a Linux bzImage or achitecture equivalent", entry point (e.g. vmlinux) or architecture equivalent",
) )
.takes_value(true) .takes_value(true)
.group("vm-config"), .group("vm-config"),

View File

@ -1558,13 +1558,9 @@ impl Migratable for CpuManager {}
#[cfg(all(feature = "kvm", target_arch = "x86_64"))] #[cfg(all(feature = "kvm", target_arch = "x86_64"))]
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use arch::x86_64::interrupts::*; use arch::x86_64::interrupts::*;
use arch::x86_64::regs::*; use arch::x86_64::regs::*;
use arch::x86_64::BootProtocol; use hypervisor::x86_64::{FpuState, LapicState, StandardRegisters};
use hypervisor::x86_64::{FpuState, LapicState, SpecialRegisters, StandardRegisters};
use vm_memory::GuestAddress;
#[test] #[test]
fn test_setlint() { fn test_setlint() {
@ -1652,41 +1648,16 @@ mod tests {
let expected_regs: StandardRegisters = StandardRegisters { let expected_regs: StandardRegisters = StandardRegisters {
rflags: 0x0000000000000002u64, rflags: 0x0000000000000002u64,
rbx: arch::layout::PVH_INFO_START.0,
rip: 1, rip: 1,
rsp: 2,
rbp: 2,
rsi: 3,
..Default::default() ..Default::default()
}; };
setup_regs( setup_regs(&vcpu, expected_regs.rip).unwrap();
&vcpu,
expected_regs.rip,
expected_regs.rsp,
expected_regs.rsi,
BootProtocol::LinuxBoot,
)
.unwrap();
let actual_regs: StandardRegisters = vcpu.get_regs().unwrap(); let actual_regs: StandardRegisters = vcpu.get_regs().unwrap();
assert_eq!(actual_regs, expected_regs); assert_eq!(actual_regs, expected_regs);
} }
#[test]
fn test_setup_sregs() {
let hv = hypervisor::new().unwrap();
let vm = hv.create_vm().expect("new VM fd creation failed");
let vcpu = vm.create_vcpu(0, None).unwrap();
let mut expected_sregs: SpecialRegisters = vcpu.get_sregs().unwrap();
let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap();
configure_segments_and_sregs(&gm, &mut expected_sregs, BootProtocol::LinuxBoot).unwrap();
setup_page_tables(&gm, &mut expected_sregs).unwrap();
setup_sregs(&gm, &vcpu, BootProtocol::LinuxBoot).unwrap();
let actual_sregs: SpecialRegisters = vcpu.get_sregs().unwrap();
assert_eq!(expected_sregs, actual_sregs);
}
} }
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]

View File

@ -41,15 +41,11 @@ use crate::{
}; };
use anyhow::anyhow; use anyhow::anyhow;
use arch::get_host_cpu_phys_bits; use arch::get_host_cpu_phys_bits;
#[cfg(target_arch = "x86_64")]
use arch::BootProtocol;
use arch::EntryPoint; use arch::EntryPoint;
use devices::AcpiNotificationFlags; use devices::AcpiNotificationFlags;
use hypervisor::vm::{HypervisorVmError, VmmOps}; use hypervisor::vm::{HypervisorVmError, VmmOps};
use linux_loader::cmdline::Cmdline; use linux_loader::cmdline::Cmdline;
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
use linux_loader::loader::elf::Error::InvalidElfMagicNumber;
#[cfg(target_arch = "x86_64")]
use linux_loader::loader::elf::PvhBootCapability::PvhEntryPresent; use linux_loader::loader::elf::PvhBootCapability::PvhEntryPresent;
use linux_loader::loader::KernelLoader; use linux_loader::loader::KernelLoader;
use seccomp::{SeccompAction, SeccompFilter}; use seccomp::{SeccompAction, SeccompFilter};
@ -89,10 +85,6 @@ use arch::aarch64::gic::gicv3::kvm::{KvmGicV3, GIC_V3_SNAPSHOT_ID};
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
use arch::aarch64::gic::kvm::create_gic; use arch::aarch64::gic::kvm::create_gic;
// 64 bit direct boot entry offset for bzImage
#[cfg(target_arch = "x86_64")]
const KERNEL_64BIT_ENTRY_OFFSET: u64 = 0x200;
/// Errors associated with VM management /// Errors associated with VM management
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
@ -245,6 +237,9 @@ pub enum Error {
/// Error triggering power button /// Error triggering power button
PowerButton(device_manager::DeviceManagerError), PowerButton(device_manager::DeviceManagerError),
/// Kernel lacks PVH header
KernelMissingPvhHeader,
/// 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),
@ -924,15 +919,6 @@ 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(linux_loader::loader::Error::Elf(InvalidElfMagicNumber)) => {
linux_loader::loader::bzimage::BzImage::load(
mem.deref(),
None,
&mut kernel,
Some(arch::layout::HIGH_RAM_START),
)
.map_err(Error::KernelLoad)?
}
Err(e) => { Err(e) => {
return Err(Error::KernelLoad(e)); return Err(Error::KernelLoad(e));
} }
@ -945,43 +931,20 @@ impl Vm {
) )
.map_err(Error::LoadCmdLine)?; .map_err(Error::LoadCmdLine)?;
if entry_addr.setup_header.is_some() { if let PvhEntryPresent(pvh_entry_addr) = entry_addr.pvh_boot_cap {
let load_addr = entry_addr // Use the PVH kernel entry point to boot the guest
.kernel_load
.raw_value()
.checked_add(KERNEL_64BIT_ENTRY_OFFSET)
.ok_or(Error::MemOverflow)?;
Ok(EntryPoint {
entry_addr: GuestAddress(load_addr),
protocol: BootProtocol::LinuxBoot,
setup_header: entry_addr.setup_header,
})
} else {
let entry_point_addr: GuestAddress; let entry_point_addr: GuestAddress;
let boot_prot: BootProtocol; entry_point_addr = pvh_entry_addr;
if let PvhEntryPresent(pvh_entry_addr) = entry_addr.pvh_boot_cap {
// Use the PVH kernel entry point to boot the guest
entry_point_addr = pvh_entry_addr;
boot_prot = BootProtocol::PvhBoot;
} else {
// Use the Linux 64-bit boot protocol
entry_point_addr = entry_addr.kernel_load;
boot_prot = BootProtocol::LinuxBoot;
}
Ok(EntryPoint { Ok(EntryPoint {
entry_addr: entry_point_addr, entry_addr: entry_point_addr,
protocol: boot_prot,
setup_header: None,
}) })
} else {
Err(Error::KernelMissingPvhHeader)
} }
} }
#[cfg(target_arch = "x86_64")] #[cfg(target_arch = "x86_64")]
fn configure_system(&mut self, entry_addr: EntryPoint) -> Result<()> { fn configure_system(&mut self) -> Result<()> {
let cmdline_cstring = self.get_cmdline()?;
let mem = self.memory_manager.lock().unwrap().boot_guest_memory(); let mem = self.memory_manager.lock().unwrap().boot_guest_memory();
let initramfs_config = match self.initramfs { let initramfs_config = match self.initramfs {
@ -1013,41 +976,20 @@ impl Vm {
.as_ref() .as_ref()
.cloned(); .cloned();
match entry_addr.setup_header { arch::configure_system(
Some(hdr) => { &mem,
arch::configure_system( arch::layout::CMDLINE_START,
&mem, &initramfs_config,
arch::layout::CMDLINE_START, boot_vcpus,
cmdline_cstring.to_bytes().len() + 1, rsdp_addr,
&initramfs_config, sgx_epc_region,
boot_vcpus, )
Some(hdr), .map_err(Error::ConfigureSystem)?;
rsdp_addr,
BootProtocol::LinuxBoot,
sgx_epc_region,
)
.map_err(Error::ConfigureSystem)?;
}
None => {
arch::configure_system(
&mem,
arch::layout::CMDLINE_START,
cmdline_cstring.to_bytes().len() + 1,
&initramfs_config,
boot_vcpus,
None,
rsdp_addr,
entry_addr.protocol,
sgx_epc_region,
)
.map_err(Error::ConfigureSystem)?;
}
}
Ok(()) Ok(())
} }
#[cfg(target_arch = "aarch64")] #[cfg(target_arch = "aarch64")]
fn configure_system(&mut self, _entry_addr: EntryPoint) -> Result<()> { fn configure_system(&mut self) -> Result<()> {
let cmdline_cstring = self.get_cmdline()?; let cmdline_cstring = self.get_cmdline()?;
let vcpu_mpidrs = self.cpu_manager.lock().unwrap().get_mpidrs(); let vcpu_mpidrs = self.cpu_manager.lock().unwrap().get_mpidrs();
let mem = self.memory_manager.lock().unwrap().boot_guest_memory(); let mem = self.memory_manager.lock().unwrap().boot_guest_memory();
@ -1715,9 +1657,7 @@ impl Vm {
}; };
// Configure shared state based on loaded kernel // Configure shared state based on loaded kernel
entry_point entry_point.map(|_| self.configure_system()).transpose()?;
.map(|entry_point| self.configure_system(entry_point))
.transpose()?;
#[cfg(feature = "tdx")] #[cfg(feature = "tdx")]
if let Some(hob_address) = hob_address { if let Some(hob_address) = hob_address {