Skip to main content

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::hypercall::HypercallOutput;
10use memory_range::AlignedSubranges;
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::TdgMemPageReleaseRcx;
28use x86defs::tdx::TdgMemPageReleaseRcxResult;
29use x86defs::tdx::TdgVmRdResult;
30use x86defs::tdx::TdxExtendedFieldCode;
31use x86defs::tdx::TdxGlaListInfo;
32
33/// Input to a tdcall. This is not defined in the TDX specification, but a
34/// contract between callers of this module and this module's handling of
35/// tdcalls.
36#[derive(Debug)]
37pub struct TdcallInput {
38    /// The leaf for the tdcall (eax)
39    pub leaf: TdCallLeaf,
40    /// rcx
41    pub rcx: u64,
42    /// rdx
43    pub rdx: u64,
44    /// r8
45    pub r8: u64,
46    /// r9
47    pub r9: u64,
48    /// r10
49    pub r10: u64,
50    /// r11
51    pub r11: u64,
52    /// r12
53    pub r12: u64,
54    /// r13
55    pub r13: u64,
56    /// r14
57    pub r14: u64,
58    /// r15
59    pub r15: u64,
60}
61
62/// Output from a tdcall. This is not defined in the TDX specification, but a
63/// contract between callers of this module and this module's handling of
64/// tdcalls.
65#[derive(Debug)]
66pub struct TdcallOutput {
67    /// The tdcall result stored in rax.
68    pub rax: TdCallResult,
69    /// rcx
70    pub rcx: u64,
71    /// rdx
72    pub rdx: u64,
73    /// r8,
74    pub r8: u64,
75    /// r10
76    pub r10: u64,
77    /// r11
78    pub r11: u64,
79}
80
81/// Trait to perform tdcalls used by this module.
82pub trait Tdcall {
83    /// Perform a tdcall instruction with the specified inputs.
84    fn tdcall(&mut self, input: TdcallInput) -> TdcallOutput;
85}
86
87/// Perform a tdcall based Hypercall. This is done by issuing a TDG.VP.VMCALL.
88pub fn tdcall_hypercall(
89    call: &mut impl Tdcall,
90    control: hvdef::hypercall::Control,
91    input_gpa: u64,
92    output_gpa: u64,
93) -> HypercallOutput {
94    let input = TdcallInput {
95        leaf: TdCallLeaf::VP_VMCALL,
96        rcx: 0x0d04, // pass RDX, R8, R10, R11
97        rdx: input_gpa,
98        r8: output_gpa,
99        r9: 0,
100        r10: u64::from(control), // hypercall control code
101        r11: 0,
102        r12: 0,
103        r13: 0,
104        r14: 0,
105        r15: 0,
106    };
107
108    let output = call.tdcall(input);
109
110    if output.rax.code() != TdCallResultCode::SUCCESS {
111        // This means something has gone horribly wrong with the TDX module, as
112        // this call should always succeed with hypercall errors returned in
113        // r11.
114        panic!(
115            "unexpected nonzero rax {:x} on tdcall_hypercall",
116            u64::from(output.rax)
117        );
118    }
119
120    // TD.VMCALL for Hypercall passes return code in r11
121    HypercallOutput::from(output.r11)
122}
123
124/// Perform a tdcall based MSR read. This is done by issuing a TDG.VP.VMCALL.
125pub fn tdcall_rdmsr(
126    call: &mut impl Tdcall,
127    msr_index: u32,
128    msr_value: &mut u64,
129) -> Result<(), TdVmCallR10Result> {
130    let input = TdcallInput {
131        leaf: TdCallLeaf::VP_VMCALL,
132        rcx: 0x1c00, // pass R10-R12
133        rdx: 0,
134        r8: 0,
135        r9: 0,
136        r10: 0, // must be 0 for ghci call
137        r11: TdVmCallSubFunction::RdMsr as u64,
138        r12: msr_index as u64,
139        r13: 0,
140        r14: 0,
141        r15: 0,
142    };
143
144    let output = call.tdcall(input);
145
146    // This assertion failing means something has gone horribly wrong with the
147    // TDX module, as this call should always succeed with hypercall errors
148    // returned in r10.
149    assert_eq!(
150        output.rax.code(),
151        TdCallResultCode::SUCCESS,
152        "unexpected nonzero rax {:x} returned by tdcall vmcall",
153        u64::from(output.rax)
154    );
155
156    let result = TdVmCallR10Result(output.r10);
157
158    *msr_value = output.r11;
159
160    #[cfg(feature = "tracing")]
161    tracing::trace!(msr_index, msr_value, output.r10, "tdcall_rdmsr");
162
163    match result {
164        TdVmCallR10Result::SUCCESS => Ok(()),
165        val => Err(val),
166    }
167}
168
169/// Perform a tdcall based MSR write. This is done by issuing a TDG.VP.VMCALL.
170pub fn tdcall_wrmsr(
171    call: &mut impl Tdcall,
172    msr_index: u32,
173    msr_value: u64,
174) -> Result<(), TdVmCallR10Result> {
175    let input = TdcallInput {
176        leaf: TdCallLeaf::VP_VMCALL,
177        rcx: 0x3c00, // pass R10-R13
178        rdx: 0,
179        r8: 0,
180        r9: 0,
181        r10: 0, // must be 0 for ghci call
182        r11: TdVmCallSubFunction::WrMsr as u64,
183        r12: msr_index as u64,
184        r13: msr_value,
185        r14: 0,
186        r15: 0,
187    };
188
189    let output = call.tdcall(input);
190
191    // This assertion failing means something has gone horribly wrong with the
192    // TDX module, as this call should always succeed with hypercall errors
193    // returned in r10.
194    assert_eq!(
195        output.rax.code(),
196        TdCallResultCode::SUCCESS,
197        "unexpected nonzero rax {:x} returned by tdcall vmcall",
198        u64::from(output.rax)
199    );
200
201    let result = TdVmCallR10Result(output.r10);
202
203    match result {
204        TdVmCallR10Result::SUCCESS => Ok(()),
205        val => Err(val),
206    }
207}
208
209/// Perform a tdcall based io port write.
210pub fn tdcall_io_out(
211    call: &mut impl Tdcall,
212    port: u16,
213    value: u32,
214    size: u8,
215) -> Result<(), TdVmCallR10Result> {
216    let input = TdcallInput {
217        leaf: TdCallLeaf::VP_VMCALL,
218        rcx: 0xFF00, // pass r10-R15
219        rdx: 0,
220        r8: 0,
221        r9: 0,
222        r10: 0, // must be 0 for ghci call
223        r11: 30,
224        r12: size as u64,
225        r13: 1, // WRITE
226        r14: port as u64,
227        r15: value as u64,
228    };
229
230    let output = call.tdcall(input);
231
232    // This assertion failing means something has gone horribly wrong with the
233    // TDX module, as this call should always succeed with hypercall errors
234    // returned in r10.
235    assert_eq!(
236        output.rax.code(),
237        TdCallResultCode::SUCCESS,
238        "unexpected nonzero rax {:x} returned by tdcall vmcall",
239        u64::from(output.rax)
240    );
241
242    if output.rax.code() != TdCallResultCode::SUCCESS {
243        // This means something has gone horribly wrong with the TDX module, as
244        // this call should always succeed with hypercall errors returned in
245        // r10.
246        panic!(
247            "unexpected nonzero rax {:x} on tdcall_io_out",
248            u64::from(output.rax)
249        );
250    }
251
252    let result = TdVmCallR10Result(output.r10);
253
254    match result {
255        TdVmCallR10Result::SUCCESS => Ok(()),
256        val => Err(val),
257    }
258}
259
260/// Perform a tdcall based io port read.
261pub fn tdcall_io_in(call: &mut impl Tdcall, port: u16, size: u8) -> Result<u32, TdVmCallR10Result> {
262    let input = TdcallInput {
263        leaf: TdCallLeaf::VP_VMCALL,
264        rcx: 0xFF00, // pass r10-R15
265        rdx: 0,
266        r8: 0,
267        r9: 0,
268        r10: 0, // must be 0 for ghci call
269        r11: TdVmCallSubFunction::IoInstr as u64,
270        r12: size as u64,
271        r13: 0, // READ
272        r14: port as u64,
273        r15: 0,
274    };
275
276    let output = call.tdcall(input);
277
278    // This assertion failing means something has gone horribly wrong with the
279    // TDX module, as this call should always succeed with hypercall errors
280    // returned in r10.
281    assert_eq!(
282        output.rax.code(),
283        TdCallResultCode::SUCCESS,
284        "unexpected nonzero rax {:x} returned by tdcall vmcall",
285        u64::from(output.rax)
286    );
287
288    let result = TdVmCallR10Result(output.r10);
289
290    match result {
291        TdVmCallR10Result::SUCCESS => Ok(output.r11 as u32),
292        val => Err(val),
293    }
294}
295
296/// Issue a TDG.MEM.PAGE.ACCEPT call.
297pub fn tdcall_accept_pages(
298    call: &mut impl Tdcall,
299    gpa_page_number: u64,
300    as_large_page: bool,
301) -> Result<(), TdCallResultCode> {
302    #[cfg(feature = "tracing")]
303    tracing::trace!(gpa_page_number, as_large_page, "tdcall_accept_pages");
304
305    let rcx = TdgMemPageAcceptRcx::new()
306        .with_gpa_page_number(gpa_page_number)
307        .with_level(if as_large_page {
308            TdgMemPageLevel::Size2Mb
309        } else {
310            TdgMemPageLevel::Size4k
311        });
312
313    let input = TdcallInput {
314        leaf: TdCallLeaf::MEM_PAGE_ACCEPT,
315        rcx: rcx.into(),
316        rdx: 0,
317        r8: 0,
318        r9: 0,
319        r10: 0,
320        r11: 0,
321        r12: 0,
322        r13: 0,
323        r14: 0,
324        r15: 0,
325    };
326
327    let output = call.tdcall(input);
328
329    match output.rax.code() {
330        TdCallResultCode::SUCCESS => Ok(()),
331        val => Err(val),
332    }
333}
334
335/// The error information returned from [`tdcall_release_page`].
336#[derive(Debug, Error)]
337pub enum TdgPageReleaseError {
338    /// Unknown error type.
339    #[error("unknown error: {0:?}")]
340    Unknown(TdCallResultCode),
341    /// Page not allocated to TD's GPA Space
342    #[error("page is not allocated to GPA space: {0:?}")]
343    NotAllocated(TdCallResultCode),
344    /// Page is in an invalid entry state
345    #[error("page has invalid state [pending: {pending:?}, mmio: {mmio:?}]: {result:?}")]
346    EntryStateInvalid {
347        /// Associated TDX Result Code
348        result: TdCallResultCode,
349        /// Page is in PENDING state
350        pending: bool,
351        /// Page is MMIO
352        mmio: bool,
353    },
354    /// Size mismatch
355    #[error("page size mismatch. Expected page level {expected_level:?}: {result:?}")]
356    PageSizeMismatch {
357        /// Associated TDX Result Code
358        result: TdCallResultCode,
359        /// Expected page level
360        expected_level: TdgMemPageLevel,
361    },
362    /// Invalid operand
363    #[error("invalid operand: {0:?}")]
364    Invalid(TdCallResultCode),
365    /// Busy Operand
366    #[error("operand busy: {0:?}")]
367    Busy(TdCallResultCode),
368}
369
370/// Issue a TDG.MEM.PAGE.RELEASE call
371pub fn tdcall_release_page(
372    call: &mut impl Tdcall,
373    gpa_page_number: u64,
374    as_large_page: bool,
375) -> Result<(), TdgPageReleaseError> {
376    #[cfg(feature = "tracing")]
377    tracing::trace!(gpa_page_number, as_large_page, "tdcall_release_page");
378
379    let rcx = TdgMemPageReleaseRcx::new()
380        .with_gpa_page_number(gpa_page_number)
381        .with_level(if as_large_page {
382            TdgMemPageLevel::Size2Mb
383        } else {
384            TdgMemPageLevel::Size4k
385        });
386
387    let input = TdcallInput {
388        leaf: TdCallLeaf::MEM_PAGE_RELEASE,
389        rcx: rcx.into(),
390        rdx: 0,
391        r8: 0,
392        r9: 0,
393        r10: 0,
394        r11: 0,
395        r12: 0,
396        r13: 0,
397        r14: 0,
398        r15: 0,
399    };
400
401    let output = call.tdcall(input);
402
403    let result_code = output.rax.code();
404    let result_info = TdgMemPageReleaseRcxResult::from(output.rcx);
405
406    match result_code {
407        TdCallResultCode::SUCCESS => Ok(()),
408        TdCallResultCode::EPT_ENTRY_FREE => Err(TdgPageReleaseError::NotAllocated(result_code)),
409        TdCallResultCode::EPT_ENTRY_STATE_INCORRECT => {
410            Err(TdgPageReleaseError::EntryStateInvalid {
411                result: result_code,
412                pending: result_info.pending(),
413                mmio: result_info.mmio(),
414            })
415        }
416        TdCallResultCode::PAGE_SIZE_MISMATCH => Err(TdgPageReleaseError::PageSizeMismatch {
417            result: result_code,
418            expected_level: result_info.level(),
419        }),
420        TdCallResultCode::OPERAND_INVALID => Err(TdgPageReleaseError::Invalid(result_code)),
421        TdCallResultCode::OPERAND_BUSY => Err(TdgPageReleaseError::Busy(result_code)),
422        val => Err(TdgPageReleaseError::Unknown(val)),
423    }
424}
425
426/// Releases memory from `range` using [`tdcall_release_page`].
427pub fn release_pages(
428    call: &mut impl Tdcall,
429    range: MemoryRange,
430) -> Result<(), TdgPageReleaseError> {
431    #[cfg(feature = "tracing")]
432    tracing::trace!(%range, "release_pages");
433
434    for_each_tdcall_page(range, |gpn, is_large_page| {
435        match tdcall_release_page(call, gpn, is_large_page) {
436            Ok(_) => Ok(TdcallPageOperationOutcome::Advance),
437            Err(TdgPageReleaseError::PageSizeMismatch {
438                expected_level: TdgMemPageLevel::Size4k,
439                ..
440            }) if is_large_page => Ok(TdcallPageOperationOutcome::Retry4k),
441            Err(e) => Err(e),
442        }
443    })
444}
445
446/// The result returned from [`tdcall_page_attr_rd`].
447#[derive(Debug)]
448pub struct TdgPageAttrRdResult {
449    /// The mapping information for the page.
450    pub mapping: TdgMemPageAttrGpaMappingReadRcxResult,
451    /// The attributes for the page.
452    pub attributes: TdgMemPageGpaAttr,
453}
454
455/// Issue a TDG.MEM.PAGE.ATTR.RD call.
456pub fn tdcall_page_attr_rd(
457    call: &mut impl Tdcall,
458    gpa: u64,
459) -> Result<TdgPageAttrRdResult, TdCallResultCode> {
460    #[cfg(feature = "tracing")]
461    tracing::trace!(gpa, "tdcall_page_attr_rd");
462
463    let input = TdcallInput {
464        leaf: TdCallLeaf::MEM_PAGE_ATTR_RD,
465        rcx: gpa,
466        rdx: 0,
467        r8: 0,
468        r9: 0,
469        r10: 0,
470        r11: 0,
471        r12: 0,
472        r13: 0,
473        r14: 0,
474        r15: 0,
475    };
476
477    let output = call.tdcall(input);
478
479    match output.rax.code() {
480        TdCallResultCode::SUCCESS => Ok(TdgPageAttrRdResult {
481            mapping: TdgMemPageAttrGpaMappingReadRcxResult::from(output.rcx),
482            attributes: TdgMemPageGpaAttr::from(output.rdx),
483        }),
484        val => Err(val),
485    }
486}
487
488/// Issue a TDG.MEM.PAGE.ATTR.WR call.
489pub fn tdcall_page_attr_wr(
490    call: &mut impl Tdcall,
491    mapping: TdgMemPageAttrWriteRcx,
492    attributes: TdgMemPageGpaAttr,
493    mask: TdgMemPageAttrWriteR8,
494) -> Result<(), TdCallResultCode> {
495    #[cfg(feature = "tracing")]
496    tracing::trace!(?mapping, ?attributes, ?mask, "tdcall_page_attr_wr");
497
498    let input = TdcallInput {
499        leaf: TdCallLeaf::MEM_PAGE_ATTR_WR,
500        rcx: mapping.into(),
501        rdx: attributes.into(),
502        r8: mask.into(),
503        r9: 0,
504        r10: 0,
505        r11: 0,
506        r12: 0,
507        r13: 0,
508        r14: 0,
509        r15: 0,
510    };
511
512    let output = call.tdcall(input);
513
514    // TODO TDX: RCX and RDX also contain info that could be returned
515
516    match output.rax.code() {
517        TdCallResultCode::SUCCESS => Ok(()),
518        val => Err(val),
519    }
520}
521
522/// Issue a TDG.MEM.PAGE.ATTR.WR call, but perform additional validation that
523/// the attributes were set correctly on debug builds.
524fn set_page_attr(
525    call: &mut impl Tdcall,
526    mapping: TdgMemPageAttrWriteRcx,
527    attributes: TdgMemPageGpaAttr,
528    mask: TdgMemPageAttrWriteR8,
529) -> Result<(), TdCallResultCode> {
530    match tdcall_page_attr_wr(call, mapping, attributes, mask) {
531        Ok(()) => {
532            #[cfg(debug_assertions)]
533            {
534                let result =
535                    tdcall_page_attr_rd(call, mapping.gpa_page_number() * x86defs::X64_PAGE_SIZE)
536                        .unwrap();
537                assert_eq!(u64::from(mapping), result.mapping.into());
538                assert_eq!(attributes.l1(), result.attributes.l1());
539                assert_eq!(
540                    attributes.into_bits() & mask.with_reserved(0).into_bits(),
541                    result.attributes.into_bits() & mask.with_reserved(0).into_bits()
542                );
543            }
544
545            Ok(())
546        }
547        Err(e) => Err(e),
548    }
549}
550
551/// The error returned by [`accept_pages`].
552// TODO: why is this an enum with multiple variants--callers don't seem to care.
553// Collapse into a struct, or at least collapse some of the variants?
554#[derive(Debug, Error)]
555pub enum AcceptPagesError {
556    /// Unknown error type.
557    #[error("unknown error: {0:?}")]
558    Unknown(TdCallResultCode),
559    /// Setting page attributes failed after accepting,
560    #[error("setting page attributes failed after accepting: {0:?}")]
561    Attributes(TdCallResultCode),
562    /// Invalid operand
563    #[error("invalid operand: {0:?}")]
564    Invalid(TdCallResultCode),
565    /// Busy Operand
566    #[error("operand busy: {0:?}")]
567    Busy(TdCallResultCode),
568}
569
570/// The page attributes to accept pages with.
571pub enum AcceptPagesAttributes {
572    /// Leave page attributes as is and do not issue TDG.MEM.PAGE.ATTR.WR calls
573    /// after accepting pages.
574    None,
575    /// Issue corresponding TDG.MEM.PAGE.ATTR.WR calls after accepting pages to
576    /// set page attributes to the following values.
577    Set {
578        /// The attributes to set for pages.
579        attributes: TdgMemPageGpaAttr,
580        /// The mask to use when setting the page attributes.
581        mask: TdgMemPageAttrWriteR8,
582    },
583}
584
585/// Accept pages from `range` using [`tdcall_accept_pages`].
586pub fn accept_pages<T: Tdcall>(
587    call: &mut T,
588    range: MemoryRange,
589    attributes: AcceptPagesAttributes,
590) -> Result<(), AcceptPagesError> {
591    #[cfg(feature = "tracing")]
592    tracing::trace!(%range, "accept_pages");
593
594    let set_attributes = |call: &mut T, mapping| -> Result<(), AcceptPagesError> {
595        match attributes {
596            AcceptPagesAttributes::None => Ok(()),
597            AcceptPagesAttributes::Set { attributes, mask } => {
598                set_page_attr(call, mapping, attributes, mask).map_err(AcceptPagesError::Attributes)
599            }
600        }
601    };
602
603    for_each_tdcall_page(range, |gpn, is_large_page| {
604        match tdcall_accept_pages(call, gpn, is_large_page) {
605            Ok(_) => {
606                set_attributes(
607                    call,
608                    TdgMemPageAttrWriteRcx::new()
609                        .with_gpa_page_number(gpn)
610                        .with_level(if is_large_page {
611                            TdgMemPageLevel::Size2Mb
612                        } else {
613                            TdgMemPageLevel::Size4k
614                        }),
615                )?;
616                Ok(TdcallPageOperationOutcome::Advance)
617            }
618            Err(e) => match e {
619                TdCallResultCode::OPERAND_BUSY => Err(AcceptPagesError::Busy(e)),
620                TdCallResultCode::OPERAND_INVALID => Err(AcceptPagesError::Invalid(e)),
621                TdCallResultCode::PAGE_ALREADY_ACCEPTED => {
622                    panic!("page {} already accepted", gpn);
623                }
624                TdCallResultCode::PAGE_SIZE_MISMATCH if is_large_page => {
625                    #[cfg(feature = "tracing")]
626                    tracing::trace!("accept pages size mismatch returned");
627                    Ok(TdcallPageOperationOutcome::Retry4k)
628                }
629                _ => Err(AcceptPagesError::Unknown(e)),
630            },
631        }
632    })
633}
634
635/// Set page attributes from `range` using
636/// [`tdcall_page_attr_wr`].
637///
638/// This will attempt to set attributes in 2MB chunks if possible.
639pub fn set_page_attributes(
640    call: &mut impl Tdcall,
641    range: MemoryRange,
642    attributes: TdgMemPageGpaAttr,
643    mask: TdgMemPageAttrWriteR8,
644) -> Result<(), TdCallResultCode> {
645    #[cfg(feature = "tracing")]
646    tracing::trace!(
647        %range,
648        ?attributes,
649        ?mask,
650        "set_page_attributes"
651    );
652
653    for_each_tdcall_page(range, |gpn, is_large_page| {
654        let level = if is_large_page {
655            TdgMemPageLevel::Size2Mb
656        } else {
657            TdgMemPageLevel::Size4k
658        };
659        let mapping = TdgMemPageAttrWriteRcx::new()
660            .with_gpa_page_number(gpn)
661            .with_level(level);
662
663        match set_page_attr(call, mapping, attributes, mask) {
664            Ok(()) => Ok(TdcallPageOperationOutcome::Advance),
665            Err(TdCallResultCode::PAGE_SIZE_MISMATCH) if is_large_page => {
666                #[cfg(feature = "tracing")]
667                tracing::trace!("set pages attr size mismatch returned");
668                Ok(TdcallPageOperationOutcome::Retry4k)
669            }
670            Err(e) => Err(e),
671        }
672    })
673}
674
675/// Issue a map gpa call to change page visibility for accepted pages via a
676/// TDG.VP.VMCALL.
677///
678/// `gpa` should specify the gpa for the address to change visibility for. The
679/// shared gpa boundary will be added or masked off as required.
680///
681/// `len` should specify the length of the region in bytes to change visibility
682/// for.
683///
684/// `host_visible` should specify whether the region should be host visible or
685/// private.
686pub fn tdcall_map_gpa(
687    call: &mut impl Tdcall,
688    range: MemoryRange,
689    host_visible: bool,
690) -> Result<(), TdVmCallR10Result> {
691    let mut gpa = if host_visible {
692        range.start() | TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT
693    } else {
694        range.start() & !TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT
695    };
696    let end = gpa + range.len();
697
698    while gpa < end {
699        let input = TdcallInput {
700            leaf: TdCallLeaf::VP_VMCALL,
701            rcx: 0x3c00, // pass R10-R13
702            rdx: 0,
703            r8: 0,
704            r9: 0,
705            r10: 0, // must be 0 for ghci call
706            r11: TdVmCallSubFunction::MapGpa as u64,
707            r12: gpa,
708            r13: end - gpa,
709            r14: 0,
710            r15: 0,
711        };
712
713        let output = call.tdcall(input);
714
715        // This assertion failing means something has gone horribly wrong with the
716        // TDX module, as this call should always succeed with hypercall errors
717        // returned in r10.
718        assert_eq!(
719            output.rax.code(),
720            TdCallResultCode::SUCCESS,
721            "unexpected nonzero rax {:x} returned by tdcall vmcall",
722            u64::from(output.rax)
723        );
724
725        let result = TdVmCallR10Result(output.r10);
726
727        match result {
728            TdVmCallR10Result::SUCCESS => gpa = end,
729            TdVmCallR10Result::RETRY => gpa = output.r11,
730            val => return Err(val),
731        }
732    }
733
734    Ok(())
735}
736
737/// Issue a TDG.VP.WR call.
738///
739/// `field_code` is the field code to use for the call.
740///
741/// `value` is the value to set, with `mask` being the mask controlling which
742/// bits will be set from `value`, as specified by the TDX API.
743///
744/// Returns the old value of the field.
745pub fn tdcall_vp_wr(
746    call: &mut impl Tdcall,
747    field_code: TdxExtendedFieldCode,
748    value: u64,
749    mask: u64,
750) -> Result<u64, TdCallResult> {
751    let input = TdcallInput {
752        leaf: TdCallLeaf::VP_WR,
753        rcx: 0,
754        rdx: field_code.into(),
755        r8: value,
756        r9: mask,
757        r10: 0,
758        r11: 0,
759        r12: 0,
760        r13: 0,
761        r14: 0,
762        r15: 0,
763    };
764
765    let output = call.tdcall(input);
766
767    match output.rax.code() {
768        TdCallResultCode::SUCCESS => Ok(output.r8),
769        _ => Err(output.rax),
770    }
771}
772
773/// Issue a TDG.VP.RD call.
774///
775/// `field_code` is the field code to use for the call.
776pub fn tdcall_vp_rd(
777    call: &mut impl Tdcall,
778    field_code: TdxExtendedFieldCode,
779) -> Result<u64, TdCallResult> {
780    let input = TdcallInput {
781        leaf: TdCallLeaf::VP_RD,
782        rcx: 0,
783        rdx: field_code.into(),
784        r8: 0,
785        r9: 0,
786        r10: 0,
787        r11: 0,
788        r12: 0,
789        r13: 0,
790        r14: 0,
791        r15: 0,
792    };
793
794    let output = call.tdcall(input);
795
796    match output.rax.code() {
797        TdCallResultCode::SUCCESS => Ok(output.r8),
798        _ => Err(output.rax),
799    }
800}
801
802/// Issue a TDG.VM.WR call to write a TD-scope metadata field (e.g. `TD_CTLS`).
803///
804/// `field_code` is the field code to use for the call.
805///
806/// `value` is the value to set, with `mask` being the mask controlling which
807/// bits will be set from `value`, as specified by the TDX API.
808///
809/// Returns the old value of the field.
810pub fn tdcall_vm_wr(
811    call: &mut impl Tdcall,
812    field_code: TdxExtendedFieldCode,
813    value: u64,
814    mask: u64,
815) -> Result<u64, TdCallResult> {
816    let input = TdcallInput {
817        leaf: TdCallLeaf::VM_WR,
818        rcx: 0,
819        rdx: field_code.into(),
820        r8: value,
821        r9: mask,
822        r10: 0,
823        r11: 0,
824        r12: 0,
825        r13: 0,
826        r14: 0,
827        r15: 0,
828    };
829
830    let output = call.tdcall(input);
831
832    match output.rax.code() {
833        TdCallResultCode::SUCCESS => Ok(output.r8),
834        _ => Err(output.rax),
835    }
836}
837
838/// Issue a TDG.SYS.RD call to read a global-scope TDX module metadata field
839/// (e.g. `TDX_FEATURES0`).
840///
841/// `field_id` is the metadata field ID to read.
842///
843/// Returns the field value.
844pub fn tdcall_sys_rd(call: &mut impl Tdcall, field_id: u64) -> Result<u64, TdCallResult> {
845    let input = TdcallInput {
846        leaf: TdCallLeaf::SYS_RD,
847        rcx: 0,
848        rdx: field_id,
849        r8: 0,
850        r9: 0,
851        r10: 0,
852        r11: 0,
853        r12: 0,
854        r13: 0,
855        r14: 0,
856        r15: 0,
857    };
858
859    let output = call.tdcall(input);
860
861    match output.rax.code() {
862        TdCallResultCode::SUCCESS => Ok(output.r8),
863        _ => Err(output.rax),
864    }
865}
866
867/// Issue a TDG.VP.INVGLA call.
868pub fn tdcall_vp_invgla(
869    call: &mut impl Tdcall,
870    gla_flags: TdGlaVmAndFlags,
871    gla_info: TdxGlaListInfo,
872) -> Result<(), TdCallResult> {
873    let input = TdcallInput {
874        leaf: TdCallLeaf::VP_INVGLA,
875        rcx: gla_flags.into(),
876        rdx: gla_info.into(),
877        r8: 0,
878        r9: 0,
879        r10: 0,
880        r11: 0,
881        r12: 0,
882        r13: 0,
883        r14: 0,
884        r15: 0,
885    };
886
887    let output = call.tdcall(input);
888
889    match output.rax.code() {
890        TdCallResultCode::SUCCESS => Ok(()),
891        _ => Err(output.rax),
892    }
893}
894
895#[repr(C, align(64))]
896struct AddlData {
897    /// Report data buffer for TDG.MR.REPORT call.
898    pub report_data: [u8; 64],
899}
900
901/// Issue a TDG.MR.REPORT call with empty additional data.
902pub fn tdcall_mr_report(call: &mut impl Tdcall, report: &mut TdReport) -> Result<(), TdCallResult> {
903    let addl_data = AddlData {
904        report_data: [0; 64],
905    };
906
907    let input = TdcallInput {
908        leaf: TdCallLeaf::MR_REPORT,
909        rcx: core::ptr::from_mut::<TdReport>(report) as u64,
910        rdx: 0,
911        r8: 0,
912        r9: 0,
913        r10: 0,
914        r11: 0,
915        r12: addl_data.report_data.as_ptr() as u64,
916        r13: 0,
917        r14: 0,
918        r15: 0,
919    };
920
921    let output = call.tdcall(input);
922
923    match output.rax.code() {
924        TdCallResultCode::SUCCESS => Ok(()),
925        _ => Err(output.rax),
926    }
927}
928
929/// Issue a TDG.VM.RD call
930pub fn tdcall_vm_rd(
931    call: &mut impl Tdcall,
932    field_id: TdxExtendedFieldCode,
933) -> Result<TdgVmRdResult, TdCallResult> {
934    let input = TdcallInput {
935        leaf: TdCallLeaf::VM_RD,
936        rcx: 0,
937        rdx: field_id.into_bits(),
938        r8: 0,
939        r9: 0,
940        r10: 0,
941        r11: 0,
942        r12: 0,
943        r13: 0,
944        r14: 0,
945        r15: 0,
946    };
947
948    let output = call.tdcall(input);
949    if output.rax.code() != TdCallResultCode::SUCCESS {
950        return Err(output.rax);
951    }
952
953    Ok(output.r8)
954}
955
956/// Outcome of a per-page TDCall operation.
957enum TdcallPageOperationOutcome {
958    /// The operation succeeded; advance past this page.
959    Advance,
960
961    /// The operation returned PAGE_SIZE_MISMATCH on a 2MB attempt;
962    /// retry the same page as 4K.
963    Retry4k,
964}
965
966/// Processes a memory range for memory operation TDCalls. Prefers 2MB when possible and handles page size mismatch.
967fn for_each_tdcall_page<E>(
968    range: MemoryRange,
969    mut op: impl FnMut(u64, bool) -> Result<TdcallPageOperationOutcome, E>,
970) -> Result<(), E> {
971    let range = AlignedSubranges::new(range).with_max_range_len(x86defs::X64_LARGE_PAGE_SIZE);
972
973    for mut subrange in range {
974        let mut is_large_page = subrange.alignment(0) == x86defs::X64_LARGE_PAGE_SIZE;
975
976        while !subrange.is_empty() {
977            match op(subrange.start_4k_gpn(), is_large_page)? {
978                TdcallPageOperationOutcome::Advance => {
979                    let page_size = if is_large_page {
980                        x86defs::X64_LARGE_PAGE_SIZE
981                    } else {
982                        x86defs::X64_PAGE_SIZE
983                    };
984
985                    subrange = MemoryRange::new(subrange.start() + page_size..subrange.end())
986                }
987                TdcallPageOperationOutcome::Retry4k => {
988                    assert!(
989                        is_large_page,
990                        "Retry4k requested while already retrying as 4K — would loop forever",
991                    );
992                    is_large_page = false;
993                }
994            }
995        }
996    }
997
998    Ok(())
999}