tdx_guest/
tdvmcall.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright(c) 2023-2024 Intel Corporation.
3
4//! The TDVMCALL helps invoke services from the host VMM. From the perspective of the host VMM, the TDVMCALL is a trap-like, VM exit into
5//! the host VMM, reported via the SEAMRET instruction flow.
6//!
7//! By design, after the SEAMRET, the host VMM services the request specified in the parameters
8//! passed by the TD during the TDG.VP.VMCALL (that are passed via SEAMRET to the VMM), then
9//! resumes the TD via a SEAMCALL [TDH.VP.ENTER] invocation.
10extern crate alloc;
11
12use alloc::fmt;
13use core::fmt::Write;
14
15use bitflags::bitflags;
16use x86_64::{
17    registers::rflags::{self, RFlags},
18    structures::port::PortRead,
19};
20
21use crate::asm::asm_td_vmcall;
22
23/// TDVMCALL Instruction Leaf Numbers Definition.
24#[repr(u64)]
25pub enum TdVmcallNum {
26    Cpuid = 0x0000a,
27    Hlt = 0x0000c,
28    Io = 0x0001e,
29    Rdmsr = 0x0001f,
30    Wrmsr = 0x00020,
31    RequestMmio = 0x00030,
32    Wbinvd = 0x00036,
33    GetTdVmcallInfo = 0x10000,
34    Mapgpa = 0x10001,
35    GetQuote = 0x10002,
36    SetupEventNotifyInterrupt = 0x10004,
37    Service = 0x10005,
38}
39
40const SERIAL_IO_PORT: u16 = 0x3F8;
41const SERIAL_LINE_STS: u16 = 0x3FD;
42const IO_READ: u64 = 0;
43const IO_WRITE: u64 = 1;
44
45#[derive(Debug, PartialEq)]
46pub enum TdVmcallError {
47    /// TDCALL[TDG.VP.VMCALL] sub-function invocation must be retried.
48    TdxRetry,
49    /// Invalid operand to TDG.VP.VMCALL sub-function.
50    TdxOperandInvalid,
51    /// GPA already mapped.
52    TdxGpaInuse,
53    /// Operand (address) aligned error.
54    TdxAlignError,
55    Other,
56}
57
58impl From<u64> for TdVmcallError {
59    fn from(val: u64) -> Self {
60        match val {
61            0x1 => Self::TdxRetry,
62            0x8000_0000_0000_0000 => Self::TdxOperandInvalid,
63            0x8000_0000_0000_0001 => Self::TdxGpaInuse,
64            0x8000_0000_0000_0002 => Self::TdxAlignError,
65            _ => Self::Other,
66        }
67    }
68}
69
70#[repr(C)]
71#[derive(Default)]
72pub(crate) struct TdVmcallArgs {
73    r10: u64,
74    r11: u64,
75    r12: u64,
76    r13: u64,
77    r14: u64,
78    r15: u64,
79}
80
81#[repr(C)]
82#[derive(Debug, Default)]
83pub struct CpuIdInfo {
84    pub eax: usize,
85    pub ebx: usize,
86    pub ecx: usize,
87    pub edx: usize,
88}
89
90pub enum Direction {
91    In,
92    Out,
93}
94
95pub enum Operand {
96    Dx,
97    Immediate,
98}
99
100pub enum IoSize {
101    Size1 = 1,
102    Size2 = 2,
103    Size4 = 4,
104    Size8 = 8,
105}
106
107pub fn cpuid(eax: u32, ecx: u32) -> Result<CpuIdInfo, TdVmcallError> {
108    let mut args = TdVmcallArgs {
109        r11: TdVmcallNum::Cpuid as u64,
110        r12: eax as u64,
111        r13: ecx as u64,
112        ..Default::default()
113    };
114    td_vmcall(&mut args)?;
115    Ok(CpuIdInfo {
116        eax: args.r12 as usize,
117        ebx: args.r13 as usize,
118        ecx: args.r14 as usize,
119        edx: args.r15 as usize,
120    })
121}
122
123pub fn hlt() {
124    let interrupt_blocked = !rflags::read().contains(RFlags::INTERRUPT_FLAG);
125    let mut args = TdVmcallArgs {
126        r11: TdVmcallNum::Hlt as u64,
127        r12: interrupt_blocked as u64,
128        ..Default::default()
129    };
130    let _ = td_vmcall(&mut args);
131}
132
133/// # Safety
134/// Make sure the index is valid.
135pub unsafe fn rdmsr(index: u32) -> Result<u64, TdVmcallError> {
136    let mut args = TdVmcallArgs {
137        r11: TdVmcallNum::Rdmsr as u64,
138        r12: index as u64,
139        ..Default::default()
140    };
141    td_vmcall(&mut args)?;
142    Ok(args.r11)
143}
144
145/// # Safety
146/// Make sure the index and the corresponding value are valid.
147pub unsafe fn wrmsr(index: u32, value: u64) -> Result<(), TdVmcallError> {
148    let mut args = TdVmcallArgs {
149        r11: TdVmcallNum::Wrmsr as u64,
150        r12: index as u64,
151        r13: value,
152        ..Default::default()
153    };
154    td_vmcall(&mut args)
155}
156
157/// Used to help perform WBINVD or WBNOINVD operation.
158/// - cache_operation: 0: WBINVD, 1: WBNOINVD
159pub fn perform_cache_operation(cache_operation: u64) -> Result<(), TdVmcallError> {
160    let mut args = TdVmcallArgs {
161        r11: TdVmcallNum::Wbinvd as u64,
162        r12: cache_operation,
163        ..Default::default()
164    };
165    td_vmcall(&mut args)
166}
167
168/// # Safety
169/// Make sure the mmio address is valid.
170pub unsafe fn read_mmio(size: IoSize, mmio_gpa: u64) -> Result<u64, TdVmcallError> {
171    let mut args = TdVmcallArgs {
172        r11: TdVmcallNum::RequestMmio as u64,
173        r12: size as u64,
174        r13: 0,
175        r14: mmio_gpa,
176        ..Default::default()
177    };
178    td_vmcall(&mut args)?;
179    Ok(args.r11)
180}
181
182/// # Safety
183/// Make sure the mmio address is valid.
184pub unsafe fn write_mmio(size: IoSize, mmio_gpa: u64, data: u64) -> Result<(), TdVmcallError> {
185    let mut args = TdVmcallArgs {
186        r11: TdVmcallNum::RequestMmio as u64,
187        r12: size as u64,
188        r13: 1,
189        r14: mmio_gpa,
190        r15: data,
191        ..Default::default()
192    };
193    td_vmcall(&mut args)
194}
195
196/// MapGPA TDG.VP.VMCALL is used to help request the host VMM to map a GPA range as private
197/// or shared-memory mappings. This API may also be used to convert page mappings from
198/// private to shared. The GPA range passed in this operation can indicate if the mapping is
199/// requested for a shared or private memory via the GPA.Shared bit in the start address.
200pub fn map_gpa(gpa: u64, size: u64) -> Result<(), (u64, TdVmcallError)> {
201    let mut args = TdVmcallArgs {
202        r11: TdVmcallNum::Mapgpa as u64,
203        r12: gpa,
204        r13: size,
205        ..Default::default()
206    };
207    td_vmcall(&mut args).map_err(|e| (args.r11, e))
208}
209
210/// GetQuote TDG.VP.VMCALL is a doorbell-like interface used to help send a message to the
211/// host VMM to queue operations that tend to be long-running operations.
212///
213/// GetQuote is designed to invoke a request to generate a TD-Quote signing by a service hosting TD-Quoting
214/// Enclave operating in the host environment for a TD Report passed as a parameter by the TD.
215///
216/// TDREPORT_STRUCT is a memory operand intended to be sent via the GetQuote
217/// TDG.VP.VMCALL to indicate the asynchronous service requested.
218pub fn get_quote(shared_gpa: u64, size: u64) -> Result<(), TdVmcallError> {
219    let mut args = TdVmcallArgs {
220        r11: TdVmcallNum::GetQuote as u64,
221        r12: shared_gpa,
222        r13: size,
223        ..Default::default()
224    };
225    td_vmcall(&mut args)
226}
227
228/// The guest TD may request that the host VMM specify which interrupt vector to use as an event-notify vector.
229///
230/// This is designed as an untrusted operation; thus, the TD OS should be designed not to use the event notification for trusted operations.
231///
232/// Example of an operation that can use the event notify is the host VMM signaling a device removal to the TD, in
233/// response to which a TD may unload a device driver.
234///
235/// The host VMM should use SEAMCALL [TDWRVPS] leaf to inject an interrupt at the requestedinterrupt vector into the TD VCPU that executed TDG.VP.VMCALL
236/// <SetupEventNotifyInterrupt> via the posted-interrupt descriptor.
237pub fn setup_event_notify_interrupt(interrupt_vector: u64) -> Result<(), TdVmcallError> {
238    let mut args = TdVmcallArgs {
239        r11: TdVmcallNum::SetupEventNotifyInterrupt as u64,
240        r12: interrupt_vector,
241        ..Default::default()
242    };
243    td_vmcall(&mut args)
244}
245
246/// GetTdVmCallInfo TDG.VP.VMCALL is used to help request the host VMM enumerate which
247/// TDG.VP.VMCALLs are supported.
248pub fn get_tdvmcall_info(interrupt_vector: u64) -> Result<(), TdVmcallError> {
249    let mut args = TdVmcallArgs {
250        r11: TdVmcallNum::GetTdVmcallInfo as u64,
251        // This register is reserved to extend TDG.VP.VMCALL enumeration in future versions.
252        r12: 0,
253        ..Default::default()
254    };
255    td_vmcall(&mut args)
256}
257
258/// In Service TD scenario, there is a need to define interfaces for the command/response that
259/// may have long latency, such as communicating with local device via Secure Protocol and Data
260/// Model (SPDM), communicating with remote platform via Transport Layer Security (TLS)
261/// Protocol, or communicating with a Quoting Enclave (QE) on attestation or mutual
262/// authentication.
263///
264/// There is also needed that the VMM may notify a service TD to do some actions, such as
265/// Migration TD (MigTD).
266///
267/// We define Command/Response Buffer (CRB) DMA interface.
268///
269/// Inputs:
270/// - shared_gpa_input: Shared 4KB aligned GPA as input – the memory contains a Command.
271///   It could be more than one 4K pages.
272/// - shared_gpa_output: Shared 4KB aligned GPA as output – the memory contains a Response.
273///   It could be more than one 4K pages.
274/// - interrupt_vector: Event notification interrupt vector - (valid values 32~255) selected by TD.
275///   0: blocking action. VMM need get response then return.
276///   1~31: Reserved. Should not be used.
277///   32~255: Non-block action. VMM can return immediately and signal the interrupt vector when the response is ready.
278///   VMM should inject interrupt vector into the TD VCPU that executed TDG.VP.VMCALL<Service>.
279/// - time_out: Timeout– Maximum wait time for the command and response. 0 means infinite wait.
280pub fn get_td_service(
281    shared_gpa_input: u64,
282    shared_gpa_output: u64,
283    interrupt_vector: u64,
284    time_out: u64,
285) -> Result<(), TdVmcallError> {
286    let mut args = TdVmcallArgs {
287        r11: TdVmcallNum::Service as u64,
288        r12: shared_gpa_input,
289        r13: shared_gpa_output,
290        r14: interrupt_vector,
291        r15: time_out,
292        ..Default::default()
293    };
294    td_vmcall(&mut args)
295}
296
297macro_rules! io_read {
298    ($port:expr, $ty:ty) => {{
299        let mut args = TdVmcallArgs {
300            r11: TdVmcallNum::Io as u64,
301            r12: core::mem::size_of::<$ty>() as u64,
302            r13: IO_READ,
303            r14: $port as u64,
304            ..Default::default()
305        };
306        td_vmcall(&mut args)?;
307        Ok(args.r11 as u32)
308    }};
309}
310
311pub fn io_read(size: IoSize, port: u16) -> Result<u32, TdVmcallError> {
312    match size {
313        IoSize::Size1 => io_read!(port, u8),
314        IoSize::Size2 => io_read!(port, u16),
315        IoSize::Size4 => io_read!(port, u32),
316        _ => unreachable!(),
317    }
318}
319
320macro_rules! io_write {
321    ($port:expr, $byte:expr, $size:expr) => {{
322        let mut args = TdVmcallArgs {
323            r11: TdVmcallNum::Io as u64,
324            r12: core::mem::size_of_val(&$byte) as u64,
325            r13: IO_WRITE,
326            r14: $port as u64,
327            r15: $byte as u64,
328            ..Default::default()
329        };
330        td_vmcall(&mut args)
331    }};
332}
333
334pub fn io_write(size: IoSize, port: u16, byte: u32) -> Result<(), TdVmcallError> {
335    match size {
336        IoSize::Size1 => io_write!(port, byte as u8, u8),
337        IoSize::Size2 => io_write!(port, byte as u16, u16),
338        IoSize::Size4 => io_write!(port, byte, u32),
339        _ => unreachable!(),
340    }
341}
342
343fn td_vmcall(args: &mut TdVmcallArgs) -> Result<(), TdVmcallError> {
344    let result = unsafe { asm_td_vmcall(args) };
345    match result {
346        0 => Ok(()),
347        _ => Err(result.into()),
348    }
349}
350
351bitflags! {
352    /// LineSts: Line Status
353    struct LineSts: u8 {
354        const INPUT_FULL = 1;
355        const OUTPUT_EMPTY = 1 << 5;
356    }
357}
358
359fn read_line_sts() -> LineSts {
360    LineSts::from_bits_truncate(unsafe { PortRead::read_from_port(SERIAL_LINE_STS) })
361}
362
363struct Serial;
364
365impl Serial {
366    fn serial_write_byte(byte: u8) {
367        match byte {
368            // Backspace/Delete
369            8 | 0x7F => {
370                while !read_line_sts().contains(LineSts::OUTPUT_EMPTY) {}
371                io_write!(SERIAL_IO_PORT, 8, u8).unwrap();
372                while !read_line_sts().contains(LineSts::OUTPUT_EMPTY) {}
373                io_write!(SERIAL_IO_PORT, b' ', u8).unwrap();
374                while !read_line_sts().contains(LineSts::OUTPUT_EMPTY) {}
375                io_write!(SERIAL_IO_PORT, 8, u8).unwrap();
376            }
377            _ => {
378                while !read_line_sts().contains(LineSts::OUTPUT_EMPTY) {}
379                io_write!(SERIAL_IO_PORT, byte, u8).unwrap();
380            }
381        }
382    }
383}
384
385impl Write for Serial {
386    fn write_str(&mut self, s: &str) -> fmt::Result {
387        for &c in s.as_bytes() {
388            Serial::serial_write_byte(c);
389        }
390        Ok(())
391    }
392}
393
394pub fn print(args: fmt::Arguments) {
395    Serial
396        .write_fmt(args)
397        .expect("Failed to write to serial port");
398}
399
400#[macro_export]
401macro_rules! serial_print {
402    ($fmt: literal $(, $($arg: tt)+)?) => {
403        $crate::tdvmcall::print(format_args!($fmt $(, $($arg)+)?));
404    }
405}
406
407#[macro_export]
408macro_rules! serial_println {
409    ($fmt: literal $(, $($arg: tt)+)?) => {
410        $crate::tdvmcall::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?))
411    }
412}