Skip to main content

openhcl_boot/arch/x86_64/
tdx.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! TDX support.
5
6use crate::arch::x86_64::address_space::TdxHypercallPage;
7use crate::arch::x86_64::address_space::tdx_unshare_large_page;
8use crate::host_params::PartitionInfo;
9use crate::hvcall;
10use crate::single_threaded::SingleThreaded;
11use core::arch::asm;
12use core::cell::Cell;
13use loader_defs::shim::TdxTrampolineContext;
14use memory_range::MemoryRange;
15use safe_intrinsics::cpuid;
16use tdcall::AcceptPagesError;
17use tdcall::Tdcall;
18use tdcall::TdcallInput;
19use tdcall::TdcallOutput;
20use tdcall::tdcall_hypercall;
21use tdcall::tdcall_map_gpa;
22use tdcall::tdcall_vm_rd;
23use tdcall::tdcall_wrmsr;
24use x86defs::X64_LARGE_PAGE_SIZE;
25use x86defs::tdx::RESET_VECTOR_PAGE;
26use x86defs::tdx::TDX_FIELD_CODE_CONFIG_FLAGS;
27use x86defs::tdx::TdConfigFlags;
28
29/// Writes a synthehtic register to tell the hypervisor the OS ID for the boot shim.
30fn report_os_id(guest_os_id: u64) {
31    tdcall_wrmsr(
32        &mut TdcallInstruction,
33        hvdef::HV_X64_MSR_GUEST_OS_ID,
34        guest_os_id,
35    )
36    .unwrap();
37}
38
39/// Initialize hypercalls for a TDX L1, sharing the hypercall I/O pages with the HV
40pub fn initialize_hypercalls(guest_os_id: u64, io: &TdxHypercallPage) {
41    // TODO: We are assuming we are running under a Microsoft hypervisor, so there is
42    // no need to check any cpuid leaves.
43    report_os_id(guest_os_id);
44
45    // Enable host visibility for hypercall page
46    let hypercall_page_range = MemoryRange::new(io.base()..io.base() + X64_LARGE_PAGE_SIZE);
47    change_page_visibility(hypercall_page_range, true);
48}
49
50/// Unitialize hypercalls for a TDX L1, stop sharing the hypercall I/O pages with the HV
51pub fn uninitialize_hypercalls(io: TdxHypercallPage) {
52    report_os_id(0);
53
54    let hypercall_page_range = MemoryRange::new(io.base()..io.base() + X64_LARGE_PAGE_SIZE);
55    tdx_unshare_large_page(io);
56
57    // Disable host visibility for hypercall page
58    change_page_visibility(hypercall_page_range, false);
59    accept_pages(hypercall_page_range).expect("pages previously accepted by the bootshim should be reaccepted without failure when sharing permissions are changed");
60
61    // SAFETY: Flushing the TLB has no pre or post conditions required by the caller, and thus is safe
62    unsafe {
63        asm! {
64            "mov rax, cr3",
65            "mov cr3, rax",
66            out("rax") _,
67        }
68    }
69}
70
71/// Perform a tdcall instruction with the specified inputs.
72fn tdcall(input: TdcallInput) -> TdcallOutput {
73    let rax: u64;
74    let rcx;
75    let rdx;
76    let r8;
77    let r10;
78    let r11;
79
80    // Any input registers can be output registers for VMCALL, so make sure
81    // they're all inout even if the output isn't used.
82    //
83    // FUTURE: consider not allowing VMCALL through this path, to avoid needing
84    // to save/restore as many registers. Hard code that separately.
85    //
86    // SAFETY: Calling tdcall with the correct arguments. It is responsible for
87    // argument validation and error handling.
88    unsafe {
89        asm! {
90            "tdcall",
91            inout("rax") input.leaf.0 => rax,
92            inout("rcx") input.rcx => rcx,
93            inout("rdx") input.rdx => rdx,
94            inout("r8") input.r8 => r8,
95            inout("r9")  input.r9 => _,
96            inout("r10") input.r10 => r10,
97            inout("r11") input.r11 => r11,
98            inout("r12") input.r12 => _,
99            inout("r13") input.r13 => _,
100            inout("r14") input.r14 => _,
101            inout("r15") input.r15 => _,
102        }
103    }
104
105    TdcallOutput {
106        rax: rax.into(),
107        rcx,
108        rdx,
109        r8,
110        r10,
111        r11,
112    }
113}
114
115pub struct TdcallInstruction;
116
117impl Tdcall for TdcallInstruction {
118    fn tdcall(&mut self, input: TdcallInput) -> TdcallOutput {
119        tdcall(input)
120    }
121}
122
123/// Accept pages from the specified range.
124pub fn accept_pages(range: MemoryRange) -> Result<(), AcceptPagesError> {
125    tdcall::accept_pages(
126        &mut TdcallInstruction,
127        range,
128        tdcall::AcceptPagesAttributes::None,
129    )
130}
131
132/// Change the visibility of pages. Note that pages that were previously host
133/// visible and are now private, must be reaccepted.
134pub fn change_page_visibility(range: MemoryRange, host_visible: bool) {
135    // If TDX Connect is present, then TDG.MEM.PAGE.RELEASE must be called before making pages host-visible.
136
137    if host_visible {
138        let flags = get_td_config_flags();
139        if flags.tdx_connect() {
140            assert!(
141                flags.page_release(),
142                "TDX Connect enabled but CONFIG_FLAGS.page_release is not set"
143            );
144
145            if let Err(err) = tdcall::release_pages(&mut TdcallInstruction, range) {
146                panic!("failed to release pages in {range}: {err:?}");
147            }
148        }
149    }
150
151    if let Err(err) = tdcall_map_gpa(&mut TdcallInstruction, range, host_visible) {
152        panic!(
153            "failed to change page visibility for {range}, host_visible = {host_visible}: {err:?}"
154        );
155    }
156}
157
158/// Tdcall based io port access.
159#[cfg(feature = "cvm_boot_log")]
160pub struct TdxIoAccess;
161
162#[cfg(feature = "cvm_boot_log")]
163impl minimal_rt::arch::IoAccess for TdxIoAccess {
164    unsafe fn inb(&self, port: u16) -> u8 {
165        tdcall::tdcall_io_in(&mut TdcallInstruction, port, 1).unwrap() as u8
166    }
167
168    unsafe fn outb(&self, port: u16, data: u8) {
169        let _ = tdcall::tdcall_io_out(&mut TdcallInstruction, port, data as u32, 1);
170    }
171}
172
173/// Invokes a hypercall via a TDCALL
174pub fn invoke_tdcall_hypercall(
175    control: hvdef::hypercall::Control,
176    io: &TdxHypercallPage,
177) -> hvdef::hypercall::HypercallOutput {
178    tdcall_hypercall(&mut TdcallInstruction, control, io.input(), io.output())
179}
180
181/// Global variable to store tsc frequency.
182static TSC_FREQUENCY: SingleThreaded<Cell<u64>> = SingleThreaded(Cell::new(0));
183
184/// Gets the timer ref time in 100ns, and None if it fails to get it
185pub fn get_tdx_tsc_reftime() -> Option<u64> {
186    // This is first called by the BSP from openhcl_boot and the frequency
187    // is saved in this gloabal variable. Subsequent calls use the global variable.
188    if TSC_FREQUENCY.get() == 0 {
189        // The TDX module interprets frequencies as multiples of 25 MHz
190        const TDX_FREQ_MULTIPLIER: u64 = 25 * 1000 * 1000;
191        const CPUID_LEAF_TDX_TSC_FREQ: u32 = 0x15;
192        TSC_FREQUENCY.set(cpuid(CPUID_LEAF_TDX_TSC_FREQ, 0x0).ebx as u64 * TDX_FREQ_MULTIPLIER);
193    }
194
195    if TSC_FREQUENCY.get() != 0 {
196        let tsc = safe_intrinsics::rdtsc();
197        let count_100ns = (tsc as u128 * 10000000) / TSC_FREQUENCY.get() as u128;
198        return Some(count_100ns as u64);
199    }
200    None
201}
202
203/// Update the TdxTrampolineContext, setting the necessary control registers for AP startup,
204/// and ensuring that LGDT will be skipped, so the GDT page does not need to be added to the
205/// e820 entries
206pub fn tdx_prepare_ap_trampoline(cr3: u64) {
207    let context_ptr: *mut TdxTrampolineContext = RESET_VECTOR_PAGE as *mut TdxTrampolineContext;
208    // SAFETY: The TdxTrampolineContext is known to be stored at the architectural reset vector address
209    let tdxcontext: &mut TdxTrampolineContext = unsafe { context_ptr.as_mut().unwrap() };
210    tdxcontext.gdtr_limit = 0;
211    tdxcontext.idtr_limit = 0;
212    tdxcontext.code_selector = 0;
213    tdxcontext.task_selector = 0;
214    tdxcontext.cr0 |= x86defs::X64_CR0_PG | x86defs::X64_CR0_PE | x86defs::X64_CR0_NE;
215    tdxcontext.cr3 = cr3;
216    tdxcontext.cr4 |= x86defs::X64_CR4_PAE | x86defs::X64_CR4_MCE;
217}
218
219pub fn setup_vtl2_vp(partition_info: &PartitionInfo) {
220    for cpu in 1..partition_info.cpus.len() {
221        hvcall()
222            .tdx_enable_vp_vtl2(cpu as u32)
223            .expect("enabling vp should not fail");
224    }
225
226    // Start VPs on Tdx-isolated VMs by sending TDVMCALL-based hypercall HvCallStartVirtualProcessor
227    for cpu in 1..partition_info.cpus.len() {
228        hvcall()
229            .tdx_start_vp(cpu as u32)
230            .expect("start vp should not fail");
231    }
232}
233
234static TDX_TD_CONFIG_FLAGS: SingleThreaded<Cell<Option<TdConfigFlags>>> =
235    SingleThreaded(Cell::new(None));
236
237fn get_td_config_flags() -> TdConfigFlags {
238    if let Some(f) = TDX_TD_CONFIG_FLAGS.get() {
239        f
240    } else {
241        let res = tdcall_vm_rd(&mut TdcallInstruction, TDX_FIELD_CODE_CONFIG_FLAGS)
242            .expect("TDG.VM.RD should not fail for CONFIG_FLAGS");
243
244        let f = TdConfigFlags::from_bits(res);
245        TDX_TD_CONFIG_FLAGS.set(Some(f));
246        f
247    }
248}