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