xref: /cloud-hypervisor/arch/src/x86_64/tdx/mod.rs (revision 7d7bfb2034001d4cb15df2ddc56d2d350c8da30f)
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