// Copyright © 2021 Intel Corporation // // SPDX-License-Identifier: Apache-2.0 use crate::GuestMemoryMmap; use std::fs::File; use std::io::{Read, Seek, SeekFrom}; use thiserror::Error; use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError}; #[derive(Error, Debug)] pub enum TdvfError { #[error("Failed read TDVF descriptor: {0}")] ReadDescriptor(#[source] std::io::Error), #[error("Failed read TDVF descriptor offset: {0}")] ReadDescriptorOffset(#[source] std::io::Error), #[error("Invalid descriptor signature")] InvalidDescriptorSignature, #[error("Invalid descriptor size")] InvalidDescriptorSize, #[error("Invalid descriptor version")] InvalidDescriptorVersion, #[error("Failed to write HOB details to guest memory: {0}")] GuestMemoryWriteHob(#[source] GuestMemoryError), } // TDVF_DESCRIPTOR #[repr(packed)] #[derive(Default)] pub struct TdvfDescriptor { signature: [u8; 4], length: u32, version: u32, num_sections: u32, // NumberOfSectionEntry } // TDVF_SECTION #[repr(packed)] #[derive(Clone, Copy, Default, Debug)] pub struct TdvfSection { pub data_offset: u32, pub data_size: u32, // RawDataSize pub address: u64, // MemoryAddress pub size: u64, // MemoryDataSize pub r#type: TdvfSectionType, pub attributes: u32, } #[repr(u32)] #[derive(Clone, Copy, Debug)] pub enum TdvfSectionType { Bfv, Cfv, TdHob, TempMem, PermMem, Payload, PayloadParam, Reserved = 0xffffffff, } impl Default for TdvfSectionType { fn default() -> Self { TdvfSectionType::Reserved } } pub fn parse_tdvf_sections(file: &mut File) -> Result, TdvfError> { // The 32-bit offset to the TDVF metadata is located 32 bytes from // the end of the file. // See "TDVF Metadata Pointer" in "TDX Virtual Firmware Design Guide file.seek(SeekFrom::End(-0x20)) .map_err(TdvfError::ReadDescriptorOffset)?; let mut descriptor_offset: [u8; 4] = [0; 4]; file.read_exact(&mut descriptor_offset) .map_err(TdvfError::ReadDescriptorOffset)?; let descriptor_offset = u32::from_le_bytes(descriptor_offset) as u64; file.seek(SeekFrom::Start(descriptor_offset)) .map_err(TdvfError::ReadDescriptor)?; let mut descriptor: TdvfDescriptor = Default::default(); // Safe as we read exactly the size of the descriptor header file.read_exact(unsafe { std::slice::from_raw_parts_mut( &mut descriptor as *mut _ as *mut u8, std::mem::size_of::(), ) }) .map_err(TdvfError::ReadDescriptor)?; if &descriptor.signature != b"TDVF" { return Err(TdvfError::InvalidDescriptorSignature); } if descriptor.length as usize != std::mem::size_of::() + std::mem::size_of::() * descriptor.num_sections as usize { return Err(TdvfError::InvalidDescriptorSize); } if descriptor.version != 1 { return Err(TdvfError::InvalidDescriptorVersion); } let mut sections = Vec::new(); sections.resize_with(descriptor.num_sections as usize, TdvfSection::default); // Safe as we read exactly the advertised sections file.read_exact(unsafe { std::slice::from_raw_parts_mut( sections.as_mut_ptr() as *mut u8, descriptor.num_sections as usize * std::mem::size_of::(), ) }) .map_err(TdvfError::ReadDescriptor)?; Ok(sections) } #[repr(u16)] #[derive(Copy, Clone, Debug)] enum HobType { Handoff = 0x1, ResourceDescriptor = 0x3, GuidExtension = 0x4, Unused = 0xfffe, EndOfHobList = 0xffff, } impl Default for HobType { fn default() -> Self { HobType::Unused } } #[repr(C)] #[derive(Copy, Clone, Default, Debug)] struct HobHeader { r#type: HobType, length: u16, reserved: u32, } #[repr(C)] #[derive(Copy, Clone, Default, Debug)] struct HobHandoffInfoTable { header: HobHeader, version: u32, boot_mode: u32, efi_memory_top: u64, efi_memory_bottom: u64, efi_free_memory_top: u64, efi_free_memory_bottom: u64, efi_end_of_hob_list: u64, } #[repr(C)] #[derive(Copy, Clone, Default, Debug)] struct EfiGuid { data1: u32, data2: u16, data3: u16, data4: [u8; 8], } #[repr(C)] #[derive(Copy, Clone, Default, Debug)] struct HobResourceDescriptor { header: HobHeader, owner: EfiGuid, resource_type: u32, resource_attribute: u32, physical_start: u64, resource_length: u64, } #[repr(C)] #[derive(Copy, Clone, Default, Debug)] struct HobGuidType { header: HobHeader, name: EfiGuid, } #[repr(u32)] #[derive(Clone, Copy, Debug)] pub enum PayloadImageType { ExecutablePayload, BzImage, RawVmLinux, } impl Default for PayloadImageType { fn default() -> Self { PayloadImageType::ExecutablePayload } } #[repr(C)] #[derive(Copy, Clone, Default, Debug)] pub struct PayloadInfo { pub image_type: PayloadImageType, pub entry_point: u64, } #[repr(C)] #[derive(Copy, Clone, Default, Debug)] struct TdPayload { guid_type: HobGuidType, payload_info: PayloadInfo, } // SAFETY: These data structures only contain a series of integers unsafe impl ByteValued for HobHeader {} unsafe impl ByteValued for HobHandoffInfoTable {} unsafe impl ByteValued for HobResourceDescriptor {} unsafe impl ByteValued for HobGuidType {} unsafe impl ByteValued for PayloadInfo {} unsafe impl ByteValued for TdPayload {} pub struct TdHob { start_offset: u64, current_offset: u64, } fn align_hob(v: u64) -> u64 { (v + 7) / 8 * 8 } impl TdHob { fn update_offset(&mut self) { self.current_offset = align_hob(self.current_offset + std::mem::size_of::() as u64) } pub fn start(offset: u64) -> TdHob { // Leave a gap to place the HandoffTable at the start as it can only be filled in later let mut hob = TdHob { start_offset: offset, current_offset: offset, }; hob.update_offset::(); hob } pub fn finish(&mut self, mem: &GuestMemoryMmap) -> Result<(), TdvfError> { // Write end let end = HobHeader { r#type: HobType::EndOfHobList, length: std::mem::size_of::() as u16, reserved: 0, }; info!("Writing HOB end {:x} {:x?}", self.current_offset, end); mem.write_obj(end, GuestAddress(self.current_offset)) .map_err(TdvfError::GuestMemoryWriteHob)?; self.update_offset::(); // Write handoff, delayed as it needs end of HOB list let efi_end_of_hob_list = self.current_offset; let handoff = HobHandoffInfoTable { header: HobHeader { r#type: HobType::Handoff, length: std::mem::size_of::() as u16, reserved: 0, }, version: 0x9, boot_mode: 0, efi_memory_top: 0, efi_memory_bottom: 0, efi_free_memory_top: 0, efi_free_memory_bottom: 0, efi_end_of_hob_list, }; info!("Writing HOB start {:x} {:x?}", self.start_offset, handoff); mem.write_obj(handoff, GuestAddress(self.start_offset)) .map_err(TdvfError::GuestMemoryWriteHob) } pub fn add_resource( &mut self, mem: &GuestMemoryMmap, physical_start: u64, resource_length: u64, resource_type: u32, resource_attribute: u32, ) -> Result<(), TdvfError> { let resource_descriptor = HobResourceDescriptor { header: HobHeader { r#type: HobType::ResourceDescriptor, length: std::mem::size_of::() as u16, reserved: 0, }, owner: EfiGuid::default(), resource_type, resource_attribute, physical_start, resource_length, }; info!( "Writing HOB resource {:x} {:x?}", self.current_offset, resource_descriptor ); mem.write_obj(resource_descriptor, GuestAddress(self.current_offset)) .map_err(TdvfError::GuestMemoryWriteHob)?; self.update_offset::(); Ok(()) } pub fn add_memory_resource( &mut self, mem: &GuestMemoryMmap, physical_start: u64, resource_length: u64, ram: bool, ) -> Result<(), TdvfError> { self.add_resource( mem, physical_start, resource_length, if ram { 0 /* EFI_RESOURCE_SYSTEM_MEMORY */ } else { 0x5 /*EFI_RESOURCE_MEMORY_RESERVED */ }, /* TODO: * QEMU currently fills it in like this: * EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_TESTED * which differs from the spec (due to TDVF implementation issue?) */ 0x7, ) } pub fn add_mmio_resource( &mut self, mem: &GuestMemoryMmap, physical_start: u64, resource_length: u64, ) -> Result<(), TdvfError> { self.add_resource( mem, physical_start, resource_length, 0x1, /* EFI_RESOURCE_MEMORY_MAPPED_IO */ /* * EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_UNCACHEABLE */ 0x403, ) } pub fn add_acpi_table( &mut self, mem: &GuestMemoryMmap, table_content: &[u8], ) -> Result<(), TdvfError> { // We already know the HobGuidType size is 8 bytes multiple, but we // need the total size to be 8 bytes multiple. That is why the ACPI // table size must be 8 bytes multiple as well. let length = std::mem::size_of::() as u16 + align_hob(table_content.len() as u64) as u16; let hob_guid_type = HobGuidType { header: HobHeader { r#type: HobType::GuidExtension, length, reserved: 0, }, // ACPI_TABLE_HOB_GUID // 0x6a0c5870, 0xd4ed, 0x44f4, {0xa1, 0x35, 0xdd, 0x23, 0x8b, 0x6f, 0xc, 0x8d } name: EfiGuid { data1: 0x6a0c_5870, data2: 0xd4ed, data3: 0x44f4, data4: [0xa1, 0x35, 0xdd, 0x23, 0x8b, 0x6f, 0xc, 0x8d], }, }; info!( "Writing HOB ACPI table {:x} {:x?} {:x?}", self.current_offset, hob_guid_type, table_content ); mem.write_obj(hob_guid_type, GuestAddress(self.current_offset)) .map_err(TdvfError::GuestMemoryWriteHob)?; let current_offset = self.current_offset + std::mem::size_of::() as u64; // In case the table is quite large, let's make sure we can handle // retrying until everything has been correctly copied. let mut offset: usize = 0; loop { let bytes_written = mem .write( &table_content[offset..], GuestAddress(current_offset + offset as u64), ) .map_err(TdvfError::GuestMemoryWriteHob)?; offset += bytes_written; if offset >= table_content.len() { break; } } self.current_offset += length as u64; Ok(()) } pub fn add_payload( &mut self, mem: &GuestMemoryMmap, payload_info: PayloadInfo, ) -> Result<(), TdvfError> { let payload = TdPayload { guid_type: HobGuidType { header: HobHeader { r#type: HobType::GuidExtension, length: std::mem::size_of::() as u16, reserved: 0, }, // HOB_PAYLOAD_INFO_GUID // 0xb96fa412, 0x461f, 0x4be3, {0x8c, 0xd, 0xad, 0x80, 0x5a, 0x49, 0x7a, 0xc0 name: EfiGuid { data1: 0xb96f_a412, data2: 0x461f, data3: 0x4be3, data4: [0x8c, 0xd, 0xad, 0x80, 0x5a, 0x49, 0x7a, 0xc0], }, }, payload_info, }; info!( "Writing HOB TD_PAYLOAD {:x} {:x?}", self.current_offset, payload ); mem.write_obj(payload, GuestAddress(self.current_offset)) .map_err(TdvfError::GuestMemoryWriteHob)?; self.update_offset::(); Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] #[ignore] fn test_parse_tdvf_sections() { let mut f = std::fs::File::open("tdvf.fd").unwrap(); let sections = parse_tdvf_sections(&mut f).unwrap(); for section in sections { eprintln!("{:x?}", section) } } }