Skip to content

Commit 0be0222

Browse files
committed
mailbox boot for TDX
1 parent 47046f8 commit 0be0222

File tree

23 files changed

+618
-107
lines changed

23 files changed

+618
-107
lines changed

openhcl/openhcl_boot/src/arch/aarch64/hypercall.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
use crate::host_params::shim_params::IsolationType;
5+
46
/// Writes a synthehtic register to tell the hypervisor the OS ID for the boot shim.
57
fn report_os_id(guest_os_id: u64) {
68
// On ARM64, to be able to make hypercalls, one needs first to set the Guest OS ID
@@ -16,12 +18,12 @@ fn report_os_id(guest_os_id: u64) {
1618
);
1719
}
1820

19-
pub(crate) fn initialize(guest_os_id: u64) {
21+
pub(crate) fn initialize(guest_os_id: u64, _input_page: Option<u64>, _isolation: IsolationType) {
2022
// We are assuming we are running under a Microsoft hypervisor.
2123
report_os_id(guest_os_id);
2224
}
2325

2426
/// Call before jumping to kernel.
25-
pub(crate) fn uninitialize() {
27+
pub(crate) fn uninitialize(_input_page: Option<u64>, _isolation: IsolationType) {
2628
report_os_id(0);
2729
}

openhcl/openhcl_boot/src/arch/aarch64/memory.rs

+2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
//! Setting up memory
55
66
use crate::hvcall;
7+
use crate::hypercall::HypercallPages;
78
use crate::PartitionInfo;
89
use crate::ShimParams;
910
use aarch64defs::IntermPhysAddrSize;
1011

1112
pub fn setup_vtl2_memory(_shim_params: &ShimParams, _partition_info: &PartitionInfo) {
1213
// TODO: memory acceptance isn't currently supported in the boot shim for aarch64.
1314
let _ = _shim_params.bounce_buffer;
15+
let _ = _shim_params.page_tables;
1416

1517
// Enable VTL protection so that vtl 2 protections can be applied. All other config
1618
// should be set by the user mode

openhcl/openhcl_boot/src/arch/aarch64/vp.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33

44
//! Setting up VTL2 VPs
55
6+
use crate::host_params::shim_params::IsolationType;
67
use crate::hypercall::hvcall;
78
use crate::PartitionInfo;
89

9-
pub fn setup_vtl2_vp(partition_info: &PartitionInfo) {
10+
pub fn setup_vtl2_vp(_isolation_type: IsolationType, partition_info: &PartitionInfo) {
1011
// VTL2 kernel boot processor will try to remote read the GICR before AP's are
1112
// brought up. But, the hypervisor doesn't set the GICR overlay pages until the
1213
// Enable VP VTL hypercall has been made. Without the VP VTLs setup, accessing

openhcl/openhcl_boot/src/arch/x86_64/address_space.rs

+42
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use core::sync::atomic::compiler_fence;
1818
use core::sync::atomic::AtomicU64;
1919
use core::sync::atomic::Ordering;
2020
use memory_range::MemoryRange;
21+
use x86defs::tdx::TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT;
2122
use x86defs::X64_LARGE_PAGE_SIZE;
2223
use zerocopy::FromBytes;
2324
use zerocopy::Immutable;
@@ -101,6 +102,18 @@ impl PageTableEntry {
101102
pub fn clear(&mut self) {
102103
self.write_pte(0);
103104
}
105+
106+
pub fn tdx_set_shared(&mut self) {
107+
let mut val = self.read_pte();
108+
val |= TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT;
109+
self.write_pte(val);
110+
}
111+
112+
pub fn tdx_set_private(&mut self) {
113+
let mut val = self.read_pte();
114+
val &= !TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT;
115+
self.write_pte(val);
116+
}
104117
}
105118

106119
#[repr(C)]
@@ -205,6 +218,7 @@ unsafe fn page_table_at_address(address: u64) -> &'static mut PageTable {
205218
/// Returns a reference to the PDE corresponding to a virtual address.
206219
///
207220
/// # Safety
221+
///
208222
/// This routine requires the caller to ensure that the VA is a valid one for which the paging
209223
/// hierarchy was configured by the file loader (the page directory must exist). If this is not
210224
/// true this routine will panic rather than corrupt the address space.
@@ -253,3 +267,31 @@ pub fn init_local_map(va: u64) -> LocalMap<'static> {
253267
unmap_page_helper(&local_map);
254268
local_map
255269
}
270+
271+
/// Set the shared bit in the PDE of a large page in the local map for a given VA.
272+
///
273+
/// # Safety
274+
/// This routine requires the caller to pass VA that is a valid large page,
275+
/// and ensure that the page is one that should be shared with the host vmm
276+
pub unsafe fn tdx_share_large_page(va: u64) {
277+
// SAFETY: See above
278+
unsafe {
279+
let entry = get_pde_for_va(va);
280+
assert!(entry.is_present() & entry.is_large_page());
281+
entry.tdx_set_shared();
282+
}
283+
}
284+
285+
/// Clear the shared bit in the PDE of the local map for a given VA.
286+
///
287+
/// # Safety
288+
/// This routine requires the caller to pass VA that is a valid large page,
289+
/// and ensure that the page is one that should be shared with the host vmm
290+
pub unsafe fn tdx_unshare_large_page(va: u64) {
291+
// SAFETY: See above
292+
unsafe {
293+
let entry = get_pde_for_va(va);
294+
assert!(entry.is_present() & entry.is_large_page());
295+
entry.tdx_set_private();
296+
}
297+
}

openhcl/openhcl_boot/src/arch/x86_64/hypercall.rs

+92-11
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,37 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
use crate::arch::tdx::TdcallInstruction;
5+
use crate::arch::x86_64::address_space::tdx_share_large_page;
6+
use crate::arch::x86_64::address_space::tdx_unshare_large_page;
7+
use crate::host_params::shim_params::IsolationType;
8+
use crate::MemoryRange;
9+
use core::arch::asm;
410
use core::ptr::addr_of;
511
use hvdef::HV_PAGE_SIZE;
612
use minimal_rt::arch::hypercall::HYPERCALL_PAGE;
713
use minimal_rt::arch::msr::read_msr;
814
use minimal_rt::arch::msr::write_msr;
15+
use tdcall::tdcall_wrmsr;
916

1017
/// Writes an MSR to tell the hypervisor the OS ID for the boot shim.
11-
fn report_os_id(guest_os_id: u64) {
12-
// SAFETY: Using the contract established in the Hyper-V TLFS.
13-
unsafe {
14-
write_msr(hvdef::HV_X64_MSR_GUEST_OS_ID, guest_os_id);
15-
};
18+
fn report_os_id(guest_os_id: u64, isolation: IsolationType) {
19+
match isolation {
20+
IsolationType::Tdx => {
21+
tdcall_wrmsr(
22+
&mut TdcallInstruction,
23+
hvdef::HV_X64_MSR_GUEST_OS_ID,
24+
guest_os_id,
25+
)
26+
.unwrap();
27+
}
28+
_ => {
29+
// SAFETY: Using the contract established in the Hyper-V TLFS.
30+
unsafe {
31+
write_msr(hvdef::HV_X64_MSR_GUEST_OS_ID, guest_os_id);
32+
};
33+
}
34+
}
1635
}
1736

1837
/// Writes an MSR to tell the hypervisor where the hypercall page is
@@ -36,15 +55,77 @@ fn write_hypercall_msr(enable: bool) {
3655
}
3756

3857
/// Has to be called before using hypercalls.
39-
pub(crate) fn initialize(guest_os_id: u64) {
58+
pub(crate) fn initialize(
59+
guest_os_id: u64,
60+
isolation: IsolationType,
61+
input_page: Option<u64>,
62+
output_page: Option<u64>,
63+
) {
4064
// We are assuming we are running under a Microsoft hypervisor, so there is
4165
// no need to check any cpuid leaves.
42-
report_os_id(guest_os_id);
43-
write_hypercall_msr(true);
66+
report_os_id(guest_os_id, isolation);
67+
68+
match isolation {
69+
IsolationType::Tdx => {
70+
// SAFETY: The hypercall i/o pages are valid virtual addresses owned by the caller
71+
unsafe {
72+
tdx_share_large_page(input_page.unwrap());
73+
tdx_share_large_page(output_page.unwrap());
74+
}
75+
76+
//// Enable host visibility for hypercall page
77+
let input_page_range =
78+
MemoryRange::new(input_page.unwrap()..input_page.unwrap() + 4096);
79+
let output_page_range =
80+
MemoryRange::new(output_page.unwrap()..output_page.unwrap() + 4096);
81+
super::tdx::change_page_visibility(input_page_range, true);
82+
super::tdx::change_page_visibility(output_page_range, true);
83+
}
84+
85+
_ => {
86+
write_hypercall_msr(true);
87+
}
88+
}
4489
}
4590

4691
/// Call before jumping to kernel.
47-
pub(crate) fn uninitialize() {
48-
write_hypercall_msr(false);
49-
report_os_id(0);
92+
pub(crate) fn uninitialize(
93+
isolation: IsolationType,
94+
input_page: Option<u64>,
95+
output_page: Option<u64>,
96+
) {
97+
report_os_id(0, isolation);
98+
99+
match isolation {
100+
IsolationType::Tdx => {
101+
// SAFETY: The hypercall i/o pages are valid virtual addresses owned by the caller
102+
unsafe {
103+
tdx_unshare_large_page(input_page.unwrap());
104+
tdx_unshare_large_page(output_page.unwrap());
105+
}
106+
107+
// Disable host visibility for hypercall page
108+
let input_page_range =
109+
MemoryRange::new(input_page.unwrap()..input_page.unwrap() + 4096);
110+
super::tdx::change_page_visibility(input_page_range, false);
111+
let output_page_range =
112+
MemoryRange::new(output_page.unwrap()..output_page.unwrap() + 4096);
113+
super::tdx::change_page_visibility(output_page_range, false);
114+
super::tdx::accept_pages(input_page_range)
115+
.expect("accepting vtl 2 memory must not fail");
116+
super::tdx::accept_pages(output_page_range)
117+
.expect("accepting vtl 2 memory must not fail");
118+
119+
// SAFETY: Flush TLB
120+
unsafe {
121+
asm! {
122+
"mov rax, cr3",
123+
"mov cr3, rax"
124+
}
125+
}
126+
}
127+
_ => {
128+
write_hypercall_msr(false);
129+
}
130+
}
50131
}

openhcl/openhcl_boot/src/arch/x86_64/memory.rs

+53
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@ use crate::host_params::shim_params::IsolationType;
99
use crate::host_params::PartitionInfo;
1010
use crate::hypercall::hvcall;
1111
use crate::ShimParams;
12+
use loader_defs::linux::boot_params;
13+
use loader_defs::linux::e820entry;
14+
use loader_defs::linux::E820_RESERVED;
15+
use loader_defs::shim::TdxTrampolineContext;
1216
use memory_range::MemoryRange;
1317
use sha2::Digest;
1418
use sha2::Sha384;
19+
use x86defs::tdx::RESET_VECTOR_PAGE;
1520
use x86defs::tdx::TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT;
1621
use x86defs::X64_LARGE_PAGE_SIZE;
1722

1823
/// On isolated systems, transitions all VTL2 RAM to be private and accepted, with the appropriate
1924
/// VTL permissions applied.
25+
/// For TDX-isolated partitions, this function returns input and outpute pages to be used for hypercalls
2026
pub fn setup_vtl2_memory(shim_params: &ShimParams, partition_info: &PartitionInfo) {
2127
// Only if the partition is VBS-isolated, accept memory and apply vtl 2 protections here.
2228
// Non-isolated partitions can undergo servicing, and additional information
@@ -271,3 +277,50 @@ pub fn verify_imported_regions_hash(shim_params: &ShimParams) {
271277
panic!("Imported regions hash mismatch");
272278
}
273279
}
280+
281+
/// Mark the page tables region of openhcl as E820-reserved,
282+
/// as otherwise the L1-VMM will while APs are spinning in the reset vector
283+
///
284+
/// TODO(babayet2)
285+
/// The page table page should be reserved when the e820 map is built,
286+
/// and the AP trampoline prep logic should happen in setup_vtl2_vp
287+
pub fn tdx_prepare_ap_memory(
288+
page_tables_address_start: u64,
289+
page_tables_address_end: u64,
290+
boot_params: &mut boot_params,
291+
) {
292+
add_page_to_e820_entry(
293+
boot_params,
294+
page_tables_address_start,
295+
page_tables_address_end - page_tables_address_start,
296+
E820_RESERVED,
297+
);
298+
299+
tdx_prepare_ap_trampoline();
300+
}
301+
302+
/// Mark the given entry in the E820 map as reserved
303+
fn add_page_to_e820_entry(boot_params: &mut boot_params, addr: u64, size: u64, typ: u32) {
304+
let entry = &mut boot_params.e820_map[boot_params.e820_entries as usize];
305+
*entry = e820entry {
306+
addr: addr.into(),
307+
size: size.into(),
308+
typ: typ.into(),
309+
};
310+
boot_params.e820_entries += 1;
311+
}
312+
313+
/// Update the TdxTrampolineContext, setting the necessary control registers for AP startup,
314+
/// and ensuring that LGDT will be skipped, so the GDT page does not need to be added to the
315+
/// e820 entries
316+
fn tdx_prepare_ap_trampoline() {
317+
let context_ptr: *mut TdxTrampolineContext = RESET_VECTOR_PAGE as *mut TdxTrampolineContext;
318+
// SAFETY: The TdxTrampolineContext is known to be stored at the architectural reset vector address
319+
let tdxcontext: &mut TdxTrampolineContext = unsafe { context_ptr.as_mut().unwrap() };
320+
tdxcontext.gdtr_limit = 0;
321+
tdxcontext.idtr_limit = 0;
322+
tdxcontext.code_selector = 0;
323+
tdxcontext.task_selector = 0;
324+
tdxcontext.cr0 |= x86defs::X64_CR0_PG | x86defs::X64_CR0_PE | x86defs::X64_CR0_NE;
325+
tdxcontext.cr4 |= x86defs::X64_CR4_PAE | x86defs::X64_CR4_MCE;
326+
}

openhcl/openhcl_boot/src/arch/x86_64/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ mod vsm;
1515

1616
use crate::host_params::shim_params::IsolationType;
1717
pub use memory::setup_vtl2_memory;
18+
pub use memory::tdx_prepare_ap_memory;
1819
pub use memory::verify_imported_regions_hash;
1920
use safe_intrinsics::cpuid;
2021
pub use vp::setup_vtl2_vp;

openhcl/openhcl_boot/src/arch/x86_64/tdx.rs

+17
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ use crate::single_threaded::SingleThreaded;
77
use core::arch::asm;
88
use core::cell::Cell;
99
use memory_range::MemoryRange;
10+
use tdcall::tdcall_hypercall;
1011
use tdcall::tdcall_map_gpa;
1112
use tdcall::tdcall_rdmsr;
1213
use tdcall::AcceptPagesError;
1314
use tdcall::Tdcall;
1415
use tdcall::TdcallInput;
1516
use tdcall::TdcallOutput;
17+
use x86defs::tdx::TdVmCallR10Result;
1618

1719
/// Perform a tdcall instruction with the specified inputs.
1820
fn tdcall(input: TdcallInput) -> TdcallOutput {
@@ -105,6 +107,21 @@ fn read_msr_tdcall(msr_index: u32) -> u64 {
105107
msr_value
106108
}
107109

110+
pub fn invoke_tdcall_hypercall(
111+
control: hvdef::hypercall::Control,
112+
input_page: u64,
113+
output_page: u64,
114+
) -> hvdef::hypercall::HypercallOutput {
115+
let result = tdcall_hypercall(&mut TdcallInstruction, control, input_page, output_page);
116+
match result {
117+
Ok(()) => 0.into(),
118+
Err(val) => {
119+
let TdVmCallR10Result(return_code) = val;
120+
return_code.into()
121+
}
122+
}
123+
}
124+
108125
/// Global variable to store tsc frequency.
109126
static TSC_FREQUENCY: SingleThreaded<Cell<u64>> = SingleThreaded(Cell::new(0));
110127

openhcl/openhcl_boot/src/arch/x86_64/vp.rs

+21-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,27 @@
33

44
//! Setting up VTL2 VPs
55
6+
use crate::host_params::shim_params::IsolationType;
67
use crate::host_params::PartitionInfo;
8+
use crate::hvcall;
79

8-
pub fn setup_vtl2_vp(_partition_info: &PartitionInfo) {
9-
// X64 doesn't require any special VTL2 VP setup in the boot loader at the
10-
// moment.
10+
/// VTL2 VP setup for x86_64
11+
///
12+
/// If the partition is TDX isolated partitions, invoke TDVMCALLs
13+
/// to enabled VTL2 and start the VPs. Otherwise do nothing.
14+
pub fn setup_vtl2_vp(isolation_type: IsolationType, partition_info: &PartitionInfo) {
15+
if isolation_type == IsolationType::Tdx {
16+
for cpu in 1..partition_info.cpus.len() {
17+
hvcall()
18+
.enable_vp_vtl(cpu as u32)
19+
.expect("enabling vp should not fail");
20+
}
21+
22+
// Start VPs on Tdx-isolated VMs by sending TDVMCALL-based hypercall HvCallStartVirtualProcessor
23+
for cpu in 1..partition_info.cpus.len() {
24+
hvcall()
25+
.start_vp(cpu as u32)
26+
.expect("start vp should not fail");
27+
}
28+
}
1129
}

0 commit comments

Comments
 (0)