Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IGVM: Add support for launching OVMF firmware when using an IGVM file for guest configuration #181

Merged
merged 4 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions igvm_params/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,26 @@ pub struct IgvmParamBlock {
/// The port number of the serial port to use for debugging.
pub debug_serial_port: u16,

/// A flag indicating whether the kernel should proceed with the flow
/// to launch guest firmware once kernel initialization is complete.
pub launch_fw: u8,
_reserved: u16,

_reserved: u8,
/// The guest physical address of the start of the guest firmware. The
/// permissions on the pages in the firmware range are adjusted to the guest
/// VMPL. If this field is zero then no firmware is launched after
/// initialization is complete.
pub fw_start: u32,

/// The number of pages of guest firmware. If the firmware size is zero then
/// no firmware is launched after initialization is complete.
pub fw_size: u32,

/// The guest physical address of the page that contains metadata that
/// corresponds to the firmware. The SVSM expects the page to contain
/// metadata in the format defined by OVMF. If this field is zero but
/// a firmware range has been provided then the firmware is launched
/// without parsing any metadata.
pub fw_metadata: u32,

_reserved2: u32,

/// The amount of space that must be reserved at the base of the kernel
/// memory region (e.g. for VMSA contents).
Expand Down
20 changes: 20 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::address::PhysAddr;
use crate::error::SvsmError;
use crate::fw_cfg::FwCfg;
use crate::igvm_params::IgvmParams;
use crate::mm::{PAGE_SIZE, SIZE_1G};
use crate::serial::SERIAL_PORT;
use crate::utils::MemoryRegion;
use alloc::vec::Vec;
Expand Down Expand Up @@ -71,4 +72,23 @@ impl<'a> SvsmConfig<'a> {
SvsmConfig::IgvmConfig(igvm_params) => igvm_params.debug_serial_port(),
}
}

pub fn get_fw_metadata(&self) -> Option<PhysAddr> {
match self {
SvsmConfig::FirmwareConfig(_) => {
// The metadata location always starts at 32 bytes below 4GB
Some(PhysAddr::from((4 * SIZE_1G) - PAGE_SIZE))
}
SvsmConfig::IgvmConfig(igvm_params) => igvm_params.get_fw_metadata(),
}
}

pub fn get_fw_regions(&self) -> Result<Vec<MemoryRegion<PhysAddr>>, SvsmError> {
match self {
SvsmConfig::FirmwareConfig(fw_cfg) => {
Ok(fw_cfg.iter_flash_regions().collect::<Vec<_>>())
}
SvsmConfig::IgvmConfig(igvm_params) => igvm_params.get_fw_regions(),
}
}
}
2 changes: 1 addition & 1 deletion src/fw_cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ impl<'a> FwCfg<'a> {
// This needs to be &mut self to prevent iterator invalidation, where the caller
// could do fw_cfg.select() while iterating. Having a mutable reference prevents
// other references.
pub fn iter_flash_regions(&mut self) -> impl Iterator<Item = MemoryRegion<PhysAddr>> + '_ {
pub fn iter_flash_regions(&self) -> impl Iterator<Item = MemoryRegion<PhysAddr>> + '_ {
Comment on lines 217 to +220
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change ignores the comment above the function. Not taking &mut self allows potential misbehavior. The comment should be updated or the change reverted.

let num = match self.file_selector("etc/flash") {
Ok(file) => {
self.select(file.selector);
Expand Down
22 changes: 21 additions & 1 deletion src/igvm_params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::error::SvsmError;
use crate::error::SvsmError::Firmware;
use crate::mm::PAGE_SIZE;
use crate::utils::MemoryRegion;
use alloc::vec;
use alloc::vec::Vec;

use core::mem::size_of;
Expand Down Expand Up @@ -132,10 +133,29 @@ impl IgvmParams<'_> {
}

pub fn should_launch_fw(&self) -> bool {
self.igvm_param_block.launch_fw != 0
self.igvm_param_block.fw_size != 0
}

pub fn debug_serial_port(&self) -> u16 {
self.igvm_param_block.debug_serial_port
}

pub fn get_fw_metadata(&self) -> Option<PhysAddr> {
if !self.should_launch_fw() || self.igvm_param_block.fw_metadata == 0 {
None
} else {
Some(PhysAddr::from(self.igvm_param_block.fw_metadata as u64))
}
}

pub fn get_fw_regions(&self) -> Result<Vec<MemoryRegion<PhysAddr>>, SvsmError> {
if !self.should_launch_fw() {
Err(Firmware)
} else {
Ok(vec![MemoryRegion::new(
PhysAddr::new(self.igvm_param_block.fw_start as usize),
self.igvm_param_block.fw_size as usize * PAGE_SIZE,
)])
}
}
}
34 changes: 14 additions & 20 deletions src/svsm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

extern crate alloc;

use alloc::vec::Vec;
use svsm::fw_meta::{parse_fw_meta_data, print_fw_meta, validate_fw_memory, SevFWMetaData};

use core::arch::global_asm;
Expand Down Expand Up @@ -39,7 +38,7 @@ use svsm::mm::alloc::{memory_info, print_memory_info, root_mem_init};
use svsm::mm::memory::init_memory_map;
use svsm::mm::pagetable::paging_init;
use svsm::mm::virtualrange::virt_log_usage;
use svsm::mm::{init_kernel_mapping_info, PerCPUPageMappingGuard, SIZE_1G};
use svsm::mm::{init_kernel_mapping_info, PerCPUPageMappingGuard};
use svsm::requests::{request_loop, update_mappings};
use svsm::serial::SerialPort;
use svsm::sev::secrets_page::{copy_secrets_page, disable_vmpck0, SecretsPage};
Expand Down Expand Up @@ -219,10 +218,8 @@ fn launch_fw() -> Result<(), SvsmError> {
Ok(())
}

fn validate_flash() -> Result<(), SvsmError> {
let mut fw_cfg = FwCfg::new(&CONSOLE_IO);

let flash_regions = fw_cfg.iter_flash_regions().collect::<Vec<_>>();
fn validate_fw(config: &SvsmConfig) -> Result<(), SvsmError> {
let flash_regions = config.get_fw_regions()?;
let kernel_region = LAUNCH_INFO.kernel_region();
let flash_range = {
let one_gib = 1024 * 1024 * 1024usize;
Expand Down Expand Up @@ -307,10 +304,9 @@ fn mapping_info_init(launch_info: &KernelLaunchInfo) {
);
}

fn map_and_parse_fw_meta() -> Result<SevFWMetaData, SvsmError> {
// Map meta-data location, it starts at 32 bytes below 4GiB
let pstart = PhysAddr::from((4 * SIZE_1G) - PAGE_SIZE);
let guard = PerCPUPageMappingGuard::create_4k(pstart)?;
fn map_and_parse_fw_meta(fw_metadata_phys: PhysAddr) -> Result<SevFWMetaData, SvsmError> {
// Map the metadata location which is defined by the firmware config
let guard = PerCPUPageMappingGuard::create_4k(fw_metadata_phys)?;
let vstart = guard.virt_addr().as_ptr::<u8>();
// Safety: we just mapped a page, so the size must hold. The type
// of the slice elements is `u8` so there are no alignment requirements.
Expand Down Expand Up @@ -461,14 +457,10 @@ pub extern "C" fn svsm_main() {

start_secondary_cpus(&cpus);

let fw_metadata = if config.should_launch_fw() {
Some(
map_and_parse_fw_meta()
.unwrap_or_else(|e| panic!("Failed to parse FW SEV meta-data: {:#?}", e)),
)
} else {
None
};
let fw_metadata = config.get_fw_metadata().map(|fw_metadata_phys| {
map_and_parse_fw_meta(fw_metadata_phys)
.unwrap_or_else(|e| panic!("Failed to parse FW SEV meta-data: {:#?}", e))
});

if let Some(ref fw_meta) = fw_metadata {
print_fw_meta(fw_meta);
Expand All @@ -480,8 +472,10 @@ pub extern "C" fn svsm_main() {
if let Err(e) = copy_tables_to_fw(fw_meta) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your PR moves the location of the firmware to the IGVM file (which is good) but there is still a hardcoded assumption that the firmware wants CPUID/secrets information in a particular place (see map_and_parse_fw_meta). Shouldn't this be abstracted in the IGVM as well so that the SVSM is not making assumptions about OVMF-specific layout?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I mentioned in the commit and the description of the PR that although the firmware is in the IGVM file the builder must currently align the firmware to 4GB. This PR allows me to get back to the point where we can boot firmware from SVSM when using an IGVM file. At this stage I think it is ok to assume that OVMF places it's configuration at the top of its GPA range as this is well defined.

The future plan is to allow for IGVM to specify a different location for the firmware, and at that point we would need to describe the layout via parameters to SVSM. But this would require changes to OVMF which I think will be the subject of future work.

Do you think this approach is acceptable?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I meant something slightly different. The current implementation of map_and_parse_fw_meta has hard-coded logic which assumes that the firmware metadata must be at 4 GB less 4 KB. That may be great for OVMF today but won't be great for any other firmware that might be packaged into an IGVM file. Since you're making IGVM flexible enough to specify the base/size of the firmware, it seems like you should also have the IGVM file include the location of the metadata information (if it exists at all) so that map_and_parse_fw_meta can do its work to match what the IGVM specifies. Note that none of this has anything to with changing OVMF; it's just about making the SVSM self-consistent in the way it looks for FW-specific data (i.e., dictate all references from the IGVM and not from hard-coded constants).

I can always add that in a future PR if this is too much of a distraction from completing your current PR. It shouldn't be much work regardless of which one of us does it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I'm aware, the format of the metadata itself is fairly unique to OVMF, including the location of where to find it. But I see your point - it is possible for the IGVM file to locate it elsewhere even if OVMF won't boot in that case. The fw range check in SVSM currently ensures it aligns with 4GB so it won't allow the firmware to be located anywhere else, but that will change and it does make sense to add the new field to describe the GPA of the firmware metadata page. I'll add it to the parameters.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is better, but I foresee a version where we have firmware but where the firmware doesn't use metadata in this way. In that case, I would expect the IGVM parameters to specify a base/size of the firmware but would specify a metadata address of zero. However, your new version of the code appears to treat that as an error, because get_fw_metadata is called unconditionally and would return an error. Can this be changed so that firmware that does not express a metadata location can just skip the step of preparing the metadata instead of generating an error.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, done. Firmware metadata is now optional and firmware can be launched even if metadata is not provided.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

panic!("Failed to copy firmware tables: {:#?}", e);
}
}

if let Err(e) = validate_flash() {
if config.should_launch_fw() {
if let Err(e) = validate_fw(&config) {
panic!("Failed to validate flash memory: {:#?}", e);
}
}
Expand All @@ -494,7 +488,7 @@ pub extern "C" fn svsm_main() {

virt_log_usage();

if fw_metadata.is_some() {
if config.should_launch_fw() {
if let Err(e) = launch_fw() {
panic!("Failed to launch FW: {:#?}", e);
}
Expand Down
Loading