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