1 // Copyright © 2021 Intel Corporation 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 use crate::GuestMemoryMmap; 5 use std::fs::File; 6 use std::io::{Read, Seek, SeekFrom}; 7 use thiserror::Error; 8 use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemoryError}; 9 10 #[derive(Error, Debug)] 11 pub enum TdvfError { 12 #[error("Failed read TDVF descriptor: {0}")] 13 ReadDescriptor(#[source] std::io::Error), 14 #[error("Failed read TDVF descriptor offset: {0}")] 15 ReadDescriptorOffset(#[source] std::io::Error), 16 #[error("Invalid descriptor signature")] 17 InvalidDescriptorSignature, 18 #[error("Invalid descriptor size")] 19 InvalidDescriptorSize, 20 #[error("Invalid descriptor version")] 21 InvalidDescriptorVersion, 22 #[error("Failed to write HOB details to guest memory: {0}")] 23 GuestMemoryWriteHob(#[source] GuestMemoryError), 24 } 25 26 // TDVF_DESCRIPTOR 27 #[repr(packed)] 28 #[derive(Default)] 29 pub struct TdvfDescriptor { 30 signature: [u8; 4], 31 length: u32, 32 version: u32, 33 num_sections: u32, // NumberOfSectionEntry 34 } 35 36 // TDVF_SECTION 37 #[repr(packed)] 38 #[derive(Clone, Copy, Default, Debug)] 39 pub struct TdvfSection { 40 pub data_offset: u32, 41 pub data_size: u32, // RawDataSize 42 pub address: u64, // MemoryAddress 43 pub size: u64, // MemoryDataSize 44 pub r#type: TdvfSectionType, 45 pub attributes: u32, 46 } 47 48 #[repr(u32)] 49 #[derive(Clone, Copy, Debug)] 50 pub enum TdvfSectionType { 51 Bfv, 52 Cfv, 53 TdHob, 54 TempMem, 55 PermMem, 56 Payload, 57 PayloadParam, 58 Reserved = 0xffffffff, 59 } 60 61 impl Default for TdvfSectionType { 62 fn default() -> Self { 63 TdvfSectionType::Reserved 64 } 65 } 66 67 pub fn parse_tdvf_sections(file: &mut File) -> Result<Vec<TdvfSection>, TdvfError> { 68 // The 32-bit offset to the TDVF metadata is located 32 bytes from 69 // the end of the file. 70 // See "TDVF Metadata Pointer" in "TDX Virtual Firmware Design Guide 71 file.seek(SeekFrom::End(-0x20)) 72 .map_err(TdvfError::ReadDescriptorOffset)?; 73 74 let mut descriptor_offset: [u8; 4] = [0; 4]; 75 file.read_exact(&mut descriptor_offset) 76 .map_err(TdvfError::ReadDescriptorOffset)?; 77 let descriptor_offset = u32::from_le_bytes(descriptor_offset) as u64; 78 79 file.seek(SeekFrom::Start(descriptor_offset)) 80 .map_err(TdvfError::ReadDescriptor)?; 81 82 let mut descriptor: TdvfDescriptor = Default::default(); 83 // Safe as we read exactly the size of the descriptor header 84 file.read_exact(unsafe { 85 std::slice::from_raw_parts_mut( 86 &mut descriptor as *mut _ as *mut u8, 87 std::mem::size_of::<TdvfDescriptor>(), 88 ) 89 }) 90 .map_err(TdvfError::ReadDescriptor)?; 91 92 if &descriptor.signature != b"TDVF" { 93 return Err(TdvfError::InvalidDescriptorSignature); 94 } 95 96 if descriptor.length as usize 97 != std::mem::size_of::<TdvfDescriptor>() 98 + std::mem::size_of::<TdvfSection>() * descriptor.num_sections as usize 99 { 100 return Err(TdvfError::InvalidDescriptorSize); 101 } 102 103 if descriptor.version != 1 { 104 return Err(TdvfError::InvalidDescriptorVersion); 105 } 106 107 let mut sections = Vec::new(); 108 sections.resize_with(descriptor.num_sections as usize, TdvfSection::default); 109 110 // Safe as we read exactly the advertised sections 111 file.read_exact(unsafe { 112 std::slice::from_raw_parts_mut( 113 sections.as_mut_ptr() as *mut u8, 114 descriptor.num_sections as usize * std::mem::size_of::<TdvfSection>(), 115 ) 116 }) 117 .map_err(TdvfError::ReadDescriptor)?; 118 119 Ok(sections) 120 } 121 122 #[repr(u16)] 123 #[derive(Copy, Clone, Debug)] 124 enum HobType { 125 Handoff = 0x1, 126 ResourceDescriptor = 0x3, 127 GuidExtension = 0x4, 128 Unused = 0xfffe, 129 EndOfHobList = 0xffff, 130 } 131 132 impl Default for HobType { 133 fn default() -> Self { 134 HobType::Unused 135 } 136 } 137 138 #[repr(C)] 139 #[derive(Copy, Clone, Default, Debug)] 140 struct HobHeader { 141 r#type: HobType, 142 length: u16, 143 reserved: u32, 144 } 145 146 #[repr(C)] 147 #[derive(Copy, Clone, Default, Debug)] 148 struct HobHandoffInfoTable { 149 header: HobHeader, 150 version: u32, 151 boot_mode: u32, 152 efi_memory_top: u64, 153 efi_memory_bottom: u64, 154 efi_free_memory_top: u64, 155 efi_free_memory_bottom: u64, 156 efi_end_of_hob_list: u64, 157 } 158 159 #[repr(C)] 160 #[derive(Copy, Clone, Default, Debug)] 161 struct EfiGuid { 162 data1: u32, 163 data2: u16, 164 data3: u16, 165 data4: [u8; 8], 166 } 167 168 #[repr(C)] 169 #[derive(Copy, Clone, Default, Debug)] 170 struct HobResourceDescriptor { 171 header: HobHeader, 172 owner: EfiGuid, 173 resource_type: u32, 174 resource_attribute: u32, 175 physical_start: u64, 176 resource_length: u64, 177 } 178 179 #[repr(C)] 180 #[derive(Copy, Clone, Default, Debug)] 181 struct HobGuidType { 182 header: HobHeader, 183 name: EfiGuid, 184 } 185 186 #[repr(u32)] 187 #[derive(Clone, Copy, Debug)] 188 pub enum PayloadImageType { 189 ExecutablePayload, 190 BzImage, 191 RawVmLinux, 192 } 193 194 impl Default for PayloadImageType { 195 fn default() -> Self { 196 PayloadImageType::ExecutablePayload 197 } 198 } 199 200 #[repr(C)] 201 #[derive(Copy, Clone, Default, Debug)] 202 pub struct PayloadInfo { 203 pub image_type: PayloadImageType, 204 pub entry_point: u64, 205 } 206 207 #[repr(C)] 208 #[derive(Copy, Clone, Default, Debug)] 209 struct TdPayload { 210 guid_type: HobGuidType, 211 payload_info: PayloadInfo, 212 } 213 214 // SAFETY: These data structures only contain a series of integers 215 unsafe impl ByteValued for HobHeader {} 216 unsafe impl ByteValued for HobHandoffInfoTable {} 217 unsafe impl ByteValued for HobResourceDescriptor {} 218 unsafe impl ByteValued for HobGuidType {} 219 unsafe impl ByteValued for PayloadInfo {} 220 unsafe impl ByteValued for TdPayload {} 221 222 pub struct TdHob { 223 start_offset: u64, 224 current_offset: u64, 225 } 226 227 fn align_hob(v: u64) -> u64 { 228 (v + 7) / 8 * 8 229 } 230 231 impl TdHob { 232 fn update_offset<T>(&mut self) { 233 self.current_offset = align_hob(self.current_offset + std::mem::size_of::<T>() as u64) 234 } 235 236 pub fn start(offset: u64) -> TdHob { 237 // Leave a gap to place the HandoffTable at the start as it can only be filled in later 238 let mut hob = TdHob { 239 start_offset: offset, 240 current_offset: offset, 241 }; 242 hob.update_offset::<HobHandoffInfoTable>(); 243 hob 244 } 245 246 pub fn finish(&mut self, mem: &GuestMemoryMmap) -> Result<(), TdvfError> { 247 // Write end 248 let end = HobHeader { 249 r#type: HobType::EndOfHobList, 250 length: std::mem::size_of::<HobHeader>() as u16, 251 reserved: 0, 252 }; 253 info!("Writing HOB end {:x} {:x?}", self.current_offset, end); 254 mem.write_obj(end, GuestAddress(self.current_offset)) 255 .map_err(TdvfError::GuestMemoryWriteHob)?; 256 self.update_offset::<HobHeader>(); 257 258 // Write handoff, delayed as it needs end of HOB list 259 let efi_end_of_hob_list = self.current_offset; 260 let handoff = HobHandoffInfoTable { 261 header: HobHeader { 262 r#type: HobType::Handoff, 263 length: std::mem::size_of::<HobHandoffInfoTable>() as u16, 264 reserved: 0, 265 }, 266 version: 0x9, 267 boot_mode: 0, 268 efi_memory_top: 0, 269 efi_memory_bottom: 0, 270 efi_free_memory_top: 0, 271 efi_free_memory_bottom: 0, 272 efi_end_of_hob_list, 273 }; 274 info!("Writing HOB start {:x} {:x?}", self.start_offset, handoff); 275 mem.write_obj(handoff, GuestAddress(self.start_offset)) 276 .map_err(TdvfError::GuestMemoryWriteHob) 277 } 278 279 pub fn add_resource( 280 &mut self, 281 mem: &GuestMemoryMmap, 282 physical_start: u64, 283 resource_length: u64, 284 resource_type: u32, 285 resource_attribute: u32, 286 ) -> Result<(), TdvfError> { 287 let resource_descriptor = HobResourceDescriptor { 288 header: HobHeader { 289 r#type: HobType::ResourceDescriptor, 290 length: std::mem::size_of::<HobResourceDescriptor>() as u16, 291 reserved: 0, 292 }, 293 owner: EfiGuid::default(), 294 resource_type, 295 resource_attribute, 296 physical_start, 297 resource_length, 298 }; 299 info!( 300 "Writing HOB resource {:x} {:x?}", 301 self.current_offset, resource_descriptor 302 ); 303 mem.write_obj(resource_descriptor, GuestAddress(self.current_offset)) 304 .map_err(TdvfError::GuestMemoryWriteHob)?; 305 self.update_offset::<HobResourceDescriptor>(); 306 Ok(()) 307 } 308 309 pub fn add_memory_resource( 310 &mut self, 311 mem: &GuestMemoryMmap, 312 physical_start: u64, 313 resource_length: u64, 314 ram: bool, 315 ) -> Result<(), TdvfError> { 316 self.add_resource( 317 mem, 318 physical_start, 319 resource_length, 320 if ram { 321 0 /* EFI_RESOURCE_SYSTEM_MEMORY */ 322 } else { 323 0x5 /*EFI_RESOURCE_MEMORY_RESERVED */ 324 }, 325 /* TODO: 326 * QEMU currently fills it in like this: 327 * EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_TESTED 328 * which differs from the spec (due to TDVF implementation issue?) 329 */ 330 0x7, 331 ) 332 } 333 334 pub fn add_mmio_resource( 335 &mut self, 336 mem: &GuestMemoryMmap, 337 physical_start: u64, 338 resource_length: u64, 339 ) -> Result<(), TdvfError> { 340 self.add_resource( 341 mem, 342 physical_start, 343 resource_length, 344 0x1, /* EFI_RESOURCE_MEMORY_MAPPED_IO */ 345 /* 346 * EFI_RESOURCE_ATTRIBUTE_PRESENT | EFI_RESOURCE_ATTRIBUTE_INITIALIZED | EFI_RESOURCE_ATTRIBUTE_UNCACHEABLE 347 */ 348 0x403, 349 ) 350 } 351 352 pub fn add_acpi_table( 353 &mut self, 354 mem: &GuestMemoryMmap, 355 table_content: &[u8], 356 ) -> Result<(), TdvfError> { 357 // We already know the HobGuidType size is 8 bytes multiple, but we 358 // need the total size to be 8 bytes multiple. That is why the ACPI 359 // table size must be 8 bytes multiple as well. 360 let length = std::mem::size_of::<HobGuidType>() as u16 361 + align_hob(table_content.len() as u64) as u16; 362 let hob_guid_type = HobGuidType { 363 header: HobHeader { 364 r#type: HobType::GuidExtension, 365 length, 366 reserved: 0, 367 }, 368 // ACPI_TABLE_HOB_GUID 369 // 0x6a0c5870, 0xd4ed, 0x44f4, {0xa1, 0x35, 0xdd, 0x23, 0x8b, 0x6f, 0xc, 0x8d } 370 name: EfiGuid { 371 data1: 0x6a0c_5870, 372 data2: 0xd4ed, 373 data3: 0x44f4, 374 data4: [0xa1, 0x35, 0xdd, 0x23, 0x8b, 0x6f, 0xc, 0x8d], 375 }, 376 }; 377 info!( 378 "Writing HOB ACPI table {:x} {:x?} {:x?}", 379 self.current_offset, hob_guid_type, table_content 380 ); 381 mem.write_obj(hob_guid_type, GuestAddress(self.current_offset)) 382 .map_err(TdvfError::GuestMemoryWriteHob)?; 383 let current_offset = self.current_offset + std::mem::size_of::<HobGuidType>() as u64; 384 385 // In case the table is quite large, let's make sure we can handle 386 // retrying until everything has been correctly copied. 387 let mut offset: usize = 0; 388 loop { 389 let bytes_written = mem 390 .write( 391 &table_content[offset..], 392 GuestAddress(current_offset + offset as u64), 393 ) 394 .map_err(TdvfError::GuestMemoryWriteHob)?; 395 offset += bytes_written; 396 if offset >= table_content.len() { 397 break; 398 } 399 } 400 self.current_offset += length as u64; 401 402 Ok(()) 403 } 404 405 pub fn add_payload( 406 &mut self, 407 mem: &GuestMemoryMmap, 408 payload_info: PayloadInfo, 409 ) -> Result<(), TdvfError> { 410 let payload = TdPayload { 411 guid_type: HobGuidType { 412 header: HobHeader { 413 r#type: HobType::GuidExtension, 414 length: std::mem::size_of::<TdPayload>() as u16, 415 reserved: 0, 416 }, 417 // HOB_PAYLOAD_INFO_GUID 418 // 0xb96fa412, 0x461f, 0x4be3, {0x8c, 0xd, 0xad, 0x80, 0x5a, 0x49, 0x7a, 0xc0 419 name: EfiGuid { 420 data1: 0xb96f_a412, 421 data2: 0x461f, 422 data3: 0x4be3, 423 data4: [0x8c, 0xd, 0xad, 0x80, 0x5a, 0x49, 0x7a, 0xc0], 424 }, 425 }, 426 payload_info, 427 }; 428 info!( 429 "Writing HOB TD_PAYLOAD {:x} {:x?}", 430 self.current_offset, payload 431 ); 432 mem.write_obj(payload, GuestAddress(self.current_offset)) 433 .map_err(TdvfError::GuestMemoryWriteHob)?; 434 self.update_offset::<TdPayload>(); 435 436 Ok(()) 437 } 438 } 439 440 #[cfg(test)] 441 mod tests { 442 use super::*; 443 444 #[test] 445 #[ignore] 446 fn test_parse_tdvf_sections() { 447 let mut f = std::fs::File::open("tdvf.fd").unwrap(); 448 let sections = parse_tdvf_sections(&mut f).unwrap(); 449 for section in sections { 450 eprintln!("{:x?}", section) 451 } 452 } 453 } 454