tdcall/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Common TDCALL handling for issuing tdcalls and functionality using tdcalls.
5
6#![no_std]
7#![forbid(unsafe_code)]
8
9use hvdef::HV_PAGE_SIZE;
10use memory_range::MemoryRange;
11use tdx_guest_device::protocol::TdReport;
12use thiserror::Error;
13use x86defs::tdx::TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT;
14use x86defs::tdx::TdCallLeaf;
15use x86defs::tdx::TdCallResult;
16use x86defs::tdx::TdCallResultCode;
17use x86defs::tdx::TdGlaVmAndFlags;
18use x86defs::tdx::TdVmCallR10Result;
19use x86defs::tdx::TdVmCallSubFunction;
20use x86defs::tdx::TdgMemPageAcceptRcx;
21use x86defs::tdx::TdgMemPageAttrGpaMappingReadRcxResult;
22use x86defs::tdx::TdgMemPageAttrWriteR8;
23use x86defs::tdx::TdgMemPageAttrWriteRcx;
24use x86defs::tdx::TdgMemPageGpaAttr;
25use x86defs::tdx::TdgMemPageLevel;
26use x86defs::tdx::TdxExtendedFieldCode;
27use x86defs::tdx::TdxGlaListInfo;
28
29/// Input to a tdcall. This is not defined in the TDX specification, but a
30/// contract between callers of this module and this module's handling of
31/// tdcalls.
32#[derive(Debug)]
33pub struct TdcallInput {
34    /// The leaf for the tdcall (eax)
35    pub leaf: TdCallLeaf,
36    /// rcx
37    pub rcx: u64,
38    /// rdx
39    pub rdx: u64,
40    /// r8
41    pub r8: u64,
42    /// r9
43    pub r9: u64,
44    /// r10
45    pub r10: u64,
46    /// r11
47    pub r11: u64,
48    /// r12
49    pub r12: u64,
50    /// r13
51    pub r13: u64,
52    /// r14
53    pub r14: u64,
54    /// r15
55    pub r15: u64,
56}
57
58/// Output from a tdcall. This is not defined in the TDX specification, but a
59/// contract between callers of this module and this module's handling of
60/// tdcalls.
61#[derive(Debug)]
62pub struct TdcallOutput {
63    /// The tdcall result stored in rax.
64    pub rax: TdCallResult,
65    /// rcx
66    pub rcx: u64,
67    /// rdx
68    pub rdx: u64,
69    /// r8,
70    pub r8: u64,
71    /// r10
72    pub r10: u64,
73    /// r11
74    pub r11: u64,
75}
76
77/// Trait to perform tdcalls used by this module.
78pub trait Tdcall {
79    /// Perform a tdcall instruction with the specified inputs.
80    fn tdcall(&mut self, input: TdcallInput) -> TdcallOutput;
81}
82
83/// Perform a tdcall based Hypercall. This is done by issuing a TDG.VP.VMCALL.
84pub fn tdcall_hypercall(
85    call: &mut impl Tdcall,
86    control: hvdef::hypercall::Control,
87    input_gpa: u64,
88    output_gpa: u64,
89) -> Result<(), TdVmCallR10Result> {
90    let input = TdcallInput {
91        leaf: TdCallLeaf::VP_VMCALL,
92        rcx: 0x0d04, // pass RDX, R8, R10, R11
93        rdx: input_gpa,
94        r8: output_gpa,
95        r9: 0,
96        r10: u64::from(control), // hypercall control code
97        r11: 0,
98        r12: 0,
99        r13: 0,
100        r14: 0,
101        r15: 0,
102    };
103
104    let output = call.tdcall(input);
105
106    if output.rax.code() != TdCallResultCode::SUCCESS {
107        // This means something has gone horribly wrong with the TDX module, as
108        // this call should always succeed with hypercall errors returned in
109        // r11.
110        panic!(
111            "unexpected nonzero rax {:x} on tdcall_hypercall",
112            u64::from(output.rax)
113        );
114    }
115
116    // TD.VMCALL for Hypercall passes return code in r11
117    let result = TdVmCallR10Result(output.r11);
118
119    match result {
120        TdVmCallR10Result::SUCCESS => Ok(()),
121        val => Err(val),
122    }
123}
124
125/// Perform a tdcall based MSR read. This is done by issuing a TDG.VP.VMCALL.
126pub fn tdcall_rdmsr(
127    call: &mut impl Tdcall,
128    msr_index: u32,
129    msr_value: &mut u64,
130) -> Result<(), TdVmCallR10Result> {
131    let input = TdcallInput {
132        leaf: TdCallLeaf::VP_VMCALL,
133        rcx: 0x1c00, // pass R10-R12
134        rdx: 0,
135        r8: 0,
136        r9: 0,
137        r10: 0, // must be 0 for ghci call
138        r11: TdVmCallSubFunction::RdMsr as u64,
139        r12: msr_index as u64,
140        r13: 0,
141        r14: 0,
142        r15: 0,
143    };
144
145    let output = call.tdcall(input);
146
147    // This assertion failing means something has gone horribly wrong with the
148    // TDX module, as this call should always succeed with hypercall errors
149    // returned in r10.
150    assert_eq!(
151        output.rax.code(),
152        TdCallResultCode::SUCCESS,
153        "unexpected nonzero rax {:x} returned by tdcall vmcall",
154        u64::from(output.rax)
155    );
156
157    let result = TdVmCallR10Result(output.r10);
158
159    *msr_value = output.r11;
160
161    #[cfg(feature = "tracing")]
162    tracing::trace!(msr_index, msr_value, output.r10, "tdcall_rdmsr");
163
164    match result {
165        TdVmCallR10Result::SUCCESS => Ok(()),
166        val => Err(val),
167    }
168}
169
170/// Perform a tdcall based MSR write. This is done by issuing a TDG.VP.VMCALL.
171pub fn tdcall_wrmsr(
172    call: &mut impl Tdcall,
173    msr_index: u32,
174    msr_value: u64,
175) -> Result<(), TdVmCallR10Result> {
176    let input = TdcallInput {
177        leaf: TdCallLeaf::VP_VMCALL,
178        rcx: 0x3c00, // pass R10-R13
179        rdx: 0,
180        r8: 0,
181        r9: 0,
182        r10: 0, // must be 0 for ghci call
183        r11: TdVmCallSubFunction::WrMsr as u64,
184        r12: msr_index as u64,
185        r13: msr_value,
186        r14: 0,
187        r15: 0,
188    };
189
190    let output = call.tdcall(input);
191
192    // This assertion failing means something has gone horribly wrong with the
193    // TDX module, as this call should always succeed with hypercall errors
194    // returned in r10.
195    assert_eq!(
196        output.rax.code(),
197        TdCallResultCode::SUCCESS,
198        "unexpected nonzero rax {:x} returned by tdcall vmcall",
199        u64::from(output.rax)
200    );
201
202    let result = TdVmCallR10Result(output.r10);
203
204    match result {
205        TdVmCallR10Result::SUCCESS => Ok(()),
206        val => Err(val),
207    }
208}
209
210/// Perform a tdcall based io port write.
211pub fn tdcall_io_out(
212    call: &mut impl Tdcall,
213    port: u16,
214    value: u32,
215    size: u8,
216) -> Result<(), TdVmCallR10Result> {
217    let input = TdcallInput {
218        leaf: TdCallLeaf::VP_VMCALL,
219        rcx: 0xFF00, // pass r10-R15
220        rdx: 0,
221        r8: 0,
222        r9: 0,
223        r10: 0, // must be 0 for ghci call
224        r11: 30,
225        r12: size as u64,
226        r13: 1, // WRITE
227        r14: port as u64,
228        r15: value as u64,
229    };
230
231    let output = call.tdcall(input);
232
233    // This assertion failing means something has gone horribly wrong with the
234    // TDX module, as this call should always succeed with hypercall errors
235    // returned in r10.
236    assert_eq!(
237        output.rax.code(),
238        TdCallResultCode::SUCCESS,
239        "unexpected nonzero rax {:x} returned by tdcall vmcall",
240        u64::from(output.rax)
241    );
242
243    if output.rax.code() != TdCallResultCode::SUCCESS {
244        // This means something has gone horribly wrong with the TDX module, as
245        // this call should always succeed with hypercall errors returned in
246        // r10.
247        panic!(
248            "unexpected nonzero rax {:x} on tdcall_io_out",
249            u64::from(output.rax)
250        );
251    }
252
253    let result = TdVmCallR10Result(output.r10);
254
255    match result {
256        TdVmCallR10Result::SUCCESS => Ok(()),
257        val => Err(val),
258    }
259}
260
261/// Perform a tdcall based io port read.
262pub fn tdcall_io_in(call: &mut impl Tdcall, port: u16, size: u8) -> Result<u32, TdVmCallR10Result> {
263    let input = TdcallInput {
264        leaf: TdCallLeaf::VP_VMCALL,
265        rcx: 0xFF00, // pass r10-R15
266        rdx: 0,
267        r8: 0,
268        r9: 0,
269        r10: 0, // must be 0 for ghci call
270        r11: TdVmCallSubFunction::IoInstr as u64,
271        r12: size as u64,
272        r13: 0, // READ
273        r14: port as u64,
274        r15: 0,
275    };
276
277    let output = call.tdcall(input);
278
279    // This assertion failing means something has gone horribly wrong with the
280    // TDX module, as this call should always succeed with hypercall errors
281    // returned in r10.
282    assert_eq!(
283        output.rax.code(),
284        TdCallResultCode::SUCCESS,
285        "unexpected nonzero rax {:x} returned by tdcall vmcall",
286        u64::from(output.rax)
287    );
288
289    let result = TdVmCallR10Result(output.r10);
290
291    match result {
292        TdVmCallR10Result::SUCCESS => Ok(output.r11 as u32),
293        val => Err(val),
294    }
295}
296
297/// Issue a TDG.MEM.PAGE.ACCEPT call.
298pub fn tdcall_accept_pages(
299    call: &mut impl Tdcall,
300    gpa_page_number: u64,
301    as_large_page: bool,
302) -> Result<(), TdCallResultCode> {
303    #[cfg(feature = "tracing")]
304    tracing::trace!(gpa_page_number, as_large_page, "tdcall_accept_pages");
305
306    let rcx = TdgMemPageAcceptRcx::new()
307        .with_gpa_page_number(gpa_page_number)
308        .with_level(if as_large_page {
309            TdgMemPageLevel::Size2Mb
310        } else {
311            TdgMemPageLevel::Size4k
312        });
313
314    let input = TdcallInput {
315        leaf: TdCallLeaf::MEM_PAGE_ACCEPT,
316        rcx: rcx.into(),
317        rdx: 0,
318        r8: 0,
319        r9: 0,
320        r10: 0,
321        r11: 0,
322        r12: 0,
323        r13: 0,
324        r14: 0,
325        r15: 0,
326    };
327
328    let output = call.tdcall(input);
329
330    match output.rax.code() {
331        TdCallResultCode::SUCCESS => Ok(()),
332        val => Err(val),
333    }
334}
335
336/// The result returned from [`tdcall_page_attr_rd`].
337#[derive(Debug)]
338pub struct TdgPageAttrRdResult {
339    /// The mapping information for the page.
340    pub mapping: TdgMemPageAttrGpaMappingReadRcxResult,
341    /// The attributes for the page.
342    pub attributes: TdgMemPageGpaAttr,
343}
344
345/// Issue a TDG.MEM.PAGE.ATTR.RD call.
346pub fn tdcall_page_attr_rd(
347    call: &mut impl Tdcall,
348    gpa: u64,
349) -> Result<TdgPageAttrRdResult, TdCallResultCode> {
350    #[cfg(feature = "tracing")]
351    tracing::trace!(gpa, "tdcall_page_attr_rd");
352
353    let input = TdcallInput {
354        leaf: TdCallLeaf::MEM_PAGE_ATTR_RD,
355        rcx: gpa,
356        rdx: 0,
357        r8: 0,
358        r9: 0,
359        r10: 0,
360        r11: 0,
361        r12: 0,
362        r13: 0,
363        r14: 0,
364        r15: 0,
365    };
366
367    let output = call.tdcall(input);
368
369    match output.rax.code() {
370        TdCallResultCode::SUCCESS => Ok(TdgPageAttrRdResult {
371            mapping: TdgMemPageAttrGpaMappingReadRcxResult::from(output.rcx),
372            attributes: TdgMemPageGpaAttr::from(output.rdx),
373        }),
374        val => Err(val),
375    }
376}
377
378/// Issue a TDG.MEM.PAGE.ATTR.WR call.
379pub fn tdcall_page_attr_wr(
380    call: &mut impl Tdcall,
381    mapping: TdgMemPageAttrWriteRcx,
382    attributes: TdgMemPageGpaAttr,
383    mask: TdgMemPageAttrWriteR8,
384) -> Result<(), TdCallResultCode> {
385    #[cfg(feature = "tracing")]
386    tracing::trace!(?mapping, ?attributes, ?mask, "tdcall_page_attr_wr");
387
388    let input = TdcallInput {
389        leaf: TdCallLeaf::MEM_PAGE_ATTR_WR,
390        rcx: mapping.into(),
391        rdx: attributes.into(),
392        r8: mask.into(),
393        r9: 0,
394        r10: 0,
395        r11: 0,
396        r12: 0,
397        r13: 0,
398        r14: 0,
399        r15: 0,
400    };
401
402    let output = call.tdcall(input);
403
404    // TODO TDX: RCX and RDX also contain info that could be returned
405
406    match output.rax.code() {
407        TdCallResultCode::SUCCESS => Ok(()),
408        val => Err(val),
409    }
410}
411
412/// Issue a TDG.MEM.PAGE.ATTR.WR call, but perform additional validation that
413/// the attributes were set correctly on debug builds.
414fn set_page_attr(
415    call: &mut impl Tdcall,
416    mapping: TdgMemPageAttrWriteRcx,
417    attributes: TdgMemPageGpaAttr,
418    mask: TdgMemPageAttrWriteR8,
419) -> Result<(), TdCallResultCode> {
420    match tdcall_page_attr_wr(call, mapping, attributes, mask) {
421        Ok(()) => {
422            #[cfg(debug_assertions)]
423            {
424                let result =
425                    tdcall_page_attr_rd(call, mapping.gpa_page_number() * HV_PAGE_SIZE).unwrap();
426                assert_eq!(u64::from(mapping), result.mapping.into());
427                assert_eq!(attributes.l1(), result.attributes.l1());
428                assert_eq!(
429                    attributes.into_bits() & mask.with_reserved(0).into_bits(),
430                    result.attributes.into_bits() & mask.with_reserved(0).into_bits()
431                );
432            }
433
434            Ok(())
435        }
436        Err(e) => Err(e),
437    }
438}
439
440/// The error returned by [`accept_pages`].
441// TODO: why is this an enum with multiple variants--callers don't seem to care.
442// Collapse into a struct, or at least collapse some of the variants?
443#[derive(Debug, Error)]
444pub enum AcceptPagesError {
445    /// Unknown error type.
446    #[error("unknown error: {0:?}")]
447    Unknown(TdCallResultCode),
448    /// Setting page attributes failed after accepting,
449    #[error("setting page attributes failed after accepting: {0:?}")]
450    Attributes(TdCallResultCode),
451    /// Invalid operand
452    #[error("invalid operand: {0:?}")]
453    Invalid(TdCallResultCode),
454    /// Busy Operand
455    #[error("operand busy: {0:?}")]
456    Busy(TdCallResultCode),
457}
458
459/// The page attributes to accept pages with.
460pub enum AcceptPagesAttributes {
461    /// Leave page attributes as is and do not issue TDG.MEM.PAGE.ATTR.WR calls
462    /// after accepting pages.
463    None,
464    /// Issue corresponding TDG.MEM.PAGE.ATTR.WR calls after accepting pages to
465    /// set page attributes to the following values.
466    Set {
467        /// The attributes to set for pages.
468        attributes: TdgMemPageGpaAttr,
469        /// The mask to use when setting the page attributes.
470        mask: TdgMemPageAttrWriteR8,
471    },
472}
473
474/// Accept pages from `range` using [`tdcall_accept_pages`].
475pub fn accept_pages<T: Tdcall>(
476    call: &mut T,
477    range: MemoryRange,
478    attributes: AcceptPagesAttributes,
479) -> Result<(), AcceptPagesError> {
480    #[cfg(feature = "tracing")]
481    tracing::trace!(%range, "accept_pages");
482
483    let set_attributes = |call: &mut T, mapping| -> Result<(), AcceptPagesError> {
484        match attributes {
485            AcceptPagesAttributes::None => Ok(()),
486            AcceptPagesAttributes::Set { attributes, mask } => {
487                set_page_attr(call, mapping, attributes, mask).map_err(AcceptPagesError::Attributes)
488            }
489        }
490    };
491
492    let mut range = range;
493    while !range.is_empty() {
494        // Attempt to accept in large page chunks if possible.
495        if range.start() % x86defs::X64_LARGE_PAGE_SIZE == 0
496            && range.len() >= x86defs::X64_LARGE_PAGE_SIZE
497        {
498            match tdcall_accept_pages(call, range.start_4k_gpn(), true) {
499                Ok(_) => {
500                    set_attributes(
501                        call,
502                        TdgMemPageAttrWriteRcx::new()
503                            .with_gpa_page_number(range.start_4k_gpn())
504                            .with_level(TdgMemPageLevel::Size2Mb),
505                    )?;
506
507                    range =
508                        MemoryRange::new(range.start() + x86defs::X64_LARGE_PAGE_SIZE..range.end());
509                    continue;
510                }
511                Err(e) => match e {
512                    TdCallResultCode::OPERAND_BUSY => return Err(AcceptPagesError::Busy(e)),
513                    TdCallResultCode::OPERAND_INVALID => return Err(AcceptPagesError::Invalid(e)),
514                    TdCallResultCode::PAGE_ALREADY_ACCEPTED => {
515                        panic!("page {} already accepted", range.start_4k_gpn());
516                    }
517                    TdCallResultCode::PAGE_SIZE_MISMATCH => {
518                        #[cfg(feature = "tracing")]
519                        tracing::trace!("accept pages size mismatch returned");
520                    }
521                    _ => return Err(AcceptPagesError::Unknown(e)),
522                },
523            }
524        }
525
526        // Accept in 4k size pages
527        match tdcall_accept_pages(call, range.start_4k_gpn(), false) {
528            Ok(_) => {
529                set_attributes(
530                    call,
531                    TdgMemPageAttrWriteRcx::new()
532                        .with_gpa_page_number(range.start_4k_gpn())
533                        .with_level(TdgMemPageLevel::Size4k),
534                )?;
535
536                range = MemoryRange::new(range.start() + HV_PAGE_SIZE..range.end());
537            }
538            Err(e) => match e {
539                TdCallResultCode::OPERAND_BUSY => return Err(AcceptPagesError::Busy(e)),
540                TdCallResultCode::OPERAND_INVALID => return Err(AcceptPagesError::Invalid(e)),
541                TdCallResultCode::PAGE_ALREADY_ACCEPTED => {
542                    panic!("page {} already accepted", range.start_4k_gpn());
543                }
544                _ => return Err(AcceptPagesError::Unknown(e)),
545            },
546        }
547    }
548
549    Ok(())
550}
551
552/// Set page attributes from `range` using
553/// [`tdcall_page_attr_wr`].
554///
555/// This will attempt to set attributes in 2MB chunks if possible.
556pub fn set_page_attributes(
557    call: &mut impl Tdcall,
558    range: MemoryRange,
559    attributes: TdgMemPageGpaAttr,
560    mask: TdgMemPageAttrWriteR8,
561) -> Result<(), TdCallResultCode> {
562    #[cfg(feature = "tracing")]
563    tracing::trace!(
564        %range,
565        ?attributes,
566        ?mask,
567        "set_page_attributes"
568    );
569
570    let mut range = range;
571    while !range.is_empty() {
572        // Attempt to set in large page chunks if possible.
573        if range.start() % x86defs::X64_LARGE_PAGE_SIZE == 0
574            && range.len() >= x86defs::X64_LARGE_PAGE_SIZE
575        {
576            let mapping = TdgMemPageAttrWriteRcx::new()
577                .with_gpa_page_number(range.start_4k_gpn())
578                .with_level(TdgMemPageLevel::Size2Mb);
579
580            match set_page_attr(call, mapping, attributes, mask) {
581                Ok(()) => {
582                    range =
583                        MemoryRange::new(range.start() + x86defs::X64_LARGE_PAGE_SIZE..range.end());
584                    continue;
585                }
586                Err(TdCallResultCode::PAGE_SIZE_MISMATCH) => {
587                    #[cfg(feature = "tracing")]
588                    tracing::trace!("set pages attr size mismatch returned");
589                }
590                Err(e) => return Err(e),
591            }
592        }
593
594        // Set in 4k size pages
595        let mapping = TdgMemPageAttrWriteRcx::new()
596            .with_gpa_page_number(range.start_4k_gpn())
597            .with_level(TdgMemPageLevel::Size4k);
598
599        match set_page_attr(call, mapping, attributes, mask) {
600            Ok(()) => range = MemoryRange::new(range.start() + HV_PAGE_SIZE..range.end()),
601            Err(e) => return Err(e),
602        }
603    }
604
605    Ok(())
606}
607
608/// Issue a map gpa call to change page visibility for accepted pages via a
609/// TDG.VP.VMCALL.
610///
611/// `gpa` should specify the gpa for the address to change visibility for. The
612/// shared gpa boundary will be added or masked off as required.
613///
614/// `len` should specify the length of the region in bytes to change visibility
615/// for.
616///
617/// `host_visible` should specify whether the region should be host visible or
618/// private.
619pub fn tdcall_map_gpa(
620    call: &mut impl Tdcall,
621    range: MemoryRange,
622    host_visible: bool,
623) -> Result<(), TdVmCallR10Result> {
624    let mut gpa = if host_visible {
625        range.start() | TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT
626    } else {
627        range.start() & !TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT
628    };
629    let end = gpa + range.len();
630
631    while gpa < end {
632        let input = TdcallInput {
633            leaf: TdCallLeaf::VP_VMCALL,
634            rcx: 0x3c00, // pass R10-R13
635            rdx: 0,
636            r8: 0,
637            r9: 0,
638            r10: 0, // must be 0 for ghci call
639            r11: TdVmCallSubFunction::MapGpa as u64,
640            r12: gpa,
641            r13: end - gpa,
642            r14: 0,
643            r15: 0,
644        };
645
646        let output = call.tdcall(input);
647
648        // This assertion failing means something has gone horribly wrong with the
649        // TDX module, as this call should always succeed with hypercall errors
650        // returned in r10.
651        assert_eq!(
652            output.rax.code(),
653            TdCallResultCode::SUCCESS,
654            "unexpected nonzero rax {:x} returned by tdcall vmcall",
655            u64::from(output.rax)
656        );
657
658        let result = TdVmCallR10Result(output.r10);
659
660        match result {
661            TdVmCallR10Result::SUCCESS => gpa = end,
662            TdVmCallR10Result::RETRY => gpa = output.r11,
663            val => return Err(val),
664        }
665    }
666
667    Ok(())
668}
669
670/// Issue a TDG.VP.WR call.
671///
672/// `field_code` is the field code to use for the call.
673///
674/// `value` is the value to set, with `mask` being the mask controlling which
675/// bits will be set from `value`, as specified by the TDX API.
676///
677/// Returns the old value of the field.
678pub fn tdcall_vp_wr(
679    call: &mut impl Tdcall,
680    field_code: TdxExtendedFieldCode,
681    value: u64,
682    mask: u64,
683) -> Result<u64, TdCallResult> {
684    let input = TdcallInput {
685        leaf: TdCallLeaf::VP_WR,
686        rcx: 0,
687        rdx: field_code.into(),
688        r8: value,
689        r9: mask,
690        r10: 0,
691        r11: 0,
692        r12: 0,
693        r13: 0,
694        r14: 0,
695        r15: 0,
696    };
697
698    let output = call.tdcall(input);
699
700    match output.rax.code() {
701        TdCallResultCode::SUCCESS => Ok(output.r8),
702        _ => Err(output.rax),
703    }
704}
705
706/// Issue a TDG.VP.RD call.
707///
708/// `field_code` is the field code to use for the call.
709pub fn tdcall_vp_rd(
710    call: &mut impl Tdcall,
711    field_code: TdxExtendedFieldCode,
712) -> Result<u64, TdCallResult> {
713    let input = TdcallInput {
714        leaf: TdCallLeaf::VP_RD,
715        rcx: 0,
716        rdx: field_code.into(),
717        r8: 0,
718        r9: 0,
719        r10: 0,
720        r11: 0,
721        r12: 0,
722        r13: 0,
723        r14: 0,
724        r15: 0,
725    };
726
727    let output = call.tdcall(input);
728
729    match output.rax.code() {
730        TdCallResultCode::SUCCESS => Ok(output.r8),
731        _ => Err(output.rax),
732    }
733}
734
735/// Issue a TDG.VP.INVGLA call.
736pub fn tdcall_vp_invgla(
737    call: &mut impl Tdcall,
738    gla_flags: TdGlaVmAndFlags,
739    gla_info: TdxGlaListInfo,
740) -> Result<(), TdCallResult> {
741    let input = TdcallInput {
742        leaf: TdCallLeaf::VP_INVGLA,
743        rcx: gla_flags.into(),
744        rdx: gla_info.into(),
745        r8: 0,
746        r9: 0,
747        r10: 0,
748        r11: 0,
749        r12: 0,
750        r13: 0,
751        r14: 0,
752        r15: 0,
753    };
754
755    let output = call.tdcall(input);
756
757    match output.rax.code() {
758        TdCallResultCode::SUCCESS => Ok(()),
759        _ => Err(output.rax),
760    }
761}
762
763#[repr(C, align(64))]
764struct AddlData {
765    /// Report data buffer for TDG.MR.REPORT call.
766    pub report_data: [u8; 64],
767}
768
769/// Issue a TDG.MR.REPORT call with empty additional data.
770pub fn tdcall_mr_report(call: &mut impl Tdcall, report: &mut TdReport) -> Result<(), TdCallResult> {
771    let addl_data = AddlData {
772        report_data: [0; 64],
773    };
774
775    let input = TdcallInput {
776        leaf: TdCallLeaf::MR_REPORT,
777        rcx: core::ptr::from_mut::<TdReport>(report) as u64,
778        rdx: 0,
779        r8: 0,
780        r9: 0,
781        r10: 0,
782        r11: 0,
783        r12: addl_data.report_data.as_ptr() as u64,
784        r13: 0,
785        r14: 0,
786        r15: 0,
787    };
788
789    let output = call.tdcall(input);
790
791    match output.rax.code() {
792        TdCallResultCode::SUCCESS => Ok(()),
793        _ => Err(output.rax),
794    }
795}