Skip to content

Commit

Permalink
igvmbuilder: Add implementation of IGVM firmware
Browse files Browse the repository at this point in the history
Hyper-V does not use OVMF firmware. Instead it uses a firmware binary
that is described in IGVM format. This commit adds an IGVM parser that
extracts the relevent directives from the firmware binary and adds them
to the built IGVM file.

Signed-off-by: Roy Hopkins <roy.hopkins@suse.com>
  • Loading branch information
roy-hopkins committed Jan 30, 2024
1 parent 12219e0 commit 9d978f7
Show file tree
Hide file tree
Showing 6 changed files with 483 additions and 85 deletions.
69 changes: 24 additions & 45 deletions igvmbuilder/src/firmware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,57 +5,36 @@
// Author: Roy Hopkins <roy.hopkins@suse.com>

use std::error::Error;
use std::fs::File;
use std::io::Read;

use igvm::IgvmDirectiveHeader;

use crate::cmd_options::CmdOptions;
use crate::igvm_params::IgvmParamBlockFwInfo;
use crate::ovmfmeta::parse_ovmf;
use crate::igvm_firmware::IgvmFirmware;
use crate::igvm_params::{IgvmGuestContext, IgvmParamBlockFwInfo};
use crate::ovmf_firmware::OvmfFirmware;

#[derive(Default)]
pub struct Firmware {
fw_info: IgvmParamBlockFwInfo,
vtom: u64,
pub trait Firmware {
fn directives(&self) -> &Vec<IgvmDirectiveHeader>;
fn get_guest_context(&self) -> Option<IgvmGuestContext>;
fn get_vtom(&self) -> u64;
fn get_fw_info(&self) -> IgvmParamBlockFwInfo;
}

impl Firmware {
pub fn parse(options: &CmdOptions) -> Result<Option<Self>, Box<dyn Error>> {
let mut firmware = Firmware::default();
if let Some(filename) = &options.firmware {
match options.hypervisor {
crate::cmd_options::Hypervisor::QEMU => {
let mut in_file = File::open(filename)?;
let len = in_file.metadata()?.len() as usize;
if len > 0xffffffff {
return Err("OVMF firmware is too large".into());
}
let mut data = vec![0u8; len];
if in_file.read(&mut data)? != len {
return Err("Failed to read OVMF file".into());
}
parse_ovmf(&data, &mut firmware.fw_info)?;

// OVMF must be located to end at 4GB.
firmware.fw_info.start = (0xffffffff - len + 1) as u32;
firmware.fw_info.size = len as u32;
}
crate::cmd_options::Hypervisor::HyperV => {
// Read and parse Hyper-V firmware.
// Populate vtom if present in firmware.
todo!()
}
pub fn parse_firmware(
options: &CmdOptions,
parameter_count: u32,
compatibility_mask: u32,
) -> Result<Box<dyn Firmware>, Box<dyn Error>> {
if let Some(filename) = &options.firmware {
match options.hypervisor {
crate::cmd_options::Hypervisor::QEMU => {
OvmfFirmware::parse(filename, parameter_count, compatibility_mask)
}
crate::cmd_options::Hypervisor::HyperV => {
IgvmFirmware::parse(filename, parameter_count, compatibility_mask)
}
Ok(Some(firmware))
} else {
Ok(None)
}
}

pub fn get_fw_info(&self) -> IgvmParamBlockFwInfo {
self.fw_info
}

pub fn get_vtom(&self) -> u64 {
self.vtom
} else {
Err("No firmware filename specified".into())
}
}
70 changes: 44 additions & 26 deletions igvmbuilder/src/gpa_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::fs::metadata;
use igvm_defs::PAGE_SIZE_4K;

use crate::cmd_options::{CmdOptions, Hypervisor};
use crate::firmware::Firmware;

#[derive(Debug, Copy, Clone)]
pub struct GpaRange {
Expand Down Expand Up @@ -59,13 +60,17 @@ pub struct GpaMap {
pub igvm_param_block: GpaRange,
pub general_params: GpaRange,
pub memory_map: GpaRange,
pub guest_context: GpaRange,
pub firmware: GpaRange,
pub kernel: GpaRange,
pub vmsa: GpaRange,
}

impl GpaMap {
pub fn new(options: &CmdOptions) -> Result<Self, Box<dyn Error>> {
pub fn new(
options: &CmdOptions,
firmware: &Option<Box<dyn Firmware>>,
) -> Result<Self, Box<dyn Error>> {
// 0x000000-0x00EFFF: zero-filled (must be pre-validated)
// 0x00F000-0x00FFFF: initial stage 2 stack page
// 0x010000-0x0nnnnn: stage 2 image
Expand All @@ -89,32 +94,29 @@ impl GpaMap {

let stage2_image = GpaRange::new(0x10000, stage2_len as u64)?;

// Plan to load the kernel image at a base address of 1 MB unless it must
// be relocated due to firmware.
let kernel_address = 1 << 20;
// TODO: If Hyper-V then parse the firmware and determine if the kernel
// address changes. Also calculate vtom from firmware

let kernel_elf = GpaRange::new(kernel_address, kernel_elf_len as u64)?;
let kernel_fs = GpaRange::new(kernel_elf.end, kernel_fs_len as u64)?;

// Calculate the firmware range
let firmware = if let Some(firmware) = &options.firmware {
match options.hypervisor {
Hypervisor::QEMU => {
// OVMF must be located to end at 4GB.
let len = metadata(firmware)?.len() as usize;
if len > 0xffffffff {
return Err("OVMF firmware is too large".into());
}
GpaRange::new((0xffffffff - len + 1) as u64, len as u64)?
}
Hypervisor::HyperV => return Err("Hyper-V firmware not yet implemented".into()),
}
let firmware_range = if let Some(firmware) = firmware {
let fw_start = firmware.get_fw_info().start as u64;
let fw_size = firmware.get_fw_info().size as u64;
GpaRange::new(fw_start, fw_size)?
} else {
GpaRange::new(0, 0)?
};

let kernel_address = match options.hypervisor {
Hypervisor::QEMU => {
// Plan to load the kernel image at a base address of 1 MB unless it must
// be relocated due to firmware.
1 << 20
}
Hypervisor::HyperV => {
// Load the kernel image after the firmware.
firmware_range.end
}
};
let kernel_elf = GpaRange::new(kernel_address, kernel_elf_len as u64)?;
let kernel_fs = GpaRange::new(kernel_elf.end, kernel_fs_len as u64)?;

// Calculate the kernel size and base.
let kernel = match options.hypervisor {
Hypervisor::QEMU => {
Expand All @@ -126,6 +128,21 @@ impl GpaMap {
GpaRange::new(0x04000000, 0x01000000)?
}
};

let igvm_param_block = GpaRange::new_page(kernel_elf.end)?;
let general_params = GpaRange::new_page(igvm_param_block.end)?;
let memory_map = GpaRange::new_page(general_params.end)?;
let guest_context = if let Some(firmware) = firmware {
if firmware.get_guest_context().is_some() {
// Locate the guest context after the memory map parameter page
GpaRange::new_page(memory_map.end)?
} else {
GpaRange::new(0, 0)?
}
} else {
GpaRange::new(0, 0)?
};

let gpa_map = Self {
low_memory: GpaRange::new(0, 0xf000)?,
stage2_stack: GpaRange::new_page(0xf000)?,
Expand All @@ -135,10 +152,11 @@ impl GpaMap {
cpuid_page: GpaRange::new_page(0x9f000)?,
kernel_elf,
kernel_fs,
igvm_param_block: GpaRange::new_page(kernel_elf.end)?,
general_params: GpaRange::new_page(kernel_elf.end + PAGE_SIZE_4K)?,
memory_map: GpaRange::new_page(kernel_elf.end + 2 * PAGE_SIZE_4K)?,
firmware,
igvm_param_block,
general_params,
memory_map,
guest_context,
firmware: firmware_range,
kernel,
vmsa: GpaRange::new_page(kernel.start)?,
};
Expand Down
75 changes: 62 additions & 13 deletions igvmbuilder/src/igvm_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ use igvm_defs::{

use crate::cmd_options::{CmdOptions, Hypervisor};
use crate::cpuid::SnpCpuidPage;
use crate::firmware::Firmware;
use crate::igvm_params::{IgvmParamBlock, IgvmParamBlockFwInfo, IgvmParamBlockFwMem};
use crate::firmware::{parse_firmware, Firmware};
use crate::igvm_params::{
IgvmGuestContext, IgvmParamBlock, IgvmParamBlockFwInfo, IgvmParamBlockFwMem,
};
use crate::stage2_stack::Stage2Stack;
use crate::vmsa::construct_vmsa;
use crate::GpaMap;
Expand All @@ -29,10 +31,11 @@ const COMPATIBILITY_MASK: u32 = 1;
// Parameter area indices
const IGVM_GENERAL_PARAMS_PA: u32 = 0;
const IGVM_MEMORY_MAP_PA: u32 = 1;
const IGVM_PARAMETER_COUNT: u32 = 2;

pub struct IgvmBuilder {
options: CmdOptions,
firmware: Option<Firmware>,
firmware: Option<Box<dyn Firmware>>,
gpa_map: GpaMap,
platforms: Vec<IgvmPlatformHeader>,
directives: Vec<IgvmDirectiveHeader>,
Expand All @@ -41,8 +44,15 @@ pub struct IgvmBuilder {
impl IgvmBuilder {
pub fn new() -> Result<Self, Box<dyn Error>> {
let options = CmdOptions::parse();
let firmware = Firmware::parse(&options)?;
let gpa_map = GpaMap::new(&options)?;
let firmware = match options.firmware {
Some(_) => Some(parse_firmware(
&options,
IGVM_PARAMETER_COUNT,
COMPATIBILITY_MASK,
)?),
None => None,
};
let gpa_map = GpaMap::new(&options, &firmware)?;
Ok(Self {
options,
firmware,
Expand Down Expand Up @@ -75,7 +85,17 @@ impl IgvmBuilder {
fn create_param_block(&self) -> Result<IgvmParamBlock, Box<dyn Error>> {
let param_page_offset = PAGE_SIZE_4K as u32;
let memory_map_offset = param_page_offset + PAGE_SIZE_4K as u32;
let memory_map_end_offset = memory_map_offset + PAGE_SIZE_4K as u32;
let (guest_context_offset, param_area_size) = if self.gpa_map.guest_context.get_size() == 0
{
(0, memory_map_offset + PAGE_SIZE_4K as u32)
} else {
(
memory_map_offset + PAGE_SIZE_4K as u32,
memory_map_offset
+ PAGE_SIZE_4K as u32
+ self.gpa_map.guest_context.get_size() as u32,
)
};

// Populate the firmware metadata.
let (fw_info, vtom) = if let Some(firmware) = &self.firmware {
Expand Down Expand Up @@ -105,10 +125,10 @@ impl IgvmBuilder {

// Most of the parameter block can be initialised with constants.
let mut param_block = IgvmParamBlock {
param_area_size: memory_map_end_offset,
param_area_size,
param_page_offset,
memory_map_offset,
guest_context_offset: 0,
guest_context_offset,
cpuid_page: self.gpa_map.cpuid_page.get_start() as u32,
secrets_page: self.gpa_map.secrets_page.get_start() as u32,
debug_serial_port: self.options.get_port_address(),
Expand Down Expand Up @@ -150,6 +170,15 @@ impl IgvmBuilder {
}

fn build_directives(&mut self, param_block: &IgvmParamBlock) -> Result<(), Box<dyn Error>> {
// Populate firmware directives.
if let Some(firmware) = &self.firmware {
self.directives.append(&mut firmware.directives().clone());
// If the firmware has a guest context then add it.
if let Some(guest_context) = firmware.get_guest_context() {
self.add_guest_context(&guest_context)?;
}
}

// Describe the kernel RAM region
self.directives.push(IgvmDirectiveHeader::RequiredMemory {
gpa: param_block.kernel_base,
Expand Down Expand Up @@ -205,11 +234,6 @@ impl IgvmBuilder {
COMPATIBILITY_MASK,
)?);

// Populate the firmware pages.
if let Some(firmware) = self.options.firmware.clone() {
self.add_data_pages_from_file(&firmware, self.gpa_map.firmware.get_start())?;
}

// Add the IGVM parameter block
self.add_param_block(param_block)?;

Expand Down Expand Up @@ -317,6 +341,31 @@ impl IgvmBuilder {
Ok(())
}

fn add_guest_context(
&mut self,
guest_context: &IgvmGuestContext,
) -> Result<(), Box<dyn Error>> {
let guest_context_data = unsafe {
let ptr = guest_context as *const IgvmGuestContext
as *const [u8; size_of::<IgvmGuestContext>()];
&*ptr
};
if guest_context_data.len() > PAGE_SIZE_4K as usize {
return Err("IGVM parameter block size exceeds 4K".into());
}
let mut guest_context_page = [0u8; PAGE_SIZE_4K as usize];
guest_context_page[..guest_context_data.len()].clone_from_slice(guest_context_data);

self.directives.push(IgvmDirectiveHeader::PageData {
gpa: self.gpa_map.guest_context.get_start(),
compatibility_mask: COMPATIBILITY_MASK,
flags: IgvmPageDataFlags::new(),
data_type: IgvmPageDataType::NORMAL,
data: guest_context_page.to_vec(),
});
Ok(())
}

fn add_empty_pages(
&mut self,
gpa_start: u64,
Expand Down
Loading

0 comments on commit 9d978f7

Please sign in to comment.