Skip to content

tdx: init APs with the ACPI mailbox protocol #970

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions openhcl/openhcl_boot/src/arch/aarch64/hypercall.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::host_params::shim_params::IsolationType;

/// Writes a synthehtic register to tell the hypervisor the OS ID for the boot shim.
fn report_os_id(guest_os_id: u64) {
// On ARM64, to be able to make hypercalls, one needs first to set the Guest OS ID
Expand All @@ -16,12 +18,12 @@ fn report_os_id(guest_os_id: u64) {
);
}

pub(crate) fn initialize(guest_os_id: u64) {
pub(crate) fn initialize(guest_os_id: u64, _input_page: Option<u64>, _isolation: IsolationType) {
// We are assuming we are running under a Microsoft hypervisor.
report_os_id(guest_os_id);
}

/// Call before jumping to kernel.
pub(crate) fn uninitialize() {
pub(crate) fn uninitialize(_input_page: Option<u64>, _isolation: IsolationType) {
report_os_id(0);
}
2 changes: 2 additions & 0 deletions openhcl/openhcl_boot/src/arch/aarch64/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
//! Setting up memory

use crate::hvcall;
use crate::hypercall::HypercallPages;
use crate::PartitionInfo;
use crate::ShimParams;
use aarch64defs::IntermPhysAddrSize;

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

// Enable VTL protection so that vtl 2 protections can be applied. All other config
// should be set by the user mode
Expand Down
3 changes: 2 additions & 1 deletion openhcl/openhcl_boot/src/arch/aarch64/vp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

//! Setting up VTL2 VPs

use crate::host_params::shim_params::IsolationType;
use crate::hypercall::hvcall;
use crate::PartitionInfo;

pub fn setup_vtl2_vp(partition_info: &PartitionInfo) {
pub fn setup_vtl2_vp(_isolation_type: IsolationType, partition_info: &PartitionInfo) {
// VTL2 kernel boot processor will try to remote read the GICR before AP's are
// brought up. But, the hypervisor doesn't set the GICR overlay pages until the
// Enable VP VTL hypercall has been made. Without the VP VTLs setup, accessing
Expand Down
42 changes: 42 additions & 0 deletions openhcl/openhcl_boot/src/arch/x86_64/address_space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use core::sync::atomic::compiler_fence;
use core::sync::atomic::AtomicU64;
use core::sync::atomic::Ordering;
use memory_range::MemoryRange;
use x86defs::tdx::TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT;
use x86defs::X64_LARGE_PAGE_SIZE;
use zerocopy::FromBytes;
use zerocopy::Immutable;
Expand Down Expand Up @@ -101,6 +102,18 @@ impl PageTableEntry {
pub fn clear(&mut self) {
self.write_pte(0);
}

pub fn tdx_set_shared(&mut self) {
let mut val = self.read_pte();
val |= TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT;
self.write_pte(val);
}

pub fn tdx_set_private(&mut self) {
let mut val = self.read_pte();
val &= !TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT;
self.write_pte(val);
}
}

#[repr(C)]
Expand Down Expand Up @@ -205,6 +218,7 @@ unsafe fn page_table_at_address(address: u64) -> &'static mut PageTable {
/// Returns a reference to the PDE corresponding to a virtual address.
///
/// # Safety
///
/// This routine requires the caller to ensure that the VA is a valid one for which the paging
/// hierarchy was configured by the file loader (the page directory must exist). If this is not
/// true this routine will panic rather than corrupt the address space.
Expand Down Expand Up @@ -253,3 +267,31 @@ pub fn init_local_map(va: u64) -> LocalMap<'static> {
unmap_page_helper(&local_map);
local_map
}

/// Set the shared bit in the PDE of a large page in the local map for a given VA.
///
/// # Safety
/// This routine requires the caller to pass VA that is a valid large page,
/// and ensure that the page is one that should be shared with the host vmm
pub unsafe fn tdx_share_large_page(va: u64) {
// SAFETY: See above
unsafe {
let entry = get_pde_for_va(va);
assert!(entry.is_present() & entry.is_large_page());
entry.tdx_set_shared();
}
}

/// Clear the shared bit in the PDE of the local map for a given VA.
///
/// # Safety
/// This routine requires the caller to pass VA that is a valid large page,
/// and ensure that the page is one that should be shared with the host vmm
pub unsafe fn tdx_unshare_large_page(va: u64) {
// SAFETY: See above
unsafe {
let entry = get_pde_for_va(va);
assert!(entry.is_present() & entry.is_large_page());
entry.tdx_set_private();
}
}
103 changes: 92 additions & 11 deletions openhcl/openhcl_boot/src/arch/x86_64/hypercall.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,37 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::arch::tdx::TdcallInstruction;
use crate::arch::x86_64::address_space::tdx_share_large_page;
use crate::arch::x86_64::address_space::tdx_unshare_large_page;
use crate::host_params::shim_params::IsolationType;
use crate::MemoryRange;
use core::arch::asm;
use core::ptr::addr_of;
use hvdef::HV_PAGE_SIZE;
use minimal_rt::arch::hypercall::HYPERCALL_PAGE;
use minimal_rt::arch::msr::read_msr;
use minimal_rt::arch::msr::write_msr;
use tdcall::tdcall_wrmsr;

/// Writes an MSR to tell the hypervisor the OS ID for the boot shim.
fn report_os_id(guest_os_id: u64) {
// SAFETY: Using the contract established in the Hyper-V TLFS.
unsafe {
write_msr(hvdef::HV_X64_MSR_GUEST_OS_ID, guest_os_id);
};
fn report_os_id(guest_os_id: u64, isolation: IsolationType) {
match isolation {
IsolationType::Tdx => {
tdcall_wrmsr(
&mut TdcallInstruction,
hvdef::HV_X64_MSR_GUEST_OS_ID,
guest_os_id,
)
.unwrap();
}
_ => {
// SAFETY: Using the contract established in the Hyper-V TLFS.
unsafe {
write_msr(hvdef::HV_X64_MSR_GUEST_OS_ID, guest_os_id);
};
}
}
}

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

/// Has to be called before using hypercalls.
pub(crate) fn initialize(guest_os_id: u64) {
pub(crate) fn initialize(
guest_os_id: u64,
isolation: IsolationType,
input_page: Option<u64>,
output_page: Option<u64>,
) {
// We are assuming we are running under a Microsoft hypervisor, so there is
// no need to check any cpuid leaves.
report_os_id(guest_os_id);
write_hypercall_msr(true);
report_os_id(guest_os_id, isolation);

match isolation {
IsolationType::Tdx => {
// SAFETY: The hypercall i/o pages are valid virtual addresses owned by the caller
unsafe {
tdx_share_large_page(input_page.unwrap());
tdx_share_large_page(output_page.unwrap());
}

//// Enable host visibility for hypercall page
let input_page_range =
MemoryRange::new(input_page.unwrap()..input_page.unwrap() + 4096);
let output_page_range =
MemoryRange::new(output_page.unwrap()..output_page.unwrap() + 4096);
super::tdx::change_page_visibility(input_page_range, true);
super::tdx::change_page_visibility(output_page_range, true);
}

_ => {
write_hypercall_msr(true);
}
}
}

/// Call before jumping to kernel.
pub(crate) fn uninitialize() {
write_hypercall_msr(false);
report_os_id(0);
pub(crate) fn uninitialize(
isolation: IsolationType,
input_page: Option<u64>,
output_page: Option<u64>,
) {
report_os_id(0, isolation);

match isolation {
IsolationType::Tdx => {
// SAFETY: The hypercall i/o pages are valid virtual addresses owned by the caller
unsafe {
tdx_unshare_large_page(input_page.unwrap());
tdx_unshare_large_page(output_page.unwrap());
}

// Disable host visibility for hypercall page
let input_page_range =
MemoryRange::new(input_page.unwrap()..input_page.unwrap() + 4096);
super::tdx::change_page_visibility(input_page_range, false);
let output_page_range =
MemoryRange::new(output_page.unwrap()..output_page.unwrap() + 4096);
super::tdx::change_page_visibility(output_page_range, false);
super::tdx::accept_pages(input_page_range)
.expect("accepting vtl 2 memory must not fail");
super::tdx::accept_pages(output_page_range)
.expect("accepting vtl 2 memory must not fail");

// SAFETY: Flush TLB
unsafe {
asm! {
"mov rax, cr3",
"mov cr3, rax"
}
}
}
_ => {
write_hypercall_msr(false);
}
}
}
53 changes: 53 additions & 0 deletions openhcl/openhcl_boot/src/arch/x86_64/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,20 @@ use crate::host_params::shim_params::IsolationType;
use crate::host_params::PartitionInfo;
use crate::hypercall::hvcall;
use crate::ShimParams;
use loader_defs::linux::boot_params;
use loader_defs::linux::e820entry;
use loader_defs::linux::E820_RESERVED;
use loader_defs::shim::TdxTrampolineContext;
use memory_range::MemoryRange;
use sha2::Digest;
use sha2::Sha384;
use x86defs::tdx::RESET_VECTOR_PAGE;
use x86defs::tdx::TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT;
use x86defs::X64_LARGE_PAGE_SIZE;

/// On isolated systems, transitions all VTL2 RAM to be private and accepted, with the appropriate
/// VTL permissions applied.
/// For TDX-isolated partitions, this function returns input and outpute pages to be used for hypercalls
pub fn setup_vtl2_memory(shim_params: &ShimParams, partition_info: &PartitionInfo) {
// Only if the partition is VBS-isolated, accept memory and apply vtl 2 protections here.
// Non-isolated partitions can undergo servicing, and additional information
Expand Down Expand Up @@ -271,3 +277,50 @@ pub fn verify_imported_regions_hash(shim_params: &ShimParams) {
panic!("Imported regions hash mismatch");
}
}

/// Mark the page tables region of openhcl as E820-reserved,
/// as otherwise the L1-VMM will while APs are spinning in the reset vector
///
/// TODO(babayet2)
/// The page table page should be reserved when the e820 map is built,
/// and the AP trampoline prep logic should happen in setup_vtl2_vp
pub fn tdx_prepare_ap_memory(
page_tables_address_start: u64,
page_tables_address_end: u64,
boot_params: &mut boot_params,
) {
add_page_to_e820_entry(
boot_params,
page_tables_address_start,
page_tables_address_end - page_tables_address_start,
E820_RESERVED,
);

tdx_prepare_ap_trampoline();
}

/// Mark the given entry in the E820 map as reserved
fn add_page_to_e820_entry(boot_params: &mut boot_params, addr: u64, size: u64, typ: u32) {
let entry = &mut boot_params.e820_map[boot_params.e820_entries as usize];
*entry = e820entry {
addr: addr.into(),
size: size.into(),
typ: typ.into(),
};
boot_params.e820_entries += 1;
}

/// Update the TdxTrampolineContext, setting the necessary control registers for AP startup,
/// and ensuring that LGDT will be skipped, so the GDT page does not need to be added to the
/// e820 entries
fn tdx_prepare_ap_trampoline() {
let context_ptr: *mut TdxTrampolineContext = RESET_VECTOR_PAGE as *mut TdxTrampolineContext;
// SAFETY: The TdxTrampolineContext is known to be stored at the architectural reset vector address
let tdxcontext: &mut TdxTrampolineContext = unsafe { context_ptr.as_mut().unwrap() };
tdxcontext.gdtr_limit = 0;
tdxcontext.idtr_limit = 0;
tdxcontext.code_selector = 0;
tdxcontext.task_selector = 0;
tdxcontext.cr0 |= x86defs::X64_CR0_PG | x86defs::X64_CR0_PE | x86defs::X64_CR0_NE;
tdxcontext.cr4 |= x86defs::X64_CR4_PAE | x86defs::X64_CR4_MCE;
}
1 change: 1 addition & 0 deletions openhcl/openhcl_boot/src/arch/x86_64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod vsm;

use crate::host_params::shim_params::IsolationType;
pub use memory::setup_vtl2_memory;
pub use memory::tdx_prepare_ap_memory;
pub use memory::verify_imported_regions_hash;
use safe_intrinsics::cpuid;
pub use vp::setup_vtl2_vp;
Expand Down
17 changes: 17 additions & 0 deletions openhcl/openhcl_boot/src/arch/x86_64/tdx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ use crate::single_threaded::SingleThreaded;
use core::arch::asm;
use core::cell::Cell;
use memory_range::MemoryRange;
use tdcall::tdcall_hypercall;
use tdcall::tdcall_map_gpa;
use tdcall::tdcall_rdmsr;
use tdcall::AcceptPagesError;
use tdcall::Tdcall;
use tdcall::TdcallInput;
use tdcall::TdcallOutput;
use x86defs::tdx::TdVmCallR10Result;

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

pub fn invoke_tdcall_hypercall(
control: hvdef::hypercall::Control,
input_page: u64,
output_page: u64,
) -> hvdef::hypercall::HypercallOutput {
let result = tdcall_hypercall(&mut TdcallInstruction, control, input_page, output_page);
match result {
Ok(()) => 0.into(),
Err(val) => {
let TdVmCallR10Result(return_code) = val;
return_code.into()
}
}
}

/// Global variable to store tsc frequency.
static TSC_FREQUENCY: SingleThreaded<Cell<u64>> = SingleThreaded(Cell::new(0));

Expand Down
24 changes: 21 additions & 3 deletions openhcl/openhcl_boot/src/arch/x86_64/vp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,27 @@

//! Setting up VTL2 VPs

use crate::host_params::shim_params::IsolationType;
use crate::host_params::PartitionInfo;
use crate::hvcall;

pub fn setup_vtl2_vp(_partition_info: &PartitionInfo) {
// X64 doesn't require any special VTL2 VP setup in the boot loader at the
// moment.
/// VTL2 VP setup for x86_64
///
/// If the partition is TDX isolated partitions, invoke TDVMCALLs
/// to enabled VTL2 and start the VPs. Otherwise do nothing.
pub fn setup_vtl2_vp(isolation_type: IsolationType, partition_info: &PartitionInfo) {
if isolation_type == IsolationType::Tdx {
for cpu in 1..partition_info.cpus.len() {
hvcall()
.enable_vp_vtl(cpu as u32)
.expect("enabling vp should not fail");
}

// Start VPs on Tdx-isolated VMs by sending TDVMCALL-based hypercall HvCallStartVirtualProcessor
for cpu in 1..partition_info.cpus.len() {
hvcall()
.start_vp(cpu as u32)
.expect("start vp should not fail");
}
}
}
Loading