Skip to main content

hcl/ioctl/
tdx.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Backing for TDX partitions.
5
6use super::Hcl;
7use super::HclVp;
8use super::MshvVtl;
9use super::NoRunner;
10use super::ProcessorRunner;
11use super::hcl_tdcall;
12use super::mshv_tdcall;
13use crate::GuestVtl;
14use crate::protocol::tdx_l2_tsc_deadline_state;
15use crate::protocol::tdx_tdg_vp_enter_exit_info;
16use crate::protocol::tdx_vp_context;
17use crate::protocol::tdx_vp_state;
18use crate::protocol::tdx_vp_state_flags;
19use hv1_structs::VtlArray;
20use hvdef::HvRegisterName;
21use hvdef::HvRegisterValue;
22use memory_range::MemoryRange;
23use sidecar_client::SidecarVp;
24use std::cell::UnsafeCell;
25use std::os::fd::AsRawFd;
26use tdcall::Tdcall;
27use tdcall::TdgPageReleaseError;
28use tdcall::tdcall_sys_rd;
29use tdcall::tdcall_vm_rd;
30use tdcall::tdcall_vm_wr;
31use tdcall::tdcall_vp_invgla;
32use tdcall::tdcall_vp_rd;
33use tdcall::tdcall_vp_wr;
34use x86defs::tdx::TDX_FIELD_CODE_CONFIG_FLAGS;
35use x86defs::tdx::TdCallResult;
36use x86defs::tdx::TdCallResultCode;
37use x86defs::tdx::TdConfigFlags;
38use x86defs::tdx::TdGlaVmAndFlags;
39use x86defs::tdx::TdVpsClassCode;
40use x86defs::tdx::TdgMemPageAttrWriteR8;
41use x86defs::tdx::TdgMemPageGpaAttr;
42use x86defs::tdx::TdxContextCode;
43use x86defs::tdx::TdxExtendedFieldCode;
44use x86defs::tdx::TdxGlaListInfo;
45use x86defs::tdx::TdxGp;
46use x86defs::tdx::TdxL2Ctls;
47use x86defs::tdx::TdxL2EnterGuestState;
48use x86defs::tdx::TdxVmFlags;
49use x86defs::vmx::VmcsField;
50use x86defs::vmx::VmxApicPage;
51
52/// Runner backing for TDX partitions.
53pub struct Tdx<'a> {
54    apic_pages: VtlArray<&'a UnsafeCell<VmxApicPage>, 2>,
55}
56
57impl MshvVtl {
58    /// Issues a tdcall to set page attributes.
59    pub fn tdx_set_page_attributes(
60        &self,
61        range: MemoryRange,
62        attributes: TdgMemPageGpaAttr,
63        mask: TdgMemPageAttrWriteR8,
64    ) -> Result<(), TdCallResultCode> {
65        tdcall::set_page_attributes(&mut MshvVtlTdcall(self), range, attributes, mask)
66    }
67
68    /// Issues a tdcall to accept pages, optionally also setting attributes.
69    ///
70    /// These operations are combined because this code tries accepting at 2MB
71    /// granularity first and then falls back to 4KB. A separate call to
72    /// [`Self::tdx_set_page_attributes`] has to re-derive the appropriate
73    /// granularity.
74    pub fn tdx_accept_pages(
75        &self,
76        range: MemoryRange,
77        attributes: Option<(TdgMemPageGpaAttr, TdgMemPageAttrWriteR8)>,
78    ) -> Result<(), tdcall::AcceptPagesError> {
79        let attributes = attributes
80            .map_or(tdcall::AcceptPagesAttributes::None, |(attributes, mask)| {
81                tdcall::AcceptPagesAttributes::Set { attributes, mask }
82            });
83
84        tdcall::accept_pages(&mut MshvVtlTdcall(self), range, attributes)
85    }
86
87    /// Issues tdcalls to release pages.
88    pub fn tdx_release_pages(&self, range: MemoryRange) -> Result<(), TdgPageReleaseError> {
89        tdcall::release_pages(&mut MshvVtlTdcall(self), range)
90    }
91
92    /// Issues tdcall to get TD-scoped config flags.
93    pub fn tdx_get_config_flags(&self) -> TdConfigFlags {
94        let res = tdcall_vm_rd(&mut MshvVtlTdcall(self), TDX_FIELD_CODE_CONFIG_FLAGS)
95            .expect("TDG.VM.RD should not fail for CONFIG_FLAGS");
96
97        TdConfigFlags::from_bits(res)
98    }
99
100    /// Reads the global-scope `TDX_FEATURES0` metadata field via the
101    /// `TDG.SYS.RD` TDCALL, which enumerates optional TDX module features
102    /// (including hardware-bound sealing support).
103    ///
104    /// Returns an error if the module does not support `TDG.SYS.RD` (older
105    /// modules) or rejects the field.
106    pub fn tdx_read_features0(&self) -> Result<x86defs::tdx::TdxFeatures0, TdCallResult> {
107        let value = tdcall_sys_rd(
108            &mut MshvVtlTdcall(self),
109            x86defs::tdx::TDX_FIELD_ID_TDX_FEATURES0,
110        )?;
111        Ok(x86defs::tdx::TdxFeatures0::from(value))
112    }
113
114    /// Attempts to opt this TD into hardware-bound seal keys by setting
115    /// `TD_CTLS.ENABLE_HW_SEAL_KEYS`, enabling the `TDG.MR.KEY.GET` TDCALL that
116    /// backs VMGS hardware key sealing.
117    ///
118    /// Returns `Ok(true)` if the bit is set after the operation (sealing keys
119    /// are available), or `Ok(false)` if the TDX module does not support
120    /// sealing.
121    ///
122    /// A TDX module that does not implement sealing treats
123    /// `ENABLE_HW_SEAL_KEYS` as a reserved bit and may *silently ignore* the
124    /// masked write while still returning success. The write status alone is
125    /// therefore not sufficient, so this reads `TD_CTLS` back and reports
126    /// whether the bit actually stuck.
127    pub fn tdx_enable_hw_seal_keys(&self) -> Result<bool, TdCallResult> {
128        let enable = x86defs::tdx::TdCtls::new().with_enable_hw_seal_keys(true);
129
130        // Masked write: only touch the ENABLE_HW_SEAL_KEYS bit.
131        tdcall_vm_wr(
132            &mut MshvVtlTdcall(self),
133            x86defs::tdx::TDX_FIELD_CODE_TD_CTLS,
134            enable.into(),
135            enable.into(),
136        )?;
137
138        // Read back to confirm the bit actually took effect, since an
139        // unsupporting module may have ignored the write.
140        let controls = x86defs::tdx::TdCtls::from(tdcall_vm_rd(
141            &mut MshvVtlTdcall(self),
142            x86defs::tdx::TDX_FIELD_CODE_TD_CTLS,
143        )?);
144
145        Ok(controls.enable_hw_seal_keys())
146    }
147}
148
149impl<'a> ProcessorRunner<'a, Tdx<'a>> {
150    /// Gets a reference to the TDX VP context that is unioned inside the run
151    /// page.
152    fn tdx_vp_context(&self) -> &tdx_vp_context {
153        // SAFETY: the VP context will not be concurrently accessed by the
154        // processor while this VP is in VTL2. This is a TDX partition so the
155        // context union should be interpreted as a `tdx_vp_context`.
156        unsafe { &*(&raw mut (*self.run.get()).context).cast() }
157    }
158
159    /// Gets a mutable reference to the TDX VP context that is unioned inside
160    /// the run page.
161    fn tdx_vp_context_mut(&mut self) -> &mut tdx_vp_context {
162        // SAFETY: the VP context will not be concurrently accessed by the
163        // processor while this VP is in VTL2. This is a TDX partition so the
164        // context union should be interpreted as a `tdx_vp_context`.
165        unsafe { &mut *(&raw mut (*self.run.get()).context).cast() }
166    }
167
168    /// Gets a reference to the TDX enter guest state.
169    fn tdx_enter_guest_state(&self) -> &TdxL2EnterGuestState {
170        &self.tdx_vp_context().gpr_list
171    }
172
173    /// Gets a mutable reference to the TDX enter guest state.
174    fn tdx_enter_guest_state_mut(&mut self) -> &mut TdxL2EnterGuestState {
175        &mut self.tdx_vp_context_mut().gpr_list
176    }
177
178    /// Gets a reference to the TDX enter guest state's GP list.
179    /// These are in canonical x86_64 order.
180    pub fn tdx_enter_guest_gps(&self) -> &[u64; 16] {
181        &self.tdx_enter_guest_state().gps
182    }
183
184    /// Gets a mutable reference to the TDX enter guest state's GP list.
185    /// These are in canonical x86_64 order.
186    pub fn tdx_enter_guest_gps_mut(&mut self) -> &mut [u64; 16] {
187        &mut self.tdx_enter_guest_state_mut().gps
188    }
189
190    /// Gets a reference to the tdx exit info from a VP.ENTER call.
191    pub fn tdx_vp_enter_exit_info(&self) -> &tdx_tdg_vp_enter_exit_info {
192        &self.tdx_vp_context().exit_info
193    }
194
195    /// Gets a reference to the tdx APIC page for the given VTL.
196    pub fn tdx_apic_page(&self, vtl: GuestVtl) -> &VmxApicPage {
197        // SAFETY: the APIC pages will not be concurrently accessed by the processor
198        // while this VP is in VTL2.
199        unsafe { &*self.state.apic_pages[vtl].get() }
200    }
201
202    /// Gets a mutable reference to the tdx APIC page for the given VTL.
203    pub fn tdx_apic_page_mut(&mut self, vtl: GuestVtl) -> &mut VmxApicPage {
204        // SAFETY: the APIC pages will not be concurrently accessed by the processor
205        // while this VP is in VTL2.
206        unsafe { &mut *self.state.apic_pages[vtl].get() }
207    }
208
209    /// Gets a reference to TDX VP specific state.
210    fn tdx_vp_state(&self) -> &tdx_vp_state {
211        &self.tdx_vp_context().vp_state
212    }
213
214    /// Gets a mutable reference to TDX VP specific state
215    fn tdx_vp_state_mut(&mut self) -> &mut tdx_vp_state {
216        &mut self.tdx_vp_context_mut().vp_state
217    }
218
219    /// Gets the value of CR2 from the shared kernel state.
220    pub fn cr2(&self) -> u64 {
221        self.tdx_vp_state().cr2
222    }
223
224    /// Gets the value of CR2 from the shared kernel state.
225    pub fn set_cr2(&mut self, value: u64) {
226        self.tdx_vp_state_mut().cr2 = value;
227    }
228
229    /// Gets a mutable reference to TDX specific VP flags.
230    pub fn tdx_vp_state_flags_mut(&mut self) -> &mut tdx_vp_state_flags {
231        &mut self.tdx_vp_state_mut().flags
232    }
233
234    /// Gets a reference to the TDX VP entry flags.
235    fn tdx_vp_entry_flags(&self) -> &TdxVmFlags {
236        &self.tdx_vp_context().entry_rcx
237    }
238
239    /// Gets a mutable reference to the TDX VP entry flags.
240    fn tdx_vp_entry_flags_mut(&mut self) -> &mut TdxVmFlags {
241        &mut self.tdx_vp_context_mut().entry_rcx
242    }
243
244    /// Gets a reference to the TDX L2 TSC deadline state.
245    pub fn tdx_l2_tsc_deadline_state(&self) -> &tdx_l2_tsc_deadline_state {
246        &self.tdx_vp_context().l2_tsc_deadline
247    }
248
249    /// Gets a mutable reference to the TDX L2 TSC deadline state.
250    pub fn tdx_l2_tsc_deadline_state_mut(&mut self) -> &mut tdx_l2_tsc_deadline_state {
251        &mut self.tdx_vp_context_mut().l2_tsc_deadline
252    }
253
254    /// Reads the private registers from the kernel's shared run page into
255    /// the given [`TdxPrivateRegs`].
256    pub fn read_private_regs(&self, regs: &mut TdxPrivateRegs) {
257        let TdxL2EnterGuestState {
258            gps, // Shared between VTLs except for RSP
259            rflags,
260            rip,
261            ssp,
262            rvi,
263            svi,
264            reserved: _reserved,
265        } = self.tdx_enter_guest_state();
266        regs.rflags = *rflags;
267        regs.rip = *rip;
268        regs.rsp = gps[TdxGp::RSP];
269        regs.ssp = *ssp;
270        regs.rvi = *rvi;
271        regs.svi = *svi;
272
273        let tdx_vp_state {
274            msr_kernel_gs_base,
275            msr_star,
276            msr_lstar,
277            msr_sfmask,
278            msr_xss,
279            cr2: _cr2, // Shared between VTLs
280            msr_tsc_aux,
281            flags: _flags, // Global flags
282        } = self.tdx_vp_state();
283        regs.msr_kernel_gs_base = *msr_kernel_gs_base;
284        regs.msr_star = *msr_star;
285        regs.msr_lstar = *msr_lstar;
286        regs.msr_sfmask = *msr_sfmask;
287        regs.msr_xss = *msr_xss;
288        regs.msr_tsc_aux = *msr_tsc_aux;
289
290        regs.vp_entry_flags = *self.tdx_vp_entry_flags();
291    }
292
293    /// Writes the private registers from the given [`TdxPrivateRegs`] to the
294    /// kernel's shared run page.
295    pub fn write_private_regs(&mut self, regs: &TdxPrivateRegs) {
296        let TdxPrivateRegs {
297            rflags,
298            rip,
299            rsp,
300            ssp,
301            rvi,
302            svi,
303            msr_kernel_gs_base,
304            msr_star,
305            msr_lstar,
306            msr_sfmask,
307            msr_xss,
308            msr_tsc_aux,
309            vp_entry_flags,
310        } = regs;
311
312        let enter_guest_state = self.tdx_enter_guest_state_mut();
313        enter_guest_state.rflags = *rflags;
314        enter_guest_state.rip = *rip;
315        enter_guest_state.ssp = *ssp;
316        enter_guest_state.rvi = *rvi;
317        enter_guest_state.svi = *svi;
318        enter_guest_state.gps[TdxGp::RSP] = *rsp;
319
320        let vp_state = self.tdx_vp_state_mut();
321        vp_state.msr_kernel_gs_base = *msr_kernel_gs_base;
322        vp_state.msr_star = *msr_star;
323        vp_state.msr_lstar = *msr_lstar;
324        vp_state.msr_sfmask = *msr_sfmask;
325        vp_state.msr_xss = *msr_xss;
326        vp_state.msr_tsc_aux = *msr_tsc_aux;
327
328        *self.tdx_vp_entry_flags_mut() = *vp_entry_flags;
329    }
330
331    fn write_vmcs(&mut self, vtl: GuestVtl, field: VmcsField, mask: u64, value: u64) -> u64 {
332        tdcall_vp_wr(
333            &mut MshvVtlTdcall(&self.hcl.mshv_vtl),
334            vmcs_field_code(field, vtl),
335            value,
336            mask,
337        )
338        .expect("fatal vmcs access failure")
339    }
340
341    fn read_vmcs(&self, vtl: GuestVtl, field: VmcsField) -> u64 {
342        tdcall_vp_rd(
343            &mut MshvVtlTdcall(&self.hcl.mshv_vtl),
344            vmcs_field_code(field, vtl),
345        )
346        .expect("fatal vmcs access failure")
347    }
348
349    /// Write a 64-bit VMCS field.
350    ///
351    /// Only updates the bits that are set in `mask`. Returns the old value of
352    /// the field.
353    ///
354    /// Panics if the field is not a 64-bit field, or if there is an error in
355    /// the TDX module when writing the field.
356    pub fn write_vmcs64(&mut self, vtl: GuestVtl, field: VmcsField, mask: u64, value: u64) -> u64 {
357        assert!(matches!(
358            field.field_width(),
359            x86defs::vmx::FieldWidth::WidthNatural | x86defs::vmx::FieldWidth::Width64
360        ));
361        self.write_vmcs(vtl, field, mask, value)
362    }
363
364    /// Reads a 64-bit VMCS field.
365    ///
366    /// Panics if the field is not a 64-bit field, or if there is an error in
367    /// the TDX module when reading the field.
368    pub fn read_vmcs64(&self, vtl: GuestVtl, field: VmcsField) -> u64 {
369        assert!(matches!(
370            field.field_width(),
371            x86defs::vmx::FieldWidth::WidthNatural | x86defs::vmx::FieldWidth::Width64
372        ));
373        self.read_vmcs(vtl, field)
374    }
375
376    /// Write a 32-bit VMCS field.
377    ///
378    /// Only updates the bits that are set in `mask`. Returns the old value of
379    /// the field.
380    ///
381    /// Panics if the field is not a 32-bit field, or if there is an error in
382    /// the TDX module when writing the field.
383    pub fn write_vmcs32(&mut self, vtl: GuestVtl, field: VmcsField, mask: u32, value: u32) -> u32 {
384        assert_eq!(field.field_width(), x86defs::vmx::FieldWidth::Width32);
385        self.write_vmcs(vtl, field, mask.into(), value.into()) as u32
386    }
387
388    /// Reads a 32-bit VMCS field.
389    ///
390    /// Panics if the field is not a 32-bit field, or if there is an error in
391    /// the TDX module when reading the field.
392    pub fn read_vmcs32(&self, vtl: GuestVtl, field: VmcsField) -> u32 {
393        assert_eq!(field.field_width(), x86defs::vmx::FieldWidth::Width32);
394        self.read_vmcs(vtl, field) as u32
395    }
396
397    /// Write a 16-bit VMCS field.
398    ///
399    /// Only updates the bits that are set in `mask`. Returns the old value of
400    /// the field.
401    ///
402    /// Panics if the field is not a 16-bit field, or if there is an error in
403    /// the TDX module when writing the field.
404    pub fn write_vmcs16(&mut self, vtl: GuestVtl, field: VmcsField, mask: u16, value: u16) -> u16 {
405        assert_eq!(field.field_width(), x86defs::vmx::FieldWidth::Width16);
406        self.write_vmcs(vtl, field, mask.into(), value.into()) as u16
407    }
408
409    /// Reads a 16-bit VMCS field.
410    ///
411    /// Panics if the field is not a 16-bit field, or if there is an error in
412    /// the TDX module when reading the field.
413    pub fn read_vmcs16(&self, vtl: GuestVtl, field: VmcsField) -> u16 {
414        assert_eq!(field.field_width(), x86defs::vmx::FieldWidth::Width16);
415        self.read_vmcs(vtl, field) as u16
416    }
417
418    /// Sets the MSR bitmap intercept bit for the given MSR index.
419    ///
420    /// Panics if there is an error in the TDX module when writing the bit.
421    pub fn set_msr_bit(&self, vtl: GuestVtl, msr_index: u32, write: bool, intercept: bool) {
422        let mut word_index = (msr_index & 0xFFFF) / 64;
423
424        if msr_index & 0x80000000 == 0x80000000 {
425            assert!((0xC0000000..=0xC0001FFF).contains(&msr_index));
426            word_index += 0x80;
427        } else {
428            assert!(msr_index <= 0x00001FFF);
429        }
430
431        if write {
432            word_index += 0x100;
433        }
434
435        self.write_msr_bitmap(
436            vtl,
437            word_index,
438            1 << (msr_index as u64 & 0x3F),
439            if intercept { !0 } else { 0 },
440        );
441    }
442
443    /// Writes 64-bit word with index `i` of the MSR bitmap.
444    ///
445    /// Only updates the bits that are set in `mask`. Returns the old value of
446    /// the word.
447    ///
448    /// Panics if there is an error in the TDX module when writing the word.
449    pub fn write_msr_bitmap(&self, vtl: GuestVtl, i: u32, mask: u64, word: u64) -> u64 {
450        let class_code = match vtl {
451            GuestVtl::Vtl0 => TdVpsClassCode::MSR_BITMAPS_1,
452            GuestVtl::Vtl1 => TdVpsClassCode::MSR_BITMAPS_2,
453        };
454        let field_code = TdxExtendedFieldCode::new()
455            .with_context_code(TdxContextCode::TD_VCPU)
456            .with_field_size(x86defs::tdx::FieldSize::Size64Bit)
457            .with_field_code(i)
458            .with_class_code(class_code.0);
459
460        tdcall_vp_wr(
461            &mut MshvVtlTdcall(&self.hcl.mshv_vtl),
462            field_code,
463            word,
464            mask,
465        )
466        .unwrap()
467    }
468
469    /// Sets the L2_CTLS field of the VP.
470    ///
471    /// Returns the old value of the field.
472    pub fn set_l2_ctls(&self, vtl: GuestVtl, value: TdxL2Ctls) -> Result<TdxL2Ctls, TdCallResult> {
473        let field_code = match vtl {
474            GuestVtl::Vtl0 => x86defs::tdx::TDX_FIELD_CODE_L2_CTLS_VM1,
475            GuestVtl::Vtl1 => x86defs::tdx::TDX_FIELD_CODE_L2_CTLS_VM2,
476        };
477        tdcall_vp_wr(
478            &mut MshvVtlTdcall(&self.hcl.mshv_vtl),
479            field_code,
480            value.into(),
481            !0,
482        )
483        .map(Into::into)
484    }
485
486    /// Issues an INVGLA instruction for the VP.
487    pub fn invgla(
488        &self,
489        gla_flags: TdGlaVmAndFlags,
490        gla_info: TdxGlaListInfo,
491    ) -> Result<(), TdCallResult> {
492        tdcall_vp_invgla(&mut MshvVtlTdcall(&self.hcl.mshv_vtl), gla_flags, gla_info)
493    }
494
495    /// Gets the FPU state for the VP.
496    pub fn fx_state(&self) -> &x86defs::xsave::Fxsave {
497        &self.tdx_vp_context().fx_state
498    }
499
500    /// Sets the FPU state for the VP.
501    pub fn fx_state_mut(&mut self) -> &mut x86defs::xsave::Fxsave {
502        &mut self.tdx_vp_context_mut().fx_state
503    }
504}
505
506fn vmcs_field_code(field: VmcsField, vtl: GuestVtl) -> TdxExtendedFieldCode {
507    let class_code = match vtl {
508        GuestVtl::Vtl0 => TdVpsClassCode::VMCS_1,
509        GuestVtl::Vtl1 => TdVpsClassCode::VMCS_2,
510    };
511    let field_size = match field.field_width() {
512        x86defs::vmx::FieldWidth::Width16 => x86defs::tdx::FieldSize::Size16Bit,
513        x86defs::vmx::FieldWidth::Width32 => x86defs::tdx::FieldSize::Size32Bit,
514        x86defs::vmx::FieldWidth::Width64 => x86defs::tdx::FieldSize::Size64Bit,
515        x86defs::vmx::FieldWidth::WidthNatural => x86defs::tdx::FieldSize::Size64Bit,
516    };
517    TdxExtendedFieldCode::new()
518        .with_context_code(TdxContextCode::TD_VCPU)
519        .with_class_code(class_code.0)
520        .with_field_code(field.into())
521        .with_field_size(field_size)
522}
523
524impl<'a> super::private::BackingPrivate<'a> for Tdx<'a> {
525    fn new(vp: &'a HclVp, sidecar: Option<&SidecarVp<'_>>, hcl: &Hcl) -> Result<Self, NoRunner> {
526        assert!(sidecar.is_none());
527        let super::BackingState::Tdx {
528            vtl0_apic_page,
529            vtl1_apic_page,
530        } = &vp.backing
531        else {
532            return Err(NoRunner::MismatchedIsolation);
533        };
534
535        // Register the VTL 1 APIC page with the TD module.
536        // The VTL 0 APIC page is registered by the kernel.
537        let vtl1_apic_page_addr = vtl1_apic_page.pfns()[0] * user_driver::memory::PAGE_SIZE64;
538        tdcall_vp_wr(
539            &mut MshvVtlTdcall(&hcl.mshv_vtl),
540            vmcs_field_code(VmcsField::VMX_VMCS_VIRTUAL_APIC_PAGE, GuestVtl::Vtl1),
541            vtl1_apic_page_addr,
542            !0,
543        )
544        .expect("failed registering VTL1 APIC page");
545
546        // SAFETY: The mapping is held for the appropriate lifetime, and the
547        // APIC page is never accessed as any other type, or by any other location.
548        let vtl1_apic_page = unsafe { &*vtl1_apic_page.base().cast() };
549
550        Ok(Self {
551            apic_pages: [vtl0_apic_page.as_ref(), vtl1_apic_page].into(),
552        })
553    }
554
555    fn try_set_reg(
556        _runner: &mut ProcessorRunner<'a, Self>,
557        _vtl: GuestVtl,
558        _name: HvRegisterName,
559        _value: HvRegisterValue,
560    ) -> bool {
561        false
562    }
563
564    fn must_flush_regs_on(_runner: &ProcessorRunner<'a, Self>, _name: HvRegisterName) -> bool {
565        false
566    }
567
568    fn try_get_reg(
569        _runner: &ProcessorRunner<'a, Self>,
570        _vtl: GuestVtl,
571        _name: HvRegisterName,
572    ) -> Option<HvRegisterValue> {
573        None
574    }
575
576    fn flush_register_page(_runner: &mut ProcessorRunner<'a, Self>) {}
577}
578
579/// Private registers that are copied to/from the kernel's shared run page.
580#[derive(inspect::InspectMut)]
581#[expect(missing_docs, reason = "Self-describing field names")]
582pub struct TdxPrivateRegs {
583    // Registers on [`TdxL2EnterGuestState`].
584    pub rflags: u64,
585    pub rip: u64,
586    pub rsp: u64,
587    pub ssp: u64,
588    pub rvi: u8,
589    pub svi: u8,
590    // Registers on [`tdx_vp_state`].
591    pub msr_kernel_gs_base: u64,
592    pub msr_star: u64,
593    pub msr_lstar: u64,
594    pub msr_sfmask: u64,
595    pub msr_xss: u64,
596    pub msr_tsc_aux: u64,
597    // VP Entry flags
598    #[inspect(hex, with = "|x| x.into_bits()")]
599    pub vp_entry_flags: TdxVmFlags,
600}
601
602impl TdxPrivateRegs {
603    /// Creates a new register set with the given values.
604    /// Other values are initialized to zero.
605    pub fn new(vtl: GuestVtl) -> Self {
606        Self {
607            rflags: x86defs::RFlags::at_reset().into(),
608            rip: 0,
609            rsp: 0,
610            ssp: 0,
611            rvi: 0,
612            svi: 0,
613            msr_kernel_gs_base: 0,
614            msr_star: 0,
615            msr_lstar: 0,
616            msr_sfmask: 0,
617            msr_xss: 0,
618            msr_tsc_aux: 0,
619            // We initialize with a TLB flush pending so that save/restore/reset
620            // operations (not supported yet, but maybe someday) will start with
621            // a clear TLB. During regular boots this won't matter, as the TLB
622            // will already be empty.
623            vp_entry_flags: TdxVmFlags::new()
624                .with_vm_index(vtl as u8 + 1)
625                .with_invd_translations(x86defs::tdx::TDX_VP_ENTER_INVD_INVEPT),
626        }
627    }
628}
629
630struct MshvVtlTdcall<'a>(&'a MshvVtl);
631
632impl Tdcall for MshvVtlTdcall<'_> {
633    fn tdcall(&mut self, input: tdcall::TdcallInput) -> tdcall::TdcallOutput {
634        let mut mshv_tdcall_args = {
635            let tdcall::TdcallInput {
636                leaf,
637                rcx,
638                rdx,
639                r8,
640                r9,
641                r10,
642                r11,
643                r12,
644                r13,
645                r14,
646                r15,
647            } = input;
648
649            // NOTE: Only TD module calls are supported by the kernel, so assert
650            // that here before dispatching. Additionally, the kernel only
651            // supports a limited set of input registers.
652            assert_ne!(leaf, x86defs::tdx::TdCallLeaf::VP_VMCALL);
653            assert_eq!(r10, 0);
654            assert_eq!(r11, 0);
655            assert_eq!(r12, 0);
656            assert_eq!(r13, 0);
657            assert_eq!(r14, 0);
658            assert_eq!(r15, 0);
659
660            mshv_tdcall {
661                rax: leaf.0,
662                rcx,
663                rdx,
664                r8,
665                r9,
666                r10_out: 0,
667                r11_out: 0,
668            }
669        };
670
671        // SAFETY: Calling tdcall ioctl with the correct arguments.
672        unsafe {
673            // NOTE: This ioctl should never fail, as the tdcall itself failing
674            // is returned as output in the structure given by the kernel.
675            hcl_tdcall(self.0.file.as_raw_fd(), &mut mshv_tdcall_args)
676                .expect("todo handle tdcall ioctl error");
677        }
678
679        tdcall::TdcallOutput {
680            rax: TdCallResult::from(mshv_tdcall_args.rax),
681            rcx: mshv_tdcall_args.rcx,
682            rdx: mshv_tdcall_args.rdx,
683            r8: mshv_tdcall_args.r8,
684            r10: mshv_tdcall_args.r10_out,
685            r11: mshv_tdcall_args.r11_out,
686        }
687    }
688}