diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e7bde598..ab67bcf1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -53,16 +53,16 @@ jobs: - name: Add `x86_64-unknown-none` target run: rustup target add x86_64-unknown-none - + - name: Preparation Work run: bash sh_script/preparation.sh - name: Test Shim Crates - run: make test + run: make test - name: Build Release TdShim run: cargo build -p td-shim --target x86_64-unknown-none --release --features=main,tdx - + - name: Build Debug TdShim run: cargo build -p td-shim --target x86_64-unknown-none --features=main,tdx --no-default-features @@ -73,7 +73,7 @@ jobs: - name: Build image without payload run: | cargo image --release - + - name: Meta data check run: | cargo run -p td-shim-tools --bin td-shim-checker --no-default-features --features=loader -- target/release/final.bin diff --git a/Cargo.lock b/Cargo.lock index b62a087b..6ed270ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "tdx-tdcall" -version = "0.2.1" +version = "0.3.0" dependencies = [ "lazy_static", "log", diff --git a/sh_script/build_final.sh b/sh_script/build_final.sh index 38440382..785cfe9b 100644 --- a/sh_script/build_final.sh +++ b/sh_script/build_final.sh @@ -20,7 +20,7 @@ final_boot_kernel() { final_elf() { echo final-elf - cargo --example-payload -o target/release/final-elf.bin + cargo --example-payload -o target/release/final-elf.bin } final_elf_test() { @@ -37,7 +37,7 @@ final_elf_test() { -p target/x86_64-unknown-none/release/test-td-payload \ --enroll-file F10E684E-3ABD-20E4-5932-8F973C355E57,tests/test-td-payload/config/test_config_${i}.json \ -o target/release/final-elf-test${i}.bin - done + done } final_elf_sb_test() { @@ -45,7 +45,7 @@ final_elf_sb_test() { cargo build -p td-payload --target x86_64-unknown-none --release --bin example --features=tdx,start,cet-shstk,stack-guard cargo run -p td-shim-tools --bin td-shim-strip-info -- -n example --target x86_64-unknown-none - cargo run -p td-shim-tools --bin td-shim-sign-payload -- -A ECDSA_NIST_P384_SHA384 data/sample-keys/ecdsa-p384-private.pk8 target/x86_64-unknown-none/release/example 1 1 + cargo run -p td-shim-tools --bin td-shim-sign-payload -- -A ECDSA_NIST_P384_SHA384 data/sample-keys/ecdsa-p384-private.pk8 target/x86_64-unknown-none/release/example 1 1 echo "Build final binary with unsigned td payload" cargo image --release -t executable --features secure-boot \ @@ -77,5 +77,5 @@ case "${1:-}" in elf) final_elf ;; elf_test) final_elf_test ;; elf_sb_test) final_elf_sb_test ;; - *) final_boot_kernel && final_elf && final_elf_test && final_elf_sb_test;; + *) final_boot_kernel && final_elf && final_elf_test && final_elf_sb_test;; esac diff --git a/td-exception/Cargo.toml b/td-exception/Cargo.toml index 5025cc10..891f644c 100644 --- a/td-exception/Cargo.toml +++ b/td-exception/Cargo.toml @@ -17,5 +17,6 @@ spin = "0.9.2" [features] cet-shstk = [] -tdx = ["tdx-tdcall"] +tdcall = ["tdx-tdcall/tdcall"] +tdvmcall = ["tdx-tdcall/tdvmcall"] integration-test = [] diff --git a/td-exception/src/interrupt.rs b/td-exception/src/interrupt.rs index 3b8b3095..ce881b63 100644 --- a/td-exception/src/interrupt.rs +++ b/td-exception/src/interrupt.rs @@ -4,8 +4,12 @@ use core::arch::asm; use spin::Mutex; -#[cfg(feature = "tdx")] -use tdx_tdcall::tdx; + +#[cfg(feature = "tdvmcall")] +use tdx_tdcall::tdcall; + +#[cfg(feature = "tdvmcall")] +use tdx_tdcall::tdvmcall; use crate::{idt::IDT_ENTRY_COUNT, ExceptionError}; @@ -141,7 +145,7 @@ pub(crate) fn init_interrupt_callbacks() { callbacks.table[17].func = alignment_check; callbacks.table[18].func = machine_check; callbacks.table[19].func = simd; - #[cfg(feature = "tdx")] + #[cfg(all(feature = "tdcall", feature = "tdvmcall"))] { callbacks.table[20].func = virtualization; } @@ -323,54 +327,54 @@ fn control_flow(stack: &mut InterruptStack) { deadloop(); } -#[cfg(feature = "tdx")] +#[cfg(all(feature = "tdcall", feature = "tdvmcall"))] const EXIT_REASON_CPUID: u32 = 10; -#[cfg(feature = "tdx")] +#[cfg(all(feature = "tdcall", feature = "tdvmcall"))] const EXIT_REASON_HLT: u32 = 12; -#[cfg(feature = "tdx")] +#[cfg(all(feature = "tdcall", feature = "tdvmcall"))] const EXIT_REASON_RDPMC: u32 = 15; -#[cfg(feature = "tdx")] +#[cfg(all(feature = "tdcall", feature = "tdvmcall"))] const EXIT_REASON_VMCALL: u32 = 18; -#[cfg(feature = "tdx")] +#[cfg(all(feature = "tdcall", feature = "tdvmcall"))] const EXIT_REASON_IO_INSTRUCTION: u32 = 30; -#[cfg(feature = "tdx")] +#[cfg(all(feature = "tdcall", feature = "tdvmcall"))] const EXIT_REASON_MSR_READ: u32 = 31; -#[cfg(feature = "tdx")] +#[cfg(all(feature = "tdcall", feature = "tdvmcall"))] const EXIT_REASON_MSR_WRITE: u32 = 32; -#[cfg(feature = "tdx")] +#[cfg(all(feature = "tdcall", feature = "tdvmcall"))] const EXIT_REASON_MWAIT_INSTRUCTION: u32 = 36; -#[cfg(feature = "tdx")] +#[cfg(all(feature = "tdcall", feature = "tdvmcall"))] const EXIT_REASON_MONITOR_INSTRUCTION: u32 = 39; -#[cfg(feature = "tdx")] +#[cfg(all(feature = "tdcall", feature = "tdvmcall"))] const EXIT_REASON_WBINVD: u32 = 54; -#[cfg(feature = "tdx")] +#[cfg(all(feature = "tdcall", feature = "tdvmcall"))] fn virtualization(stack: &mut InterruptStack) { // Firstly get VE information from TDX module, halt it error occurs - let ve_info = tdx::tdcall_get_ve_info().expect("#VE handler: fail to get VE info\n"); + let ve_info = tdcall::get_ve_info().expect("#VE handler: fail to get VE info\n"); match ve_info.exit_reason { EXIT_REASON_HLT => { - tdx::tdvmcall_halt(); + tdvmcall::halt(); } EXIT_REASON_IO_INSTRUCTION => { if !handle_tdx_ioexit(&ve_info, stack) { - tdx::tdvmcall_halt(); + tdvmcall::halt(); } } EXIT_REASON_MSR_READ => { - let msr = tdx::tdvmcall_rdmsr(stack.scratch.rcx as u32) + let msr = tdvmcall::rdmsr(stack.scratch.rcx as u32) .expect("fail to perform RDMSR operation\n"); stack.scratch.rax = (msr as u32 & u32::MAX) as usize; // EAX stack.scratch.rdx = ((msr >> 32) as u32 & u32::MAX) as usize; // EDX } EXIT_REASON_MSR_WRITE => { let data = stack.scratch.rax as u64 | ((stack.scratch.rdx as u64) << 32); // EDX:EAX - tdx::tdvmcall_wrmsr(stack.scratch.rcx as u32, data) + tdvmcall::wrmsr(stack.scratch.rcx as u32, data) .expect("fail to perform WRMSR operation\n"); } EXIT_REASON_CPUID => { - let cpuid = tdx::tdvmcall_cpuid(stack.scratch.rax as u32, stack.scratch.rcx as u32); + let cpuid = tdvmcall::cpuid(stack.scratch.rax as u32, stack.scratch.rcx as u32); let mask = 0xFFFF_FFFF_0000_0000_usize; stack.scratch.rax = (stack.scratch.rax & mask) | cpuid.eax as usize; stack.preserved.rbx = (stack.preserved.rbx & mask) | cpuid.ebx as usize; @@ -451,8 +455,8 @@ fn virtualization(stack: &mut InterruptStack) { // // Use TDVMCALL to realize IO read/write operation // Return false if VE info is invalid -#[cfg(feature = "tdx")] -fn handle_tdx_ioexit(ve_info: &tdx::TdVeInfo, stack: &mut InterruptStack) -> bool { +#[cfg(all(feature = "tdcall", feature = "tdvmcall"))] +fn handle_tdx_ioexit(ve_info: &tdcall::TdVeInfo, stack: &mut InterruptStack) -> bool { let size = ((ve_info.exit_qualification & 0x7) + 1) as usize; // 0 - 1bytes, 1 - 2bytes, 3 - 4bytes let read = (ve_info.exit_qualification >> 3) & 0x1 == 1; let string = (ve_info.exit_qualification >> 4) & 0x1 == 1; @@ -471,17 +475,17 @@ fn handle_tdx_ioexit(ve_info: &tdx::TdVeInfo, stack: &mut InterruptStack) -> boo // Define closure to perform IO port read with different size operands let io_read = |size, port| match size { - 1 => tdx::tdvmcall_io_read_8(port) as u32, - 2 => tdx::tdvmcall_io_read_16(port) as u32, - 4 => tdx::tdvmcall_io_read_32(port), + 1 => tdvmcall::io_read_8(port) as u32, + 2 => tdvmcall::io_read_16(port) as u32, + 4 => tdvmcall::io_read_32(port), _ => 0, }; // Define closure to perform IO port write with different size operands let io_write = |size, port, data| match size { - 1 => tdx::tdvmcall_io_write_8(port, data as u8), - 2 => tdx::tdvmcall_io_write_16(port, data as u16), - 4 => tdx::tdvmcall_io_write_32(port, data), + 1 => tdvmcall::io_write_8(port, data as u8), + 2 => tdvmcall::io_write_16(port, data as u16), + 4 => tdvmcall::io_write_32(port, data), _ => {} }; diff --git a/td-logger/Cargo.toml b/td-logger/Cargo.toml index d16aa217..a7245891 100644 --- a/td-logger/Cargo.toml +++ b/td-logger/Cargo.toml @@ -16,5 +16,5 @@ tdx-tdcall = { path = "../tdx-tdcall", optional = true } x86 = { version = "0.47.0", optional = true } [features] -tdx = ["tdx-tdcall"] +tdvmcall = ["tdx-tdcall/tdvmcall"] serial-port = ["x86"] diff --git a/td-logger/src/lib.rs b/td-logger/src/lib.rs index af2e591a..b065fd97 100644 --- a/td-logger/src/lib.rs +++ b/td-logger/src/lib.rs @@ -53,20 +53,20 @@ pub fn dbg_write_string(s: &str) { } } -#[cfg(any(feature = "tdx", feature = "serial-port"))] +#[cfg(any(feature = "tdvmcall", feature = "serial-port"))] const SERIAL_IO_PORT: u16 = 0x3F8; -#[cfg(feature = "tdx")] +#[cfg(feature = "tdvmcall")] fn dbg_port_write(byte: u8) { - tdx_tdcall::tdx::tdvmcall_io_write_8(SERIAL_IO_PORT, byte); + tdx_tdcall::tdvmcall::io_write_8(SERIAL_IO_PORT, byte); } -#[cfg(all(not(feature = "tdx"), feature = "serial-port"))] +#[cfg(all(not(feature = "tdvmcall"), feature = "serial-port"))] fn dbg_port_write(byte: u8) { unsafe { x86::io::outb(SERIAL_IO_PORT, byte) }; } -#[cfg(all(not(feature = "tdx"), not(feature = "serial-port")))] +#[cfg(all(not(feature = "tdvmcall"), not(feature = "serial-port")))] fn dbg_port_write(_byte: u8) {} #[cfg(test)] diff --git a/td-payload/Cargo.toml b/td-payload/Cargo.toml index 6f19bf4a..ba93755f 100644 --- a/td-payload/Cargo.toml +++ b/td-payload/Cargo.toml @@ -36,7 +36,9 @@ minicov = { version = "0.2", default-features = false, optional = true } [features] default = ["tdx"] -tdx = ["tdx-tdcall", "td-logger/tdx", "td-exception/tdx"] +tdcall = ["tdx-tdcall/tdcall", "td-exception/tdcall"] +tdvmcall = ["tdx-tdcall/tdvmcall", "td-exception/tdvmcall", "td-logger/tdvmcall"] +tdx = ["tdcall", "tdvmcall"] stack-guard = [] cet-shstk = ["td-exception/cet-shstk"] cet-ibt = [] diff --git a/td-payload/src/arch/x86_64/apic.rs b/td-payload/src/arch/x86_64/apic.rs index 89fc4441..14d7976b 100644 --- a/td-payload/src/arch/x86_64/apic.rs +++ b/td-payload/src/arch/x86_64/apic.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Intel Corporation +// Copyright (c) 2022, 2025 Intel Corporation // // SPDX-License-Identifier: BSD-2-Clause-Patent @@ -13,21 +13,33 @@ pub const LOCAL_APIC_LVTT: u32 = 0xfee0_0320; pub const INITIAL_COUNT: u32 = 0xfee0_0380; pub const DIVIDE_CONFIGURATION_REGISTER: u32 = 0xfee0_03e0; +#[cfg(feature = "tdvmcall")] pub fn enable_apic_interrupt() { // Enable the local APIC by setting bit 8 of the APIC spurious vector region (SVR) // Ref: Intel SDM Vol3. 8.4.4.1 // In x2APIC mode, SVR is mapped to MSR address 0x80f. // Since SVR(SIVR) is not virtualized, before we implement the handling in #VE of MSRRD/WR, // use tdvmcall instead direct read/write operation. - let svr = tdx_tdcall::tdx::tdvmcall_rdmsr(0x80f).expect("fail to perform RDMSR operation\n"); - tdx_tdcall::tdx::tdvmcall_wrmsr(0x80f, svr | (0x1 << 8)) + let svr = tdx_tdcall::tdvmcall::rdmsr(0x80f).expect("fail to perform RDMSR operation\n"); + tdx_tdcall::tdvmcall::wrmsr(0x80f, svr | (0x1 << 8)) .expect("fail to perform WRMSR operation\n"); } +#[cfg(not(feature = "tdvmcall"))] +pub fn enable_apic_interrupt() { + // Enable the local APIC by setting bit 8 of the APIC spurious vector region (SVR) + // Ref: Intel SDM Vol3. 8.4.4.1 + // In x2APIC mode, SVR is mapped to MSR address 0x80f. + let svr = unsafe { x86::msr::rdmsr(0x80f) }; + unsafe { + x86::msr::wrmsr(0x80f, svr | (0x1 << 8)); + } +} + pub fn enable_and_hlt() { - #[cfg(feature = "tdx")] - tdx_tdcall::tdx::tdvmcall_sti_halt(); - #[cfg(not(feature = "tdx"))] + #[cfg(feature = "tdvmcall")] + tdx_tdcall::tdvmcall::sti_halt(); + #[cfg(not(feature = "tdvmcall"))] x86_64::instructions::interrupts::enable_and_hlt() } diff --git a/td-payload/src/arch/x86_64/mod.rs b/td-payload/src/arch/x86_64/mod.rs index b2577c9e..44d2b92c 100644 --- a/td-payload/src/arch/x86_64/mod.rs +++ b/td-payload/src/arch/x86_64/mod.rs @@ -10,5 +10,4 @@ pub mod idt; pub mod init; pub mod paging; pub mod serial; -#[cfg(feature = "tdx")] pub mod shared; diff --git a/td-payload/src/arch/x86_64/paging.rs b/td-payload/src/arch/x86_64/paging.rs index d476c64d..dcc440b5 100644 --- a/td-payload/src/arch/x86_64/paging.rs +++ b/td-payload/src/arch/x86_64/paging.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Intel Corporation +// Copyright (c) 2022, 2025 Intel Corporation // // SPDX-License-Identifier: BSD-2-Clause-Patent @@ -129,13 +129,13 @@ pub fn set_not_present(address: u64, size: usize) { set_page_flags(&mut pt, address, size, flags); } -#[cfg(feature = "tdx")] +#[cfg(feature = "tdcall")] pub fn set_shared_bit(address: u64, size: usize) { let mut pt = offset_pt(); map_shared(&mut pt, address, size, true); } -#[cfg(feature = "tdx")] +#[cfg(feature = "tdcall")] pub fn clear_shared_bit(address: u64, size: usize) { let mut pt = offset_pt(); map_shared(&mut pt, address, size, false); @@ -202,7 +202,7 @@ pub(crate) fn set_page_flags(pt: &mut OffsetPageTable, va: u64, size: usize, fla } } -#[cfg(feature = "tdx")] +#[cfg(feature = "tdcall")] fn map_shared(pt: &mut OffsetPageTable, va: u64, size: usize, shared: bool) { let end = va + size as u64; let mut va = VirtAddr::new(va); @@ -217,7 +217,7 @@ fn map_shared(pt: &mut OffsetPageTable, va: u64, size: usize, shared: bool) { } } -#[cfg(feature = "tdx")] +#[cfg(feature = "tdcall")] fn pt_set_shared_bit(pt: &mut OffsetPageTable, page: &Page, shared: bool) { let p4 = pt.level_4_table(); let p3 = unsafe { &mut *(p4.index(page.p4_index()).addr().as_u64() as *mut PageTable) }; @@ -237,10 +237,10 @@ fn pt_set_shared_bit(pt: &mut OffsetPageTable, page: &Page, shared: bool) { } } -#[cfg(feature = "tdx")] +#[cfg(feature = "tdcall")] fn pt_entry_set_shared_bit(page_table: &mut PageTable, index: PageTableIndex, shared: bool) { let entry = page_table.index(index); - let shared_bit = tdx_tdcall::tdx::td_shared_mask().expect("Failed to get shared bit of GPA"); + let shared_bit = tdx_tdcall::tdcall::td_shared_mask().expect("Failed to get shared bit of GPA"); let addr = if shared { entry.addr().as_u64() | shared_bit diff --git a/td-payload/src/arch/x86_64/serial.rs b/td-payload/src/arch/x86_64/serial.rs index d61d2838..f4b624eb 100644 --- a/td-payload/src/arch/x86_64/serial.rs +++ b/td-payload/src/arch/x86_64/serial.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Intel Corporation +// Copyright (c) 2022, 2025 Intel Corporation // // SPDX-License-Identifier: BSD-2-Clause-Patent @@ -19,12 +19,12 @@ pub fn serial_write_string(s: &str) { const SERIAL_IO_PORT: u16 = 0x3F8; -#[cfg(feature = "tdx")] +#[cfg(feature = "tdvmcall")] fn io_write(byte: u8) { - tdx_tdcall::tdx::tdvmcall_io_write_8(SERIAL_IO_PORT, byte); + tdx_tdcall::tdvmcall::io_write_8(SERIAL_IO_PORT, byte); } -#[cfg(not(feature = "tdx"))] +#[cfg(not(feature = "tdvmcall"))] fn io_write(byte: u8) { unsafe { x86::io::outb(SERIAL_IO_PORT, byte) }; } diff --git a/td-payload/src/arch/x86_64/shared.rs b/td-payload/src/arch/x86_64/shared.rs index 8d05c5cf..da69482d 100644 --- a/td-payload/src/arch/x86_64/shared.rs +++ b/td-payload/src/arch/x86_64/shared.rs @@ -1,38 +1,57 @@ -// Copyright (c) 2022 Intel Corporation +// Copyright (c) 2022, 2025 Intel Corporation // // SPDX-License-Identifier: BSD-2-Clause-Patent +#[cfg(feature = "tdcall")] use crate::mm::SIZE_4K; -use tdx_tdcall::tdx; +#[cfg(feature = "tdcall")] +use tdx_tdcall::tdcall; + +#[cfg(feature = "tdvmcall")] +use tdx_tdcall::tdvmcall; use super::paging::{clear_shared_bit, set_shared_bit}; +#[cfg(feature = "tdvmcall")] pub fn decrypt(addr: u64, length: usize) { set_shared_bit(addr, length); // Safety: Fail to map GPA is a fatal error that we cannot handle - if tdx::tdvmcall_mapgpa(true, addr, length).is_err() { + if tdvmcall::mapgpa(true, addr, length).is_err() { panic!("Fail to map GPA to shared memory with TDVMCALL"); } } +#[cfg(feature = "tdvmcall")] pub fn encrypt(addr: u64, length: usize) { clear_shared_bit(addr, length); // Safety: Fail to map GPA is a fatal error that we cannot handle - if tdx_tdcall::tdx::tdvmcall_mapgpa(false, addr, length).is_err() { + if tdvmcall::mapgpa(false, addr, length).is_err() { panic!("Fail to map GPA to private memory with TDVMCALL"); } accept_memory(addr, length); } +#[cfg(not(feature = "tdvmcall"))] +pub fn decrypt(addr: u64, length: usize) { + set_shared_bit(addr, length); +} + +#[cfg(not(feature = "tdvmcall"))] +pub fn encrypt(addr: u64, length: usize) { + clear_shared_bit(addr, length); + accept_memory(addr, length); +} + +#[cfg(feature = "tdcall")] fn accept_memory(addr: u64, length: usize) { let page_num = length / SIZE_4K; for p in 0..page_num { - if let Err(e) = tdx::tdcall_accept_page(addr + (p * SIZE_4K) as u64) { - if let tdx_tdcall::TdCallError::LeafSpecific(error_code) = e { - if error_code == tdx_tdcall::TDCALL_STATUS_PAGE_ALREADY_ACCEPTED { + if let Err(e) = tdcall::accept_page(addr + (p * SIZE_4K) as u64) { + if let tdcall::TdCallError::LeafSpecific(error_code) = e { + if error_code == tdcall::TDCALL_STATUS_PAGE_ALREADY_ACCEPTED { continue; } } diff --git a/td-payload/src/bin/example/main.rs b/td-payload/src/bin/example/main.rs index 519574a0..2871ec8a 100644 --- a/td-payload/src/bin/example/main.rs +++ b/td-payload/src/bin/example/main.rs @@ -51,7 +51,7 @@ pub extern "C" fn main() -> ! { stack::bench_stack(); } - #[cfg(feature = "tdx")] + #[cfg(feature = "tdcall")] { use tdx_tdcall::tdreport::TD_REPORT_ADDITIONAL_DATA_SIZE; //Dump TD Report diff --git a/td-payload/src/mm/mod.rs b/td-payload/src/mm/mod.rs index 9cf66a1e..de4e298f 100644 --- a/td-payload/src/mm/mod.rs +++ b/td-payload/src/mm/mod.rs @@ -21,7 +21,6 @@ use crate::Error; #[cfg(any(target_os = "none", target_os = "uefi"))] pub(crate) mod heap; -#[cfg(feature = "tdx")] pub mod shared; #[cfg(not(any(target_os = "none", target_os = "uefi")))] pub(crate) mod heap { diff --git a/td-shim/Cargo.toml b/td-shim/Cargo.toml index f876b241..6d83ddd5 100644 --- a/td-shim/Cargo.toml +++ b/td-shim/Cargo.toml @@ -32,7 +32,7 @@ linked_list_allocator = { version = "0.10", optional = true } log = { version = "0.4.13", features = ["release_max_level_off"], optional = true } ring = { version = "0.17.6", default-features = false, features = ["alloc"], optional = true } spin = { version = "0.9.2", optional = true } -td-exception = { path = "../td-exception", features = ["tdx"], optional = true } +td-exception = { path = "../td-exception", optional = true } td-logger = { path = "../td-logger", optional = true } td-paging = { path = "../td-paging", optional = true } x86 = { version ="0.47.0", optional = true } @@ -47,8 +47,10 @@ tdx-tdcall = { path = "../tdx-tdcall", optional = true } [features] default = ["secure-boot"] secure-boot = ["der", "ring"] -tdx = ["tdx-tdcall", "td-exception/tdx", "td-logger/tdx", "x86"] -lazy-accept = ["tdx"] +tdcall = ["tdx-tdcall/tdcall", "td-exception/tdcall", "x86"] +tdvmcall = ["tdx-tdcall/tdvmcall", "td-exception/tdvmcall", "td-logger/tdvmcall", "x86"] +tdx = ["tdcall", "tdvmcall"] +lazy-accept = ["tdcall"] ring-hash = ["cc-measurement/ring"] sha2-hash = ["cc-measurement/sha2"] main = [ diff --git a/td-shim/src/bin/td-shim/asm/ap_loop_notdvmcall.asm b/td-shim/src/bin/td-shim/asm/ap_loop_notdvmcall.asm new file mode 100644 index 00000000..94ca59c0 --- /dev/null +++ b/td-shim/src/bin/td-shim/asm/ap_loop_notdvmcall.asm @@ -0,0 +1,70 @@ +# Copyright (c) 2022, 2025 Intel Corporation +# SPDX-License-Identifier: BSD-2-Clause-Patent + +.set CommandOffset, 0 +.set ApicIdOffset, 0x4 +.set WakeupVectorOffset, 0x8 + +.set MpProtectedModeWakeupCommandNoop, 0 +.set MpProtectedModeWakeupCommandWakeup, 1 +.set MpProtectedModeWakeupCommandSleep, 2 + +.set MailboxApicIdInvalid, 0xffffffff +.set MailboxApicIdBroadcast, 0xfffffffe + +.section .text + +#-------------------------------------------------------------------- +# ap_relocated_vector +# +# rbx: Relocated mailbox address +# rbp: vCpuId +#-------------------------------------------------------------------- +.global ap_relocated_func +ap_relocated_func: + mov r8, rbx + # + # Get the APIC ID via CPUID + mov rax, 1 + cpuid + shr ebx, 24 + # + # r8 will hold the APIC ID of current AP + xchg r8, rbx + +.check_apicid: + # + # Determine if this is a broadcast or directly for my apic-id, if not, ignore + cmp dword ptr[rbx + ApicIdOffset], MailboxApicIdBroadcast + je .check_command + cmp dword ptr[rbx + ApicIdOffset], r8d + jne .check_apicid + +.check_command: + mov eax, dword ptr[rbx + CommandOffset] + cmp eax, MpProtectedModeWakeupCommandNoop + je .check_apicid + + cmp eax, MpProtectedModeWakeupCommandWakeup + je .wakeup + + jmp .check_apicid + +.wakeup: + # + # BSP sets these variables before unblocking APs + mov rax, 0 + mov eax, dword ptr[rbx + WakeupVectorOffset] + + # + # Clear the command as the acknowledgement that the wake up command is received + mov qword ptr[rbx + CommandOffset], MpProtectedModeWakeupCommandNoop + nop + jmp rax + +.panic: + ud2 + +.global ap_relocated_func_end +ap_relocated_func_end: + jmp .panic diff --git a/td-shim/src/bin/td-shim/asm/exception_notdvmcall.asm b/td-shim/src/bin/td-shim/asm/exception_notdvmcall.asm new file mode 100644 index 00000000..3cbb95ac --- /dev/null +++ b/td-shim/src/bin/td-shim/asm/exception_notdvmcall.asm @@ -0,0 +1,20 @@ +# Copyright (c) 2022, 2025 Intel Corporation +# SPDX-License-Identifier: BSD-2-Clause-Patent + +.section .text + +#-------------------------------------------------------------------- +# empty_exception_handler +# +# We do not need to save the context here because this function will +# never return. +#-------------------------------------------------------------------- +.global empty_exception_handler +empty_exception_handler: + hlt +.exception_loop: + jmp .exception_loop + +.global empty_exception_handler_end +empty_exception_handler_end: + jmp .exception_loop diff --git a/td-shim/src/bin/td-shim/asm/mod.rs b/td-shim/src/bin/td-shim/asm/mod.rs index b1e7ae2d..46a4e892 100644 --- a/td-shim/src/bin/td-shim/asm/mod.rs +++ b/td-shim/src/bin/td-shim/asm/mod.rs @@ -4,8 +4,16 @@ use core::arch::global_asm; global_asm!(include_str!("msr64.asm")); -global_asm!(include_str!("ap_loop.asm")); + +#[cfg(feature = "tdvmcall")] global_asm!(include_str!("exception.asm")); +#[cfg(not(feature = "tdvmcall"))] +global_asm!(include_str!("exception_notdvmcall.asm")); + +#[cfg(feature = "tdvmcall")] +global_asm!(include_str!("ap_loop.asm")); +#[cfg(not(feature = "tdvmcall"))] +global_asm!(include_str!("ap_loop_notdvmcall.asm")); extern "C" { fn ap_relocated_func(); diff --git a/td-shim/src/bin/td-shim/linux/boot.rs b/td-shim/src/bin/td-shim/linux/boot.rs index b0c53da5..6087002c 100644 --- a/td-shim/src/bin/td-shim/linux/boot.rs +++ b/td-shim/src/bin/td-shim/linux/boot.rs @@ -62,13 +62,13 @@ pub fn boot_kernel( e820: &[E820Entry], mailbox: &mut [u8], info: &PayloadInfo, - #[cfg(feature = "tdx")] unaccepted_bitmap: u64, + #[cfg(feature = "tdcall")] unaccepted_bitmap: u64, ) -> Result<(), Error> { let mut params: BootParams = BootParams::default(); params.acpi_rsdp_addr = rsdp_addr; params.e820_entries = e820.len() as u8; params.e820_table[..e820.len()].copy_from_slice(e820); - #[cfg(feature = "tdx")] + #[cfg(feature = "tdcall")] { params.unaccepted_memory = unaccepted_bitmap; } diff --git a/td-shim/src/bin/td-shim/main.rs b/td-shim/src/bin/td-shim/main.rs index 4d9af7e2..19e8f515 100644 --- a/td-shim/src/bin/td-shim/main.rs +++ b/td-shim/src/bin/td-shim/main.rs @@ -196,7 +196,7 @@ fn boot_linux_kernel( e820_table.as_slice(), mailbox, kernel_info, - #[cfg(feature = "tdx")] + #[cfg(feature = "tdcall")] mem.build_unaccepted_memory_bitmap(), ); panic!("Linux kernel should not return here!!!"); diff --git a/td-shim/src/bin/td-shim/memory.rs b/td-shim/src/bin/td-shim/memory.rs index d90cb8da..351aa326 100644 --- a/td-shim/src/bin/td-shim/memory.rs +++ b/td-shim/src/bin/td-shim/memory.rs @@ -224,7 +224,7 @@ impl<'a> Memory<'a> { regions.push(new); } - #[cfg(feature = "tdx")] + #[cfg(feature = "tdcall")] Self::accept_memory_resources(&mut regions); regions @@ -245,7 +245,7 @@ impl<'a> Memory<'a> { false } - #[cfg(feature = "tdx")] + #[cfg(feature = "tdcall")] /// Build a 2M granularity bitmap for kernel to track the unaccepted memory pub fn build_unaccepted_memory_bitmap(&self) -> u64 { #[cfg(not(feature = "lazy-accept"))] @@ -287,7 +287,7 @@ impl<'a> Memory<'a> { .base_address as u64 } - #[cfg(feature = "tdx")] + #[cfg(feature = "tdcall")] fn accept_memory_resources(resources: &mut Vec) { use td_layout::TD_PAYLOAD_PARTIAL_ACCEPT_MEMORY_SIZE; use td_shim_interface::td_uefi_pi::pi; diff --git a/td-shim/src/bin/td-shim/td/mod.rs b/td-shim/src/bin/td-shim/td/mod.rs index c20a1473..2587da94 100644 --- a/td-shim/src/bin/td-shim/td/mod.rs +++ b/td-shim/src/bin/td-shim/td/mod.rs @@ -2,14 +2,14 @@ // // SPDX-License-Identifier: BSD-2-Clause-Patent -#[cfg(feature = "tdx")] +#[cfg(feature = "tdcall")] mod tdx; -#[cfg(feature = "tdx")] +#[cfg(feature = "tdcall")] mod tdx_mailbox; -#[cfg(feature = "tdx")] +#[cfg(feature = "tdcall")] pub use tdx::*; -#[cfg(not(feature = "tdx"))] +#[cfg(not(feature = "tdcall"))] mod dummy; -#[cfg(not(feature = "tdx"))] +#[cfg(not(feature = "tdcall"))] pub use dummy::*; diff --git a/td-shim/src/bin/td-shim/td/tdx.rs b/td-shim/src/bin/td-shim/td/tdx.rs index fad23d6e..ac508ed0 100644 --- a/td-shim/src/bin/td-shim/td/tdx.rs +++ b/td-shim/src/bin/td-shim/td/tdx.rs @@ -4,7 +4,7 @@ use cc_measurement::log::CcEventLogError; use td_exception::idt::DescriptorTablePointer; -use tdx_tdcall::tdx; +use tdx_tdcall::tdcall; extern "win64" { fn asm_read_msr64(index: u32) -> u64; @@ -17,7 +17,7 @@ const EXTENDED_PROCESSOR_INFO: u32 = 0x80000001; const SHA384_DIGEST_SIZE: usize = 48; pub fn get_shared_page_mask() -> u64 { - tdx::td_shared_mask().expect("Unable to get GPAW") + tdcall::td_shared_mask().expect("Unable to get GPAW") } pub fn accept_memory_resource_range(address: u64, size: u64) { @@ -38,7 +38,7 @@ pub fn set_idt(idt_ptr: &DescriptorTablePointer) { } pub fn get_num_vcpus() -> u32 { - let td_info = tdx::tdcall_get_td_info().expect("Fail to get TDINFO"); + let td_info = tdcall::get_td_info().expect("Fail to get TDINFO"); log::info!("gpaw - {:?}\n", td_info.gpaw); log::info!("num_vcpus - {:?}\n", td_info.num_vcpus); @@ -47,12 +47,12 @@ pub fn get_num_vcpus() -> u32 { } pub fn extend_rtmr(data: &[u8; SHA384_DIGEST_SIZE], mr_index: u32) -> Result<(), CcEventLogError> { - let digest = tdx::TdxDigest { data: *data }; + let digest = tdcall::TdxDigest { data: *data }; let rtmr_index = match mr_index { 1 | 2 | 3 | 4 => mr_index - 1, e => return Err(CcEventLogError::InvalidMrIndex(e)), }; - tdx::tdcall_extend_rtmr(&digest, rtmr_index).map_err(|_| CcEventLogError::ExtendMr) + tdcall::extend_rtmr(&digest, rtmr_index).map_err(|_| CcEventLogError::ExtendMr) } diff --git a/td-shim/src/bin/td-shim/td/tdx_mailbox.rs b/td-shim/src/bin/td-shim/td/tdx_mailbox.rs index a3039677..810b3e87 100644 --- a/td-shim/src/bin/td-shim/td/tdx_mailbox.rs +++ b/td-shim/src/bin/td-shim/td/tdx_mailbox.rs @@ -10,7 +10,7 @@ use core::cmp::min; use core::ops::RangeInclusive; use td_exception::idt::DescriptorTablePointer; use td_layout::memslice::{get_mem_slice_mut, SliceType}; -use tdx_tdcall::{self, tdx::*}; +use tdx_tdcall::{self, tdcall::*}; use crate::asm::{ap_relocated_func_addr, ap_relocated_func_size}; diff --git a/tdx-tdcall/CHANGELOG.md b/tdx-tdcall/CHANGELOG.md index 1b06898f..58a3c273 100644 --- a/tdx-tdcall/CHANGELOG.md +++ b/tdx-tdcall/CHANGELOG.md @@ -1,6 +1,11 @@ # Changelog All notable changes to this project will be documented in this file. +## [0.3.0] - 2025-03-05 +### Changed +- Split tdx feature into two features: tdcall, tdvmcall +- Moved corresponding functions into tdcall.rs and tdvmcall.rs + ## [0.2.1] - 2024-07-23 ### Changed - Remove nightly feature in the x86_64 crate diff --git a/tdx-tdcall/Cargo.toml b/tdx-tdcall/Cargo.toml index ff6fe2d4..75d3cbe0 100644 --- a/tdx-tdcall/Cargo.toml +++ b/tdx-tdcall/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tdx-tdcall" -version = "0.2.1" +version = "0.3.0" description = "Constants, stuctures and wrappers to access TDCALL services" repository = "https://github.com/confidential-containers/td-shim" homepage = "https://github.com/confidential-containers" @@ -18,5 +18,7 @@ spin = "0.9.2" x86_64 = { version = "0.14.9", default-features = false, features = ["instructions"] } [features] -default = [] +default = ["tdcall", "tdvmcall"] use_tdx_emulation = [] +tdvmcall = [] +tdcall = [] diff --git a/tdx-tdcall/src/asm/mod.rs b/tdx-tdcall/src/asm/mod.rs index 02e43d40..33ee4853 100644 --- a/tdx-tdcall/src/asm/mod.rs +++ b/tdx-tdcall/src/asm/mod.rs @@ -4,19 +4,21 @@ use core::arch::global_asm; use core::ffi::c_void; -#[cfg(feature = "use_tdx_emulation")] +#[cfg(all(feature = "use_tdx_emulation", feature = "tdcall"))] global_asm!(include_str!("tdcall_emu.asm")); -#[cfg(feature = "use_tdx_emulation")] +#[cfg(all(feature = "use_tdx_emulation", feature = "tdvmcall"))] global_asm!(include_str!("tdvmcall_emu.asm")); -#[cfg(not(feature = "use_tdx_emulation"))] +#[cfg(all(not(feature = "use_tdx_emulation"), feature = "tdcall"))] global_asm!(include_str!("tdcall.asm")); -#[cfg(not(feature = "use_tdx_emulation"))] +#[cfg(all(not(feature = "use_tdx_emulation"), feature = "tdvmcall"))] global_asm!(include_str!("tdvmcall.asm")); extern "win64" { + #[cfg(feature = "tdcall")] pub(crate) fn asm_td_call(args: *mut c_void) -> u64; + #[cfg(feature = "tdvmcall")] pub(crate) fn asm_td_vmcall(args: *mut c_void, do_sti: u64) -> u64; } diff --git a/tdx-tdcall/src/lib.rs b/tdx-tdcall/src/lib.rs index a4b572e6..6275da53 100644 --- a/tdx-tdcall/src/lib.rs +++ b/tdx-tdcall/src/lib.rs @@ -29,187 +29,9 @@ pub const USE_TDX_EMULATION: bool = true; pub const USE_TDX_EMULATION: bool = false; pub mod asm; +#[cfg(feature = "tdcall")] +pub mod tdcall; +#[cfg(feature = "tdcall")] pub mod tdreport; -pub mod tdx; - -// Guest-Side (TDCALL) interface functions leaf numbers -const TDCALL_TDINFO: u64 = 1; -const TDCALL_TDEXTENDRTMR: u64 = 2; -const TDCALL_TDGETVEINFO: u64 = 3; -const TDCALL_TDREPORT: u64 = 4; -const TDCALL_TDACCEPTPAGE: u64 = 6; -const TDCALL_VM_RD: u64 = 7; -const TDCALL_VM_WR: u64 = 8; -const TDCALL_VP_RD: u64 = 9; -const TDCALL_VP_WR: u64 = 10; -const TDCALL_SYS_RD: u64 = 11; -const TDCALL_SERVTD_RD: u64 = 18; -const TDCALL_SERVTD_WR: u64 = 20; -const TDCALL_MEM_PAGE_ATTR_WR: u64 = 24; -const TDCALL_VP_ENTER: u64 = 25; -const TDCALL_VP_INVEPT: u64 = 26; -const TDCALL_VP_INVVPID: u64 = 27; - -// GTDG.VP.VMCALL leaf sub-function numbers -const TDVMCALL_CPUID: u64 = 0x0000a; -const TDVMCALL_HALT: u64 = 0x0000c; -const TDVMCALL_IO: u64 = 0x0001e; -const TDVMCALL_RDMSR: u64 = 0x0001f; -const TDVMCALL_WRMSR: u64 = 0x00020; -const TDVMCALL_MMIO: u64 = 0x00030; -const TDVMCALL_MAPGPA: u64 = 0x10001; -const TDVMCALL_GETQUOTE: u64 = 0x10002; -const TDVMCALL_SETUPEVENTNOTIFY: u64 = 0x10004; -const TDVMCALL_SERVICE: u64 = 0x10005; - -// TDCALL completion status code -const TDCALL_STATUS_SUCCESS: u64 = 0; - -// leaf-specific completion status code -pub const TDCALL_STATUS_PAGE_ALREADY_ACCEPTED: u64 = 0x00000B0A00000000; -pub const TDCALL_STATUS_PAGE_SIZE_MISMATCH: u64 = 0xC0000B0B00000001; - -// TDVMCALL completion status code -const TDVMCALL_STATUS_SUCCESS: u64 = 0; -const TDVMCALL_STATUS_RETRY: u64 = 1; - -// A public wrapper for use of asm_td_vmcall, this function takes a mutable reference of a -// TdcallArgs structure to ensure the input is valid -// -// ## TDVMCALL ABI -// Defined in GHCI Spec section 'TDCALL [TDG.VP.VMCALL] leaf' -// -// ### Input Operands: -// * RAX - TDCALL instruction leaf number (0 - TDG.VP.VMCALL) -// * RCX - A bitmap that controls which part of guest TD GPR is exposed to VMM. -// * R10 - Set to 0 indicates leaf-function used in R11 is defined in standard GHCI Spec. -// * R11 - TDG.VP.VMCALL sub-function is R10 is zero -// * RBX, RBP, RDI, RSI, R8-R10, R12-R15 - Used to pass values to VMM in sub-functions. -// -// ### Output Operands: -// * RAX - TDCALL instruction return code, always return Success(0). -// * R10 - TDG.VP.VMCALL sub-function return value -// * R11 - Correspond to each TDG.VP.VMCALL. -// * R8-R9, R12-R15, RBX, RBP, RDI, RSI - Correspond to each TDG.VP.VMCALL sub-function. -// -pub fn td_vmcall(args: &mut TdVmcallArgs) -> u64 { - unsafe { asm::asm_td_vmcall(args as *mut TdVmcallArgs as *mut c_void, 0) } -} - -// An extended public wrapper for use of asm_td_vmcall. -// -// `do_sti` is a flag used to determine whether to execute `sti` instruction before `tdcall` -pub fn td_vmcall_ex(args: &mut TdVmcallArgs, do_sti: bool) -> u64 { - unsafe { asm::asm_td_vmcall(args as *mut TdVmcallArgs as *mut c_void, do_sti as u64) } -} - -// Wrapper for use of asm_td_call, this function takes a mutable reference of a -// TdVmcallArgs structure to ensure the input is valid -// -// ## TDCALL ABI -// Defined in TDX Module 1.0 Spec section 'TDCALL Instruction (Common)' -// -// ### Input Operands: -// * RAX - Leaf and version numbers. -// * Other - Used by leaf functions as input values. -// -// ### Output Operands: -// * RAX - Instruction return code. -// * Other - Used by leaf functions as output values. -// -pub fn td_call(args: &mut TdcallArgs) -> u64 { - unsafe { asm::asm_td_call(args as *mut TdcallArgs as *mut c_void) } -} - -// Used to pass the values of input/output register when performing TDVMCALL -// instruction -#[repr(C)] -#[derive(Default)] -pub struct TdcallArgs { - pub rax: u64, - pub rcx: u64, - pub rdx: u64, - pub r8: u64, - pub r9: u64, - pub r10: u64, - pub r11: u64, - pub r12: u64, - pub r13: u64, -} - -// Used to pass the values of input/output register when performing TDVMCALL -// instruction -#[repr(C)] -#[derive(Default)] -pub struct TdVmcallArgs { - // Input: Always 0 for (standard VMCALL) - // Output: Sub-function - pub r10: u64, - pub r11: u64, - pub r12: u64, - pub r13: u64, - pub r14: u64, - pub r15: u64, -} - -/// TDCALL instruction return error code -/// -/// Refer to Intel TDX Module 1.0 Specifiction section 'TDCALL Instruction (Common)' -#[derive(Debug, PartialEq)] -pub enum TdCallError { - // Invalid parameters - TdxExitInvalidParameters, - - // The operand is busy (e.g., it is locked in Exclusive mode) - TdxExitReasonOperandBusy(u32), - - // Operand is invalid (e.g., illegal leaf number) - TdxExitReasonOperandInvalid(u32), - - // Error code defined by individual leaf function - LeafSpecific(u64), -} - -// TDCALL Completion Status Codes (Returned in RAX) Definition -impl From for TdCallError { - fn from(val: u64) -> Self { - match val >> 32 { - 0x8000_0200 => Self::TdxExitReasonOperandBusy(val as u32), - 0xC000_0100 => Self::TdxExitReasonOperandInvalid(val as u32), - _ => Self::LeafSpecific(val), - } - } -} - -/// TDVMCALL sub-function return error code -/// -/// Refer to Guest-Host-Communication-Interface(GHCI) for Intel TDX -/// table 'TDCALL[TDG.VP.VMCALL]- Sub-function Completion-Status Codes' -#[derive(Debug, PartialEq)] -pub enum TdVmcallError { - // TDCALL[TDG.VP.VMCALL] sub-function invocation must be retried - VmcallRetry, - - // Invalid operand to TDG.VP.VMCALL sub-function - VmcallOperandInvalid, - - // GPA already mapped - VmcallGpaInuse, - - // Operand (address) alignment error - VmcallAlignError, - - Other, -} - -impl From for TdVmcallError { - fn from(val: u64) -> Self { - match val { - 0x1 => TdVmcallError::VmcallRetry, - 0x8000_0000_0000_0000 => TdVmcallError::VmcallOperandInvalid, - 0x8000_0000_0000_0001 => TdVmcallError::VmcallGpaInuse, - 0x8000_0000_0000_0002 => TdVmcallError::VmcallAlignError, - _ => TdVmcallError::Other, - } - } -} +#[cfg(feature = "tdvmcall")] +pub mod tdvmcall; diff --git a/tdx-tdcall/src/tdcall.rs b/tdx-tdcall/src/tdcall.rs new file mode 100644 index 00000000..2ea1626d --- /dev/null +++ b/tdx-tdcall/src/tdcall.rs @@ -0,0 +1,625 @@ +// Copyright (c) 2020-2022, 2025 Intel Corporation +// +// SPDX-License-Identifier: BSD-2-Clause-Patent + +//! Implemention of a subset of TDCALL functions defined in +//! Intel TDX Module v1.0 and v1.5 Spec. +//! +//! The TDCALL instruction causes a VM exit to the Intel TDX Module. It is used to call +//! guest-side Intel TDX functions, either local or a TD exit to the host VMM. + +use crate::*; +use core::result::Result; + +pub const PAGE_SIZE_4K: u64 = 0x1000; +pub const PAGE_SIZE_2M: u64 = 0x200000; +pub const TARGET_TD_UUID_NUM: usize = 4; + +// Guest-Side (TDCALL) interface functions leaf numbers +pub const TDCALL_TDINFO: u64 = 1; +pub const TDCALL_TDEXTENDRTMR: u64 = 2; +pub const TDCALL_TDGETVEINFO: u64 = 3; +pub const TDCALL_TDREPORT: u64 = 4; +pub const TDCALL_TDACCEPTPAGE: u64 = 6; +pub const TDCALL_VM_RD: u64 = 7; +pub const TDCALL_VM_WR: u64 = 8; +pub const TDCALL_VP_RD: u64 = 9; +pub const TDCALL_VP_WR: u64 = 10; +pub const TDCALL_SYS_RD: u64 = 11; +pub const TDCALL_SERVTD_RD: u64 = 18; +pub const TDCALL_SERVTD_WR: u64 = 20; +pub const TDCALL_MEM_PAGE_ATTR_WR: u64 = 24; +pub const TDCALL_VP_ENTER: u64 = 25; +pub const TDCALL_VP_INVEPT: u64 = 26; +pub const TDCALL_VP_INVVPID: u64 = 27; + +// TDCALL completion status code +pub const TDCALL_STATUS_SUCCESS: u64 = 0; + +// leaf-specific completion status code +pub const TDCALL_STATUS_PAGE_ALREADY_ACCEPTED: u64 = 0x00000B0A00000000; +pub const TDCALL_STATUS_PAGE_SIZE_MISMATCH: u64 = 0xC0000B0B00000001; + +/// SHA384 digest value extended to RTMR +/// Both alignment and size are 64 bytes. +#[repr(C, align(64))] +pub struct TdxDigest { + pub data: [u8; 48], +} + +/// Guest TD execution evironment returned from TDG.VP.INFO leaf +#[repr(C)] +#[derive(Debug, Default)] +pub struct TdInfo { + pub gpaw: u64, + pub attributes: u64, + pub max_vcpus: u32, + pub num_vcpus: u32, + pub vcpu_index: u32, + pub rsvd: [u32; 5], +} + +/// Virtualization exception information returned from TDG.VP.VEINFO.GET leaf +#[repr(C)] +#[derive(Debug, Default)] +pub struct TdVeInfo { + pub exit_reason: u32, + pub rsvd: u32, + pub exit_qualification: u64, + pub guest_la: u64, + pub guest_pa: u64, + pub exit_instruction_length: u32, + pub exit_instruction_info: u32, + pub rsvd1: u64, +} + +#[derive(Debug, Default)] +#[repr(C)] +pub struct ServtdRWResult { + pub content: u64, + pub uuid: [u64; 4], +} + +// Wrapper for use of asm_td_call, this function takes a mutable reference of a +// TdVmcallArgs structure to ensure the input is valid +// +// ## TDCALL ABI +// Defined in TDX Module 1.0 Spec section 'TDCALL Instruction (Common)' +// +// ### Input Operands: +// * RAX - Leaf and version numbers. +// * Other - Used by leaf functions as input values. +// +// ### Output Operands: +// * RAX - Instruction return code. +// * Other - Used by leaf functions as output values. +// +pub fn td_call(args: &mut TdcallArgs) -> u64 { + unsafe { asm::asm_td_call(args as *mut TdcallArgs as *mut c_void) } +} + +// Used to pass the values of input/output register when performing TDCALL +// instruction +#[repr(C)] +#[derive(Default)] +pub struct TdcallArgs { + pub rax: u64, + pub rcx: u64, + pub rdx: u64, + pub r8: u64, + pub r9: u64, + pub r10: u64, + pub r11: u64, + pub r12: u64, + pub r13: u64, +} + +/// TDCALL instruction return error code +/// +/// Refer to Intel TDX Module 1.0 Specifiction section 'TDCALL Instruction (Common)' +#[derive(Debug, PartialEq)] +pub enum TdCallError { + // Invalid parameters + TdxExitInvalidParameters, + + // The operand is busy (e.g., it is locked in Exclusive mode) + TdxExitReasonOperandBusy(u32), + + // Operand is invalid (e.g., illegal leaf number) + TdxExitReasonOperandInvalid(u32), + + // Error code defined by individual leaf function + LeafSpecific(u64), +} + +// TDCALL Completion Status Codes (Returned in RAX) Definition +impl From for TdCallError { + fn from(val: u64) -> Self { + match val >> 32 { + 0x8000_0200 => Self::TdxExitReasonOperandBusy(val as u32), + 0xC000_0100 => Self::TdxExitReasonOperandInvalid(val as u32), + _ => Self::LeafSpecific(val), + } + } +} + +/// Get guest TD execution environment information +/// +/// Details can be found in TDX Module ABI spec section 'TDG.VP.INFO Leaf' +pub fn get_td_info() -> Result { + let mut args = TdcallArgs { + rax: TDCALL_TDINFO, + ..Default::default() + }; + + let ret = td_call(&mut args); + + if ret != TDCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + let td_info = TdInfo { + gpaw: args.rcx & 0x3f, + attributes: args.rdx, + max_vcpus: (args.r8 >> 32) as u32, + num_vcpus: args.r8 as u32, + vcpu_index: args.r9 as u32, + ..Default::default() + }; + + Ok(td_info) +} + +/// Extend a TDCS.RTMR measurement register +/// +/// Details can be found in TDX Module ABI spec section 'TDG.VP.INFO Leaf' +pub fn extend_rtmr(digest: &TdxDigest, mr_index: u32) -> Result<(), TdCallError> { + let buffer: u64 = core::ptr::addr_of!(digest.data) as u64; + + let mut args = TdcallArgs { + rax: TDCALL_TDEXTENDRTMR, + rcx: buffer, + rdx: mr_index as u64, + ..Default::default() + }; + + let ret = td_call(&mut args); + + if ret != TDCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + Ok(()) +} + +/// Get virtualization exception information for the recent #VE +/// +/// Details can be found in TDX Module ABI spec section 'TDG.VP.VEINFO.GET Leaf' +pub fn get_ve_info() -> Result { + let mut args = TdcallArgs { + rax: TDCALL_TDGETVEINFO, + ..Default::default() + }; + + let ret = td_call(&mut args); + + if ret != TDCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + let ve_info = TdVeInfo { + exit_reason: args.rcx as u32, + exit_qualification: args.rdx, + guest_la: args.r8, + guest_pa: args.r9, + exit_instruction_length: args.r10 as u32, + exit_instruction_info: (args.r10 >> 32) as u32, + ..Default::default() + }; + + Ok(ve_info) +} + +/// Accept a pending private page, and initialize the page to zeros using the TD ephemeral +/// private key +/// +/// Details can be found in TDX Module ABI spec section 'TDG.MEM.PAGE.Accept Leaf' +pub fn accept_page(address: u64) -> Result<(), TdCallError> { + let mut args = TdcallArgs { + rax: TDCALL_TDACCEPTPAGE, + rcx: address, + ..Default::default() + }; + + const MAX_RETRIES_ACCEPT_PAGE: usize = 5; + let mut retry_counter = 0; + let mut ret = 0; + + while retry_counter < MAX_RETRIES_ACCEPT_PAGE { + ret = td_call(&mut args); + + if ret == TDCALL_STATUS_SUCCESS { + return Ok(()); + } else { + match TdCallError::from(ret) { + TdCallError::TdxExitReasonOperandBusy(_) => retry_counter += 1, + e => return Err(e), + } + } + } + + Err(ret.into()) +} + +/// Accept a range of private pages and initialize the pages to zeros using the TD ephemeral +/// private key. +/// +/// This function is a wrapper to `accept_page()`. +pub fn td_accept_pages(address: u64, pages: u64, page_size: u64) { + for i in 0..pages { + let accept_addr = address + i * page_size; + let accept_level = if page_size == PAGE_SIZE_2M { 1 } else { 0 }; + match accept_page(accept_addr | accept_level) { + Ok(()) => {} + Err(e) => { + if let TdCallError::LeafSpecific(error_code) = e { + if error_code == TDCALL_STATUS_PAGE_SIZE_MISMATCH { + if page_size == PAGE_SIZE_2M { + td_accept_pages(accept_addr, 512, PAGE_SIZE_4K); + continue; + } + } else if error_code == TDCALL_STATUS_PAGE_ALREADY_ACCEPTED { + continue; + } + } + panic!( + "Accept Page Error: 0x{:x}, page_size: {}, err {:x?}\n", + accept_addr, page_size, e + ); + } + } + } +} + +/// Accept a range of either 4K normal pages or 2M huge pages. This is basically a wrapper over +/// td_accept_pages and initializes the pages to zero using the TD ephemeral private key. +pub fn td_accept_memory(address: u64, len: u64) { + let mut start = address; + let end = address + len; + + while start < end { + let remaining = end - start; + + // Try larger accepts first to keep 1G/2M Secure EPT entries + // where possible and speeds up process by cutting number of + // tdcalls (if successful). + if remaining >= PAGE_SIZE_2M && (start & (PAGE_SIZE_2M - 1)) == 0 { + let npages = remaining >> 21; + td_accept_pages(start, npages, PAGE_SIZE_2M); + start += npages << 21; + } else if remaining >= PAGE_SIZE_4K && (start & (PAGE_SIZE_4K - 1)) == 0 { + let mut npages = remaining >> 12; + // Try to consume in 4K chunks until 2M aligned. + if remaining >= PAGE_SIZE_2M { + npages = (PAGE_SIZE_2M - (start & (PAGE_SIZE_2M - 1))) >> 12; + } + td_accept_pages(start, npages, PAGE_SIZE_4K); + start += npages << 12; + } else { + panic!("Accept Memory Error: 0x{:x}, length: {}\n", address, len); + } + } +} + +/// Get the guest physical address (GPA) width via TDG.VP.INFO +/// The GPA width can be used to determine the shared-bit of GPA +pub fn td_shared_mask() -> Option { + let td_info = get_td_info().ok()?; + let gpaw = (td_info.gpaw & 0x3f) as u8; + + // Detail can be found in TDX Module v1.5 ABI spec section 'TDVPS(excluding TD VMCS)'. + if gpaw == 48 || gpaw == 52 { + Some(1u64 << (gpaw - 1)) + } else { + None + } +} + +/// Used by a service TD to read a metadata field (control structure field) of +/// a target TD. +/// +/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.SERVTD.RD Leaf'. +pub fn servtd_rd( + binding_handle: u64, + field_identifier: u64, + target_td_uuid: &[u64], +) -> Result { + if target_td_uuid.len() != TARGET_TD_UUID_NUM { + return Err(TdCallError::TdxExitInvalidParameters); + } + + let mut args = TdcallArgs { + rax: TDCALL_SERVTD_RD, + rcx: binding_handle, + rdx: field_identifier, + r10: target_td_uuid[0], + r11: target_td_uuid[1], + r12: target_td_uuid[2], + r13: target_td_uuid[3], + ..Default::default() + }; + + let ret = td_call(&mut args); + + if ret != TDCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + let sertd_rw_result = ServtdRWResult { + content: args.r8, + uuid: [args.r10, args.r11, args.r12, args.r13], + }; + + Ok(sertd_rw_result) +} + +/// Used by a service TD to write a metadata field (control structure field) of +/// a target TD. +/// +/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.SERVTD.RD Leaf'. +pub fn servtd_wr( + binding_handle: u64, + field_identifier: u64, + data: u64, + target_td_uuid: &[u64], +) -> Result { + if target_td_uuid.len() != TARGET_TD_UUID_NUM { + return Err(TdCallError::TdxExitInvalidParameters); + } + + let mut args = TdcallArgs { + rax: TDCALL_SERVTD_WR, + rcx: binding_handle, + rdx: field_identifier, + r8: data, + r9: u64::MAX, + r10: target_td_uuid[0], + r11: target_td_uuid[1], + r12: target_td_uuid[2], + r13: target_td_uuid[3], + }; + + let ret = td_call(&mut args); + + if ret != TDCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + let result = ServtdRWResult { + content: args.r8, + uuid: [args.r10, args.r11, args.r12, args.r13], + }; + + Ok(result) +} + +/// Used to read a TDX Module global-scope metadata field. +/// +/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.SYS.RD Leaf'. +pub fn sys_rd(field_identifier: u64) -> core::result::Result<(u64, u64), TdCallError> { + let mut args = TdcallArgs { + rax: TDCALL_SYS_RD, + rdx: field_identifier, + ..Default::default() + }; + + let ret = td_call(&mut args); + + if ret != TDCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + Ok((args.rdx, args.r8)) +} + +/// Read a VCPU-scope metadata field (control structure field) of a TD. +/// +/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.VP.RD Leaf'. +pub fn vp_read(field: u64) -> Result<(u64, u64), TdCallError> { + let mut args = TdcallArgs { + rax: TDCALL_VP_RD, + rdx: field, + ..Default::default() + }; + + let ret = td_call(&mut args); + + if ret != TDCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + Ok((args.rdx, args.r8)) +} + +/// Write a VCPU-scope metadata field (control structure field) of a TD. +/// +/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.VP.WR Leaf'. +pub fn vp_write(field: u64, value: u64, mask: u64) -> Result { + let mut args = TdcallArgs { + rax: TDCALL_VP_WR, + rdx: field, + r8: value, + r9: mask, + ..Default::default() + }; + + let ret = td_call(&mut args); + + if ret != TDCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + Ok(args.r8) +} + +/// Invalidate mappings in the translation lookaside buffers (TLBs) and paging-structure caches +/// for a specified L2 VM and a specified list of 4KB page linear addresses. +/// +/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.VP.INVVPID Leaf'. +pub fn vp_invvpid(flags: u64, gla: u64) -> Result { + let mut args = TdcallArgs { + rax: TDCALL_VP_INVVPID, + rcx: flags, + rdx: gla, + ..Default::default() + }; + + let ret = td_call(&mut args); + + if ret != TDCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + Ok(args.rdx) +} + +/// Invalidate cached EPT translations for selected L2 VM. +/// +/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.VP.INVEPT Leaf'. +pub fn vp_invept(vm_flags: u64) -> Result<(), TdCallError> { + let mut args = TdcallArgs { + rax: TDCALL_VP_INVEPT, + rcx: vm_flags, + ..Default::default() + }; + + let ret = td_call(&mut args); + + if ret != TDCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + Ok(()) +} + +/// Enter L2 VCPU operation. +/// +/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.VP.ENTER Leaf'. +pub fn vp_enter(vm_flags: u64, gpa: u64) -> TdcallArgs { + let mut args = TdcallArgs { + rax: TDCALL_VP_ENTER, + rcx: vm_flags, + rdx: gpa, + ..Default::default() + }; + + td_call(&mut args); + + args +} + +/// Read a TD-scope metadata field (control structure field) of a TD. +/// +/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.VM.RD Leaf'. +pub fn vm_read(field: u64, version: u8) -> Result<(u64, u64), TdCallError> { + let mut args = TdcallArgs { + rax: TDCALL_VM_RD | (version as u64) << 16, + rdx: field, + ..Default::default() + }; + + let ret = td_call(&mut args); + + if ret != TDCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + Ok((args.rdx, args.r8)) +} + +/// Write a TD-scope metadata field (control structure field) of a TD. +/// +/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.VM.WR Leaf'. +pub fn vm_write(field: u64, value: u64, mask: u64) -> Result { + let mut args = TdcallArgs { + rax: TDCALL_VM_WR, + rdx: field, + r8: value, + r9: mask, + ..Default::default() + }; + + let ret = td_call(&mut args); + + if ret != TDCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + Ok(args.r8) +} + +/// Write the attributes of a private page. Create or remove L2 page aliases as required. +/// +/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.MEM.PAGE.ATTR.WR Leaf'. +pub fn mem_page_attr_wr( + gpa_mapping: u64, + gpa_attr: u64, + attr_flags: u64, +) -> Result<(u64, u64), TdCallError> { + let mut args = TdcallArgs { + rax: TDCALL_MEM_PAGE_ATTR_WR, + rcx: gpa_mapping, + rdx: gpa_attr, + r8: attr_flags, + ..Default::default() + }; + + const MAX_RETRIES_ATTR_WR: usize = 5; + let mut retry_counter = 0; + let mut ret = 0; + + while retry_counter < MAX_RETRIES_ATTR_WR { + ret = td_call(&mut args); + + if ret == TDCALL_STATUS_SUCCESS { + return Ok((args.rcx, args.rdx)); + } else { + match TdCallError::from(ret) { + TdCallError::TdxExitReasonOperandBusy(_) => retry_counter += 1, + e => return Err(e), + } + } + } + + Err(ret.into()) +} + +#[cfg(test)] +mod tests { + use super::*; + use core::mem::{align_of, size_of}; + + #[test] + fn test_struct_size_alignment() { + assert_eq!(align_of::(), 64); + assert_eq!(size_of::(), 64); + assert_eq!(size_of::(), 48); + assert_eq!(size_of::(), 48); + } + + #[test] + fn test_servtd_rd() { + let uuid: [u64; 3] = [0; 3]; + let ret = servtd_rd(0x0, 0x0, &uuid); + + assert!(ret.is_err()); + } + + #[test] + fn test_servtd_wr() { + let uuid: [u64; 3] = [0; 3]; + let ret = servtd_wr(0x0, 0x0, 0x0, &uuid); + + assert!(ret.is_err()); + } +} diff --git a/tdx-tdcall/src/tdreport.rs b/tdx-tdcall/src/tdreport.rs index 87470eab..059a5707 100644 --- a/tdx-tdcall/src/tdreport.rs +++ b/tdx-tdcall/src/tdreport.rs @@ -7,7 +7,7 @@ use core::mem::{size_of, zeroed}; use core::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut}; use scroll::{Pread, Pwrite}; -use crate::{td_call, TdCallError, TdcallArgs, TDCALL_TDREPORT, TDVMCALL_STATUS_SUCCESS}; +use crate::tdcall::{td_call, TdCallError, TdcallArgs, TDCALL_STATUS_SUCCESS, TDCALL_TDREPORT}; pub const TD_REPORT_SIZE: usize = 0x400; pub const TD_REPORT_ADDITIONAL_DATA_SIZE: usize = 64; @@ -208,7 +208,7 @@ pub fn tdcall_report( }; let ret = td_call(&mut args); - if ret != TDVMCALL_STATUS_SUCCESS { + if ret != TDCALL_STATUS_SUCCESS { return Err(args.r10.into()); } diff --git a/tdx-tdcall/src/tdvmcall.rs b/tdx-tdcall/src/tdvmcall.rs new file mode 100644 index 00000000..71872a9b --- /dev/null +++ b/tdx-tdcall/src/tdvmcall.rs @@ -0,0 +1,537 @@ +// Copyright (c) 2020-2022, 2025 Intel Corporation +// +// SPDX-License-Identifier: BSD-2-Clause-Patent + +//! Implemention of a subset of TDVMCALL sub-functions defined in TDX GHCI Spec. +//! +//! TDVMCALL (TDG.VP.VMCALL) is a leaf function 0 for TDCALL. It helps invoke services from +//! the host VMM. + +use crate::*; +use core::result::Result; +use core::sync::atomic::{fence, Ordering}; +use lazy_static::lazy_static; +use x86_64::registers::rflags::{self, RFlags}; + +lazy_static! { + static ref SHARED_MASK: u64 = + tdcall::td_shared_mask().expect("Fail to get the shared mask of TD"); +} + +// GTDG.VP.VMCALL leaf sub-function numbers +const TDVMCALL_CPUID: u64 = 0x0000a; +const TDVMCALL_HALT: u64 = 0x0000c; +const TDVMCALL_IO: u64 = 0x0001e; +const TDVMCALL_RDMSR: u64 = 0x0001f; +const TDVMCALL_WRMSR: u64 = 0x00020; +const TDVMCALL_MMIO: u64 = 0x00030; +const TDVMCALL_MAPGPA: u64 = 0x10001; +const TDVMCALL_GETQUOTE: u64 = 0x10002; +const TDVMCALL_SETUPEVENTNOTIFY: u64 = 0x10004; +const TDVMCALL_SERVICE: u64 = 0x10005; + +// TDVMCALL completion status code +const TDVMCALL_STATUS_SUCCESS: u64 = 0; +const TDVMCALL_STATUS_RETRY: u64 = 1; + +// Used to pass the values of input/output register when performing TDVMCALL +// instruction +#[repr(C)] +#[derive(Default)] +pub struct TdVmcallArgs { + // Input: Always 0 for (standard VMCALL) + // Output: Sub-function + pub r10: u64, + pub r11: u64, + pub r12: u64, + pub r13: u64, + pub r14: u64, + pub r15: u64, +} + +/// Emulated CPUID values returned from TDG.VP.VMCALL +#[repr(C)] +#[derive(Debug, Default)] +pub struct CpuIdInfo { + pub eax: u32, + pub ebx: u32, + pub ecx: u32, + pub edx: u32, +} + +/// TDVMCALL sub-function return error code +/// +/// Refer to Guest-Host-Communication-Interface(GHCI) for Intel TDX +/// table 'TDCALL[TDG.VP.VMCALL]- Sub-function Completion-Status Codes' +#[derive(Debug, PartialEq)] +pub enum TdVmcallError { + // TDCALL[TDG.VP.VMCALL] sub-function invocation must be retried + VmcallRetry, + + // Invalid operand to TDG.VP.VMCALL sub-function + VmcallOperandInvalid, + + // GPA already mapped + VmcallGpaInuse, + + // Operand (address) alignment error + VmcallAlignError, + + Other, +} + +impl From for TdVmcallError { + fn from(val: u64) -> Self { + match val { + 0x1 => TdVmcallError::VmcallRetry, + 0x8000_0000_0000_0000 => TdVmcallError::VmcallOperandInvalid, + 0x8000_0000_0000_0001 => TdVmcallError::VmcallGpaInuse, + 0x8000_0000_0000_0002 => TdVmcallError::VmcallAlignError, + _ => TdVmcallError::Other, + } + } +} + +// A public wrapper for use of asm_td_vmcall, this function takes a mutable reference of a +// TdcallArgs structure to ensure the input is valid +// +// ## TDVMCALL ABI +// Defined in GHCI Spec section 'TDCALL [TDG.VP.VMCALL] leaf' +// +// ### Input Operands: +// * RAX - TDCALL instruction leaf number (0 - TDG.VP.VMCALL) +// * RCX - A bitmap that controls which part of guest TD GPR is exposed to VMM. +// * R10 - Set to 0 indicates leaf-function used in R11 is defined in standard GHCI Spec. +// * R11 - TDG.VP.VMCALL sub-function is R10 is zero +// * RBX, RBP, RDI, RSI, R8-R10, R12-R15 - Used to pass values to VMM in sub-functions. +// +// ### Output Operands: +// * RAX - TDCALL instruction return code, always return Success(0). +// * R10 - TDG.VP.VMCALL sub-function return value +// * R11 - Correspond to each TDG.VP.VMCALL. +// * R8-R9, R12-R15, RBX, RBP, RDI, RSI - Correspond to each TDG.VP.VMCALL sub-function. +// +pub fn td_vmcall(args: &mut TdVmcallArgs) -> u64 { + unsafe { asm::asm_td_vmcall(args as *mut TdVmcallArgs as *mut c_void, 0) } +} + +// An extended public wrapper for use of asm_td_vmcall. +// +// `do_sti` is a flag used to determine whether to execute `sti` instruction before `tdcall` +pub fn td_vmcall_ex(args: &mut TdVmcallArgs, do_sti: bool) -> u64 { + unsafe { asm::asm_td_vmcall(args as *mut TdVmcallArgs as *mut c_void, do_sti as u64) } +} + +/// Used to help perform HLT operation. +/// +/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' +pub fn halt() { + let interrupt_blocked = !rflags::read().contains(RFlags::INTERRUPT_FLAG); + + let mut args = TdVmcallArgs { + r11: TDVMCALL_HALT, + r12: interrupt_blocked as u64, + ..Default::default() + }; + + let _ = td_vmcall(&mut args); +} + +/// Executing `hlt` instruction will cause a #VE to emulate the instruction. Safe halt operation +/// `sti;hlt` which typically used for idle is not working in this case since `hlt` instruction +/// must be the instruction next to `sti`. To use safe halt, `sti` must be executed just before +/// `tdcall` instruction. +pub fn sti_halt() { + let mut args = TdVmcallArgs { + r11: TDVMCALL_HALT, + ..Default::default() + }; + + // Set the `do_sti` flag to execute `sti` before `tdcall` instruction + // Result is always `TDG.VP.VMCALL_SUCCESS` + let _ = td_vmcall_ex(&mut args, true); +} + +const IO_READ: u64 = 0; +const IO_WRITE: u64 = 1; + +/// Request the VMM perform single byte IO read operation +/// +/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' +pub fn io_read_8(port: u16) -> u8 { + let mut args = TdVmcallArgs { + r11: TDVMCALL_IO, + r12: core::mem::size_of::() as u64, + r13: IO_READ, + r14: port as u64, + ..Default::default() + }; + + let ret = td_vmcall(&mut args); + + if ret != TDVMCALL_STATUS_SUCCESS { + halt(); + } + + (args.r11 & 0xff) as u8 +} + +/// Request the VMM perform 2-bytes byte IO read operation +/// +/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' +pub fn io_read_16(port: u16) -> u16 { + let mut args = TdVmcallArgs { + r11: TDVMCALL_IO, + r12: core::mem::size_of::() as u64, + r13: IO_READ, + r14: port as u64, + ..Default::default() + }; + + let ret = td_vmcall(&mut args); + + if ret != TDVMCALL_STATUS_SUCCESS { + halt(); + } + + (args.r11 & 0xffff) as u16 +} + +/// Request the VMM perform 4-bytes byte IO read operation +/// +/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' +pub fn io_read_32(port: u16) -> u32 { + let mut args = TdVmcallArgs { + r11: TDVMCALL_IO, + r12: core::mem::size_of::() as u64, + r13: IO_READ, + r14: port as u64, + ..Default::default() + }; + + let ret = td_vmcall(&mut args); + + if ret != TDVMCALL_STATUS_SUCCESS { + halt(); + } + + (args.r11 & 0xffff_ffff) as u32 +} + +/// Request the VMM perform single byte IO write operation +/// +/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' +pub fn io_write_8(port: u16, byte: u8) { + let mut args = TdVmcallArgs { + r11: TDVMCALL_IO, + r12: core::mem::size_of::() as u64, + r13: IO_WRITE, + r14: port as u64, + r15: byte as u64, + ..Default::default() + }; + + let ret = td_vmcall(&mut args); + + if ret != TDVMCALL_STATUS_SUCCESS { + halt(); + } +} + +/// Request the VMM perform 2-bytes IO write operation +/// +/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' +pub fn io_write_16(port: u16, byte: u16) { + let mut args = TdVmcallArgs { + r11: TDVMCALL_IO, + r12: core::mem::size_of::() as u64, + r13: IO_WRITE, + r14: port as u64, + r15: byte as u64, + ..Default::default() + }; + + let ret = td_vmcall(&mut args); + + if ret != TDVMCALL_STATUS_SUCCESS { + halt(); + } +} + +/// Request the VMM perform 4-bytes IO write operation +/// +/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' +pub fn io_write_32(port: u16, byte: u32) { + let mut args = TdVmcallArgs { + r11: TDVMCALL_IO, + r12: core::mem::size_of::() as u64, + r13: IO_WRITE, + r14: port as u64, + r15: byte as u64, + ..Default::default() + }; + + let ret = td_vmcall(&mut args); + + if ret != TDVMCALL_STATUS_SUCCESS { + halt(); + } +} + +/// Used to help request the VMM perform emulated-MMIO-write operation. +/// +/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL<#VE.RequestMMIO>' +pub fn mmio_write(address: *const T, value: T) { + let address = address as u64 | *SHARED_MASK; + fence(Ordering::SeqCst); + let val = unsafe { *(core::ptr::addr_of!(value) as *const u64) }; + + let mut args = TdVmcallArgs { + r11: TDVMCALL_MMIO, + r12: core::mem::size_of::() as u64, + r13: IO_WRITE, + r14: address, + r15: val, + ..Default::default() + }; + + let ret = td_vmcall(&mut args); + + if ret != TDVMCALL_STATUS_SUCCESS { + halt(); + } +} + +/// Used to help request the VMM perform emulated-MMIO-read operation. +/// +/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL<#VE.RequestMMIO>' +pub fn mmio_read(address: usize) -> T { + let address = address as u64 | *SHARED_MASK; + fence(Ordering::SeqCst); + + let mut args = TdVmcallArgs { + r11: TDVMCALL_MMIO, + r12: core::mem::size_of::() as u64, + r13: IO_READ, + r14: address, + ..Default::default() + }; + + let ret = td_vmcall(&mut args); + + if ret != TDVMCALL_STATUS_SUCCESS { + halt(); + } + + unsafe { *(core::ptr::addr_of!(args.r11) as *const T) } +} + +/// Used to request the host VMM to map a GPA range as a private or shared memory mappings. +/// It can be used to convert page mappings from private to shared or vice versa +/// +/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' +pub fn mapgpa(shared: bool, paddr: u64, length: usize) -> Result<(), TdVmcallError> { + let share_bit = *SHARED_MASK; + let mut map_start = if shared { + paddr | share_bit + } else { + paddr & (!share_bit) + }; + let map_end = map_start + .checked_add(length as u64) + .ok_or(TdVmcallError::Other)?; + + const MAX_RETRIES_PER_PAGE: usize = 3; + let mut retry_counter = 0; + + while retry_counter < MAX_RETRIES_PER_PAGE { + log::trace!( + "tdvmcall mapgpa - start: {:x}, length: {:x}\n", + map_start, + map_end - map_start, + ); + let mut args = TdVmcallArgs { + r11: TDVMCALL_MAPGPA, + r12: map_start, + r13: map_end - map_start, + ..Default::default() + }; + + let ret = td_vmcall(&mut args); + if ret == TDVMCALL_STATUS_SUCCESS { + return Ok(()); + } else if ret != TDVMCALL_STATUS_RETRY { + return Err(ret.into()); + } + + let retry_addr = args.r11; + if retry_addr < map_start || retry_addr >= map_end { + return Err(TdVmcallError::Other); + } + + // Increase the retry count for the current page + if retry_addr == map_start { + retry_counter += 1; + continue; + } + + // Failed in a new address, update the `map_start` and reset counter + map_start = retry_addr; + retry_counter = 0; + } + + Err(TdVmcallError::Other) +} + +/// Used to help perform RDMSR operation. +/// +/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' +pub fn rdmsr(index: u32) -> Result { + let mut args = TdVmcallArgs { + r11: TDVMCALL_RDMSR, + r12: index as u64, + ..Default::default() + }; + + let ret = td_vmcall(&mut args); + + if ret != TDVMCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + Ok(args.r11) +} + +/// Used to help perform WRMSR operation. +/// +/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' +pub fn wrmsr(index: u32, value: u64) -> Result<(), TdVmcallError> { + let mut args = TdVmcallArgs { + r11: TDVMCALL_WRMSR, + r12: index as u64, + r13: value, + ..Default::default() + }; + + let ret = td_vmcall(&mut args); + + if ret != TDVMCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + Ok(()) +} + +/// Used to enable the TD-guest to request the VMM to emulate the CPUID operation +/// +/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' +pub fn cpuid(eax: u32, ecx: u32) -> CpuIdInfo { + let mut args = TdVmcallArgs { + r11: TDVMCALL_CPUID, + r12: eax as u64, + r13: ecx as u64, + ..Default::default() + }; + + let ret = td_vmcall(&mut args); + + if ret != TDVMCALL_STATUS_SUCCESS { + halt(); + } + + CpuIdInfo { + eax: (args.r12 & 0xffff_ffff) as u32, + ebx: (args.r13 & 0xffff_ffff) as u32, + ecx: (args.r14 & 0xffff_ffff) as u32, + edx: (args.r15 & 0xffff_ffff) as u32, + } +} + +/// Used to request the host VMM specify which interrupt vector to use as an event-notify +/// vector. +/// +/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' +pub fn setup_event_notify(vector: u64) -> Result<(), TdVmcallError> { + let mut args = TdVmcallArgs { + r11: TDVMCALL_SETUPEVENTNOTIFY, + r12: vector, + ..Default::default() + }; + + let ret = td_vmcall(&mut args); + + if ret != TDVMCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + Ok(()) +} + +/// Used to invoke a request to generate a TD-Quote signing by a TD-Quoting Enclave +/// operating in the host environment. +/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' +/// +/// * buffer: a piece of 4KB-aligned shared memory +pub fn get_quote(buffer: &mut [u8]) -> Result<(), TdVmcallError> { + let addr = buffer.as_mut_ptr() as u64 | *SHARED_MASK; + + let mut args = TdVmcallArgs { + r11: TDVMCALL_GETQUOTE, + r12: addr, + r13: buffer.len() as u64, + ..Default::default() + }; + + let ret = td_vmcall(&mut args); + + if ret != TDVMCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + Ok(()) +} + +/// TDG.VP.VMCALL defines an interface for the command/response that +/// may have long latency. +/// Details can be found in TDX GHCI v1.5 spec section 'TDG.VP.VMCALL' +/// +/// * command: a piece of 4KB-aligned shared memory as input +/// * response: a piece of 4KB-aligned shared memory as ouput +/// * interrupt: event notification interrupt vector, valid values [32-255] +/// * wait_time: Maximum wait time for the command and response +pub fn service( + command: &[u8], + response: &mut [u8], + interrupt: u64, + wait_time: u64, +) -> Result<(), TdVmcallError> { + let command = command.as_ptr() as u64 | *SHARED_MASK; + let response = response.as_mut_ptr() as u64 | *SHARED_MASK; + + // Ensure the address is aligned to 4K bytes + if (command & 0xfff) != 0 || (response & 0xfff) != 0 { + return Err(TdVmcallError::VmcallAlignError); + } + + // Ensure that the interrupt vector is in a valid range + if (1..32).contains(&interrupt) { + return Err(TdVmcallError::VmcallOperandInvalid); + } + + let mut args = TdVmcallArgs { + r11: TDVMCALL_SERVICE, + r12: command, + r13: response, + r14: interrupt, + r15: wait_time, + ..Default::default() + }; + + let ret = td_vmcall(&mut args); + + if ret != TDVMCALL_STATUS_SUCCESS { + return Err(ret.into()); + } + + Ok(()) +} diff --git a/tdx-tdcall/src/tdx.rs b/tdx-tdcall/src/tdx.rs deleted file mode 100644 index 522d62a7..00000000 --- a/tdx-tdcall/src/tdx.rs +++ /dev/null @@ -1,971 +0,0 @@ -// Copyright (c) 2020-2022 Intel Corporation -// -// SPDX-License-Identifier: BSD-2-Clause-Patent - -//! Implemention of a subset of TDCALL functions defined in Intel TDX Module v1.0 and v1.5 -//! Spec and TDVMCALL sub-functions defined in TDX GHCI Spec. -//! -//! The TDCALL instruction causes a VM exit to the Intel TDX Module. It is used to call -//! guest-side Intel TDX functions, either local or a TD exit to the host VMM. -//! -//! TDVMCALL (TDG.VP.VMCALL) is a leaf function 0 for TDCALL. It helps invoke services from -//! the host VMM. - -use core::result::Result; -use core::sync::atomic::{fence, Ordering}; -use lazy_static::lazy_static; -use x86_64::registers::rflags::{self, RFlags}; - -use crate::*; - -const IO_READ: u64 = 0; -const IO_WRITE: u64 = 1; -const TARGET_TD_UUID_NUM: usize = 4; -pub const PAGE_SIZE_4K: u64 = 0x1000; -pub const PAGE_SIZE_2M: u64 = 0x200000; - -/// SHA384 digest value extended to RTMR -/// Both alignment and size are 64 bytes. -#[repr(C, align(64))] -pub struct TdxDigest { - pub data: [u8; 48], -} - -/// Guest TD execution evironment returned from TDG.VP.INFO leaf -#[repr(C)] -#[derive(Debug, Default)] -pub struct TdInfo { - pub gpaw: u64, - pub attributes: u64, - pub max_vcpus: u32, - pub num_vcpus: u32, - pub vcpu_index: u32, - pub rsvd: [u32; 5], -} - -/// Virtualization exception information returned from TDG.VP.VEINFO.GET leaf -#[repr(C)] -#[derive(Debug, Default)] -pub struct TdVeInfo { - pub exit_reason: u32, - pub rsvd: u32, - pub exit_qualification: u64, - pub guest_la: u64, - pub guest_pa: u64, - pub exit_instruction_length: u32, - pub exit_instruction_info: u32, - pub rsvd1: u64, -} - -/// Emulated CPUID values returned from TDG.VP.VMCALL -#[repr(C)] -#[derive(Debug, Default)] -pub struct CpuIdInfo { - pub eax: u32, - pub ebx: u32, - pub ecx: u32, - pub edx: u32, -} - -#[derive(Debug, Default)] -#[repr(C)] -pub struct ServtdRWResult { - pub content: u64, - pub uuid: [u64; 4], -} - -lazy_static! { - static ref SHARED_MASK: u64 = td_shared_mask().expect("Fail to get the shared mask of TD"); -} - -/// Used to help perform HLT operation. -/// -/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' -pub fn tdvmcall_halt() { - let interrupt_blocked = !rflags::read().contains(RFlags::INTERRUPT_FLAG); - - let mut args = TdVmcallArgs { - r11: TDVMCALL_HALT, - r12: interrupt_blocked as u64, - ..Default::default() - }; - - let _ = td_vmcall(&mut args); -} - -/// Executing `hlt` instruction will cause a #VE to emulate the instruction. Safe halt operation -/// `sti;hlt` which typically used for idle is not working in this case since `hlt` instruction -/// must be the instruction next to `sti`. To use safe halt, `sti` must be executed just before -/// `tdcall` instruction. -pub fn tdvmcall_sti_halt() { - let mut args = TdVmcallArgs { - r11: TDVMCALL_HALT, - ..Default::default() - }; - - // Set the `do_sti` flag to execute `sti` before `tdcall` instruction - // Result is always `TDG.VP.VMCALL_SUCCESS` - let _ = td_vmcall_ex(&mut args, true); -} - -/// Request the VMM perform single byte IO read operation -/// -/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' -pub fn tdvmcall_io_read_8(port: u16) -> u8 { - let mut args = TdVmcallArgs { - r11: TDVMCALL_IO, - r12: core::mem::size_of::() as u64, - r13: IO_READ, - r14: port as u64, - ..Default::default() - }; - - let ret = td_vmcall(&mut args); - - if ret != TDVMCALL_STATUS_SUCCESS { - tdvmcall_halt(); - } - - (args.r11 & 0xff) as u8 -} - -/// Request the VMM perform 2-bytes byte IO read operation -/// -/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' -pub fn tdvmcall_io_read_16(port: u16) -> u16 { - let mut args = TdVmcallArgs { - r11: TDVMCALL_IO, - r12: core::mem::size_of::() as u64, - r13: IO_READ, - r14: port as u64, - ..Default::default() - }; - - let ret = td_vmcall(&mut args); - - if ret != TDVMCALL_STATUS_SUCCESS { - tdvmcall_halt(); - } - - (args.r11 & 0xffff) as u16 -} - -/// Request the VMM perform 4-bytes byte IO read operation -/// -/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' -pub fn tdvmcall_io_read_32(port: u16) -> u32 { - let mut args = TdVmcallArgs { - r11: TDVMCALL_IO, - r12: core::mem::size_of::() as u64, - r13: IO_READ, - r14: port as u64, - ..Default::default() - }; - - let ret = td_vmcall(&mut args); - - if ret != TDVMCALL_STATUS_SUCCESS { - tdvmcall_halt(); - } - - (args.r11 & 0xffff_ffff) as u32 -} - -/// Request the VMM perform single byte IO write operation -/// -/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' -pub fn tdvmcall_io_write_8(port: u16, byte: u8) { - let mut args = TdVmcallArgs { - r11: TDVMCALL_IO, - r12: core::mem::size_of::() as u64, - r13: IO_WRITE, - r14: port as u64, - r15: byte as u64, - ..Default::default() - }; - - let ret = td_vmcall(&mut args); - - if ret != TDVMCALL_STATUS_SUCCESS { - tdvmcall_halt(); - } -} - -/// Request the VMM perform 2-bytes IO write operation -/// -/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' -pub fn tdvmcall_io_write_16(port: u16, byte: u16) { - let mut args = TdVmcallArgs { - r11: TDVMCALL_IO, - r12: core::mem::size_of::() as u64, - r13: IO_WRITE, - r14: port as u64, - r15: byte as u64, - ..Default::default() - }; - - let ret = td_vmcall(&mut args); - - if ret != TDVMCALL_STATUS_SUCCESS { - tdvmcall_halt(); - } -} - -/// Request the VMM perform 4-bytes IO write operation -/// -/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' -pub fn tdvmcall_io_write_32(port: u16, byte: u32) { - let mut args = TdVmcallArgs { - r11: TDVMCALL_IO, - r12: core::mem::size_of::() as u64, - r13: IO_WRITE, - r14: port as u64, - r15: byte as u64, - ..Default::default() - }; - - let ret = td_vmcall(&mut args); - - if ret != TDVMCALL_STATUS_SUCCESS { - tdvmcall_halt(); - } -} - -/// Used to help request the VMM perform emulated-MMIO-write operation. -/// -/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL<#VE.RequestMMIO>' -pub fn tdvmcall_mmio_write(address: *const T, value: T) { - let address = address as u64 | *SHARED_MASK; - fence(Ordering::SeqCst); - let val = unsafe { *(core::ptr::addr_of!(value) as *const u64) }; - - let mut args = TdVmcallArgs { - r11: TDVMCALL_MMIO, - r12: core::mem::size_of::() as u64, - r13: IO_WRITE, - r14: address, - r15: val, - ..Default::default() - }; - - let ret = td_vmcall(&mut args); - - if ret != TDVMCALL_STATUS_SUCCESS { - tdvmcall_halt(); - } -} - -/// Used to help request the VMM perform emulated-MMIO-read operation. -/// -/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL<#VE.RequestMMIO>' -pub fn tdvmcall_mmio_read(address: usize) -> T { - let address = address as u64 | *SHARED_MASK; - fence(Ordering::SeqCst); - - let mut args = TdVmcallArgs { - r11: TDVMCALL_MMIO, - r12: core::mem::size_of::() as u64, - r13: IO_READ, - r14: address, - ..Default::default() - }; - - let ret = td_vmcall(&mut args); - - if ret != TDVMCALL_STATUS_SUCCESS { - tdvmcall_halt(); - } - - unsafe { *(core::ptr::addr_of!(args.r11) as *const T) } -} - -/// Used to request the host VMM to map a GPA range as a private or shared memory mappings. -/// It can be used to convert page mappings from private to shared or vice versa -/// -/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' -pub fn tdvmcall_mapgpa(shared: bool, paddr: u64, length: usize) -> Result<(), TdVmcallError> { - let share_bit = *SHARED_MASK; - let mut map_start = if shared { - paddr | share_bit - } else { - paddr & (!share_bit) - }; - let map_end = map_start - .checked_add(length as u64) - .ok_or(TdVmcallError::Other)?; - - const MAX_RETRIES_PER_PAGE: usize = 3; - let mut retry_counter = 0; - - while retry_counter < MAX_RETRIES_PER_PAGE { - log::trace!( - "tdvmcall mapgpa - start: {:x}, length: {:x}\n", - map_start, - map_end - map_start, - ); - let mut args = TdVmcallArgs { - r11: TDVMCALL_MAPGPA, - r12: map_start, - r13: map_end - map_start, - ..Default::default() - }; - - let ret = td_vmcall(&mut args); - if ret == TDVMCALL_STATUS_SUCCESS { - return Ok(()); - } else if ret != TDVMCALL_STATUS_RETRY { - return Err(ret.into()); - } - - let retry_addr = args.r11; - if retry_addr < map_start || retry_addr >= map_end { - return Err(TdVmcallError::Other); - } - - // Increase the retry count for the current page - if retry_addr == map_start { - retry_counter += 1; - continue; - } - - // Failed in a new address, update the `map_start` and reset counter - map_start = retry_addr; - retry_counter = 0; - } - - Err(TdVmcallError::Other) -} - -/// Used to help perform RDMSR operation. -/// -/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' -pub fn tdvmcall_rdmsr(index: u32) -> Result { - let mut args = TdVmcallArgs { - r11: TDVMCALL_RDMSR, - r12: index as u64, - ..Default::default() - }; - - let ret = td_vmcall(&mut args); - - if ret != TDVMCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - Ok(args.r11) -} - -/// Used to help perform WRMSR operation. -/// -/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' -pub fn tdvmcall_wrmsr(index: u32, value: u64) -> Result<(), TdVmcallError> { - let mut args = TdVmcallArgs { - r11: TDVMCALL_WRMSR, - r12: index as u64, - r13: value, - ..Default::default() - }; - - let ret = td_vmcall(&mut args); - - if ret != TDVMCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - Ok(()) -} - -/// Used to enable the TD-guest to request the VMM to emulate the CPUID operation -/// -/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' -pub fn tdvmcall_cpuid(eax: u32, ecx: u32) -> CpuIdInfo { - let mut args = TdVmcallArgs { - r11: TDVMCALL_CPUID, - r12: eax as u64, - r13: ecx as u64, - ..Default::default() - }; - - let ret = td_vmcall(&mut args); - - if ret != TDVMCALL_STATUS_SUCCESS { - tdvmcall_halt(); - } - - CpuIdInfo { - eax: (args.r12 & 0xffff_ffff) as u32, - ebx: (args.r13 & 0xffff_ffff) as u32, - ecx: (args.r14 & 0xffff_ffff) as u32, - edx: (args.r15 & 0xffff_ffff) as u32, - } -} - -/// Used to request the host VMM specify which interrupt vector to use as an event-notify -/// vector. -/// -/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' -pub fn tdvmcall_setup_event_notify(vector: u64) -> Result<(), TdVmcallError> { - let mut args = TdVmcallArgs { - r11: TDVMCALL_SETUPEVENTNOTIFY, - r12: vector, - ..Default::default() - }; - - let ret = td_vmcall(&mut args); - - if ret != TDVMCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - Ok(()) -} - -/// Used to invoke a request to generate a TD-Quote signing by a TD-Quoting Enclave -/// operating in the host environment. -/// Details can be found in TDX GHCI spec section 'TDG.VP.VMCALL' -/// -/// * buffer: a piece of 4KB-aligned shared memory -pub fn tdvmcall_get_quote(buffer: &mut [u8]) -> Result<(), TdVmcallError> { - let addr = buffer.as_mut_ptr() as u64 | *SHARED_MASK; - - let mut args = TdVmcallArgs { - r11: TDVMCALL_GETQUOTE, - r12: addr, - r13: buffer.len() as u64, - ..Default::default() - }; - - let ret = td_vmcall(&mut args); - - if ret != TDVMCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - Ok(()) -} - -/// TDG.VP.VMCALL defines an interface for the command/response that -/// may have long latency. -/// Details can be found in TDX GHCI v1.5 spec section 'TDG.VP.VMCALL' -/// -/// * command: a piece of 4KB-aligned shared memory as input -/// * response: a piece of 4KB-aligned shared memory as ouput -/// * interrupt: event notification interrupt vector, valid values [32-255] -/// * wait_time: Maximum wait time for the command and response -pub fn tdvmcall_service( - command: &[u8], - response: &mut [u8], - interrupt: u64, - wait_time: u64, -) -> Result<(), TdVmcallError> { - let command = command.as_ptr() as u64 | *SHARED_MASK; - let response = response.as_mut_ptr() as u64 | *SHARED_MASK; - - // Ensure the address is aligned to 4K bytes - if (command & 0xfff) != 0 || (response & 0xfff) != 0 { - return Err(TdVmcallError::VmcallAlignError); - } - - // Ensure that the interrupt vector is in a valid range - if (1..32).contains(&interrupt) { - return Err(TdVmcallError::VmcallOperandInvalid); - } - - let mut args = TdVmcallArgs { - r11: TDVMCALL_SERVICE, - r12: command, - r13: response, - r14: interrupt, - r15: wait_time, - ..Default::default() - }; - - let ret = td_vmcall(&mut args); - - if ret != TDVMCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - Ok(()) -} - -/// Get guest TD execution environment information -/// -/// Details can be found in TDX Module ABI spec section 'TDG.VP.INFO Leaf' -pub fn tdcall_get_td_info() -> Result { - let mut args = TdcallArgs { - rax: TDCALL_TDINFO, - ..Default::default() - }; - - let ret = td_call(&mut args); - - if ret != TDCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - let td_info = TdInfo { - gpaw: args.rcx & 0x3f, - attributes: args.rdx, - max_vcpus: (args.r8 >> 32) as u32, - num_vcpus: args.r8 as u32, - vcpu_index: args.r9 as u32, - ..Default::default() - }; - - Ok(td_info) -} - -/// Extend a TDCS.RTMR measurement register -/// -/// Details can be found in TDX Module ABI spec section 'TDG.VP.INFO Leaf' -pub fn tdcall_extend_rtmr(digest: &TdxDigest, mr_index: u32) -> Result<(), TdCallError> { - let buffer: u64 = core::ptr::addr_of!(digest.data) as u64; - - let mut args = TdcallArgs { - rax: TDCALL_TDEXTENDRTMR, - rcx: buffer, - rdx: mr_index as u64, - ..Default::default() - }; - - let ret = td_call(&mut args); - - if ret != TDCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - Ok(()) -} - -/// Get virtualization exception information for the recent #VE -/// -/// Details can be found in TDX Module ABI spec section 'TDG.VP.VEINFO.GET Leaf' -pub fn tdcall_get_ve_info() -> Result { - let mut args = TdcallArgs { - rax: TDCALL_TDGETVEINFO, - ..Default::default() - }; - - let ret = td_call(&mut args); - - if ret != TDCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - let ve_info = TdVeInfo { - exit_reason: args.rcx as u32, - exit_qualification: args.rdx, - guest_la: args.r8, - guest_pa: args.r9, - exit_instruction_length: args.r10 as u32, - exit_instruction_info: (args.r10 >> 32) as u32, - ..Default::default() - }; - - Ok(ve_info) -} - -/// Accept a pending private page, and initialize the page to zeros using the TD ephemeral -/// private key -/// -/// Details can be found in TDX Module ABI spec section 'TDG.MEM.PAGE.Accept Leaf' -pub fn tdcall_accept_page(address: u64) -> Result<(), TdCallError> { - let mut args = TdcallArgs { - rax: TDCALL_TDACCEPTPAGE, - rcx: address, - ..Default::default() - }; - - const MAX_RETRIES_ACCEPT_PAGE: usize = 5; - let mut retry_counter = 0; - let mut ret = 0; - - while retry_counter < MAX_RETRIES_ACCEPT_PAGE { - ret = td_call(&mut args); - - if ret == TDCALL_STATUS_SUCCESS { - return Ok(()); - } else { - match TdCallError::from(ret) { - TdCallError::TdxExitReasonOperandBusy(_) => retry_counter += 1, - e => return Err(e), - } - } - } - - return Err(ret.into()); -} - -/// Accept a range of private pages and initialize the pages to zeros using the TD ephemeral -/// private key. -/// -/// This function is a wrapper to `tdcall_accept_page()`. -pub fn td_accept_pages(address: u64, pages: u64, page_size: u64) { - for i in 0..pages { - let accept_addr = address + i * page_size; - let accept_level = if page_size == PAGE_SIZE_2M { 1 } else { 0 }; - match tdcall_accept_page(accept_addr | accept_level) { - Ok(()) => {} - Err(e) => { - if let TdCallError::LeafSpecific(error_code) = e { - if error_code == TDCALL_STATUS_PAGE_SIZE_MISMATCH { - if page_size == PAGE_SIZE_2M { - td_accept_pages(accept_addr, 512, PAGE_SIZE_4K); - continue; - } - } else if error_code == TDCALL_STATUS_PAGE_ALREADY_ACCEPTED { - continue; - } - } - panic!( - "Accept Page Error: 0x{:x}, page_size: {}, err {:x?}\n", - accept_addr, page_size, e - ); - } - } - } -} - -/// Accept a range of either 4K normal pages or 2M huge pages. This is basically a wrapper over -/// td_accept_pages and initializes the pages to zero using the TD ephemeral private key. -pub fn td_accept_memory(address: u64, len: u64) { - let mut start = address; - let end = address + len; - - while start < end { - let remaining = end - start; - - // Try larger accepts first to keep 1G/2M Secure EPT entries - // where possible and speeds up process by cutting number of - // tdcalls (if successful). - if remaining >= PAGE_SIZE_2M && (start & (PAGE_SIZE_2M - 1)) == 0 { - let npages = remaining >> 21; - td_accept_pages(start, npages, PAGE_SIZE_2M); - start += npages << 21; - } else if remaining >= PAGE_SIZE_4K && (start & (PAGE_SIZE_4K - 1)) == 0 { - let mut npages = remaining >> 12; - // Try to consume in 4K chunks until 2M aligned. - if remaining >= PAGE_SIZE_2M { - npages = (PAGE_SIZE_2M - (start & (PAGE_SIZE_2M - 1))) >> 12; - } - td_accept_pages(start, npages, PAGE_SIZE_4K); - start += npages << 12; - } else { - panic!("Accept Memory Error: 0x{:x}, length: {}\n", address, len); - } - } -} - -/// Get the guest physical address (GPA) width via TDG.VP.INFO -/// The GPA width can be used to determine the shared-bit of GPA -pub fn td_shared_mask() -> Option { - let td_info = tdcall_get_td_info().ok()?; - let gpaw = (td_info.gpaw & 0x3f) as u8; - - // Detail can be found in TDX Module v1.5 ABI spec section 'TDVPS(excluding TD VMCS)'. - if gpaw == 48 || gpaw == 52 { - Some(1u64 << (gpaw - 1)) - } else { - None - } -} - -/// Used by a service TD to read a metadata field (control structure field) of -/// a target TD. -/// -/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.SERVTD.RD Leaf'. -pub fn tdcall_servtd_rd( - binding_handle: u64, - field_identifier: u64, - target_td_uuid: &[u64], -) -> Result { - if target_td_uuid.len() != TARGET_TD_UUID_NUM { - return Err(TdCallError::TdxExitInvalidParameters); - } - - let mut args = TdcallArgs { - rax: TDCALL_SERVTD_RD, - rcx: binding_handle, - rdx: field_identifier, - r10: target_td_uuid[0], - r11: target_td_uuid[1], - r12: target_td_uuid[2], - r13: target_td_uuid[3], - ..Default::default() - }; - - let ret = td_call(&mut args); - - if ret != TDCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - let sertd_rw_result = ServtdRWResult { - content: args.r8, - uuid: [args.r10, args.r11, args.r12, args.r13], - }; - - Ok(sertd_rw_result) -} - -/// Used by a service TD to write a metadata field (control structure field) of -/// a target TD. -/// -/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.SERVTD.RD Leaf'. -pub fn tdcall_servtd_wr( - binding_handle: u64, - field_identifier: u64, - data: u64, - target_td_uuid: &[u64], -) -> Result { - if target_td_uuid.len() != TARGET_TD_UUID_NUM { - return Err(TdCallError::TdxExitInvalidParameters); - } - - let mut args = TdcallArgs { - rax: TDCALL_SERVTD_WR, - rcx: binding_handle, - rdx: field_identifier, - r8: data, - r9: u64::MAX, - r10: target_td_uuid[0], - r11: target_td_uuid[1], - r12: target_td_uuid[2], - r13: target_td_uuid[3], - }; - - let ret = td_call(&mut args); - - if ret != TDCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - let result = ServtdRWResult { - content: args.r8, - uuid: [args.r10, args.r11, args.r12, args.r13], - }; - - Ok(result) -} - -/// Used to read a TDX Module global-scope metadata field. -/// -/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.SYS.RD Leaf'. -pub fn tdcall_sys_rd(field_identifier: u64) -> core::result::Result<(u64, u64), TdCallError> { - let mut args = TdcallArgs { - rax: TDCALL_SYS_RD, - rdx: field_identifier, - ..Default::default() - }; - - let ret = td_call(&mut args); - - if ret != TDCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - Ok((args.rdx, args.r8)) -} - -/// Read a VCPU-scope metadata field (control structure field) of a TD. -/// -/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.VP.RD Leaf'. -pub fn tdcall_vp_read(field: u64) -> Result<(u64, u64), TdCallError> { - let mut args = TdcallArgs { - rax: TDCALL_VP_RD, - rdx: field, - ..Default::default() - }; - - let ret = td_call(&mut args); - - if ret != TDCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - Ok((args.rdx, args.r8)) -} - -/// Write a VCPU-scope metadata field (control structure field) of a TD. -/// -/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.VP.WR Leaf'. -pub fn tdcall_vp_write(field: u64, value: u64, mask: u64) -> Result { - let mut args = TdcallArgs { - rax: TDCALL_VP_WR, - rdx: field, - r8: value, - r9: mask, - ..Default::default() - }; - - let ret = td_call(&mut args); - - if ret != TDCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - Ok(args.r8) -} - -/// Invalidate mappings in the translation lookaside buffers (TLBs) and paging-structure caches -/// for a specified L2 VM and a specified list of 4KB page linear addresses. -/// -/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.VP.INVVPID Leaf'. -pub fn tdcall_vp_invvpid(flags: u64, gla: u64) -> Result { - let mut args = TdcallArgs { - rax: TDCALL_VP_INVVPID, - rcx: flags, - rdx: gla, - ..Default::default() - }; - - let ret = td_call(&mut args); - - if ret != TDCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - Ok(args.rdx) -} - -/// Invalidate cached EPT translations for selected L2 VM. -/// -/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.VP.INVEPT Leaf'. -pub fn tdcall_vp_invept(vm_flags: u64) -> Result<(), TdCallError> { - let mut args = TdcallArgs { - rax: TDCALL_VP_INVEPT, - rcx: vm_flags, - ..Default::default() - }; - - let ret = td_call(&mut args); - - if ret != TDCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - Ok(()) -} - -/// Enter L2 VCPU operation. -/// -/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.VP.ENTER Leaf'. -pub fn tdcall_vp_enter(vm_flags: u64, gpa: u64) -> TdcallArgs { - let mut args = TdcallArgs { - rax: TDCALL_VP_ENTER, - rcx: vm_flags, - rdx: gpa, - ..Default::default() - }; - - td_call(&mut args); - - args -} - -/// Read a TD-scope metadata field (control structure field) of a TD. -/// -/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.VM.RD Leaf'. -pub fn tdcall_vm_read(field: u64, version: u8) -> Result<(u64, u64), TdCallError> { - let mut args = TdcallArgs { - rax: TDCALL_VM_RD | (version as u64) << 16, - rdx: field, - ..Default::default() - }; - - let ret = td_call(&mut args); - - if ret != TDCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - Ok((args.rdx, args.r8)) -} - -/// Write a TD-scope metadata field (control structure field) of a TD. -/// -/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.VM.WR Leaf'. -pub fn tdcall_vm_write(field: u64, value: u64, mask: u64) -> Result { - let mut args = TdcallArgs { - rax: TDCALL_VM_WR, - rdx: field, - r8: value, - r9: mask, - ..Default::default() - }; - - let ret = td_call(&mut args); - - if ret != TDCALL_STATUS_SUCCESS { - return Err(ret.into()); - } - - Ok(args.r8) -} - -/// Write the attributes of a private page. Create or remove L2 page aliases as required. -/// -/// Details can be found in TDX Module v1.5 ABI spec section 'TDG.MEM.PAGE.ATTR.WR Leaf'. -pub fn tdcall_mem_page_attr_wr( - gpa_mapping: u64, - gpa_attr: u64, - attr_flags: u64, -) -> Result<(u64, u64), TdCallError> { - let mut args = TdcallArgs { - rax: TDCALL_MEM_PAGE_ATTR_WR, - rcx: gpa_mapping, - rdx: gpa_attr, - r8: attr_flags, - ..Default::default() - }; - - const MAX_RETRIES_ATTR_WR: usize = 5; - let mut retry_counter = 0; - let mut ret = 0; - - while retry_counter < MAX_RETRIES_ATTR_WR { - ret = td_call(&mut args); - - if ret == TDCALL_STATUS_SUCCESS { - return Ok((args.rcx, args.rdx)); - } else { - match TdCallError::from(ret) { - TdCallError::TdxExitReasonOperandBusy(_) => retry_counter += 1, - e => return Err(e), - } - } - } - - return Err(ret.into()); -} - -#[cfg(test)] -mod tests { - use super::*; - use core::mem::{align_of, size_of}; - - #[test] - fn test_struct_size_alignment() { - assert_eq!(align_of::(), 64); - assert_eq!(size_of::(), 64); - assert_eq!(size_of::(), 48); - assert_eq!(size_of::(), 48); - } - - #[test] - fn test_tdcall_servtd_rd() { - let uuid: [u64; 3] = [0; 3]; - let ret = tdcall_servtd_rd(0x0, 0x0, &uuid); - - assert!(ret.is_err()); - } - - #[test] - fn test_tdcall_servtd_wr() { - let uuid: [u64; 3] = [0; 3]; - let ret = tdcall_servtd_wr(0x0, 0x0, 0x0, &uuid); - - assert!(ret.is_err()); - } -} diff --git a/tests/test-td-payload/Cargo.toml b/tests/test-td-payload/Cargo.toml index 1fa6cda8..f8ef9495 100644 --- a/tests/test-td-payload/Cargo.toml +++ b/tests/test-td-payload/Cargo.toml @@ -24,7 +24,7 @@ serde_json = { version = "1.0", default-features = false, features = ["alloc"] } x86 = { version = "0.47.0" } ring = { version = "0.17.6", default-features = false, features = ["alloc"] } td-shim = { path = "../../td-shim" } -td-payload = { path = "../../td-payload", features = ["tdx","cet-shstk","stack-guard"] } +td-payload = { path = "../../td-payload", features = ["cet-shstk","stack-guard"] } zerocopy = { version = "0.7.31", features = ["derive"] } minicov = { version = "0.2", default-features = false, optional = true } @@ -37,6 +37,8 @@ features = ["spin_no_std"] map-physical-memory = true [features] -tdx = ["tdx-tdcall", "td-logger/tdx"] +tdvmcall = ["td-payload/tdvmcall", "tdx-tdcall/tdvmcall", "td-logger/tdvmcall"] +tdcall = ["td-payload/tdcall", "tdx-tdcall/tdcall"] +tdx = ["tdcall", "tdvmcall"] main = [] coverage = ["minicov"] diff --git a/tests/test-td-payload/src/testiorw32.rs b/tests/test-td-payload/src/testiorw32.rs index 001db8cc..f6303922 100644 --- a/tests/test-td-payload/src/testiorw32.rs +++ b/tests/test-td-payload/src/testiorw32.rs @@ -8,7 +8,7 @@ extern crate alloc; use crate::lib::{TestCase, TestResult}; use alloc::string::String; use core::ffi::c_void; -use tdx_tdcall::tdx; +use tdx_tdcall::tdvmcall; use serde::{Deserialize, Serialize}; @@ -38,14 +38,14 @@ impl TestCase for Tdiorw32 { * io read Century of RTC */ fn run(&mut self) { - tdx::tdvmcall_io_write_8(0x70, 0x32); + tdvmcall::io_write_8(0x70, 0x32); - let read1 = tdx::tdvmcall_io_read_32(0x71); + let read1 = tdvmcall::io_read_32(0x71); log::info!("First time read {}\n", read1); - tdx::tdvmcall_io_write_32(0x71, read1 + 1); + tdvmcall::io_write_32(0x71, read1 + 1); - let read2 = tdx::tdvmcall_io_read_32(0x71); + let read2 = tdvmcall::io_read_32(0x71); log::info!("Second time read {}\n", read2); if (read1 + 1 != read2) { diff --git a/tests/test-td-payload/src/testiorw8.rs b/tests/test-td-payload/src/testiorw8.rs index 43ac4270..8df1368b 100644 --- a/tests/test-td-payload/src/testiorw8.rs +++ b/tests/test-td-payload/src/testiorw8.rs @@ -8,7 +8,7 @@ extern crate alloc; use crate::lib::{TestCase, TestResult}; use alloc::string::String; use core::ffi::c_void; -use tdx_tdcall::tdx; +use tdx_tdcall::tdvmcall; use serde::{Deserialize, Serialize}; @@ -38,14 +38,14 @@ impl TestCase for Tdiorw8 { * io read Century of RTC */ fn run(&mut self) { - tdx::tdvmcall_io_write_8(0x70, 0x32); + tdvmcall::io_write_8(0x70, 0x32); - let read1 = tdx::tdvmcall_io_read_8(0x71); + let read1 = tdvmcall::io_read_8(0x71); log::info!("First time read {}\n", read1); - tdx::tdvmcall_io_write_8(0x71, read1 + 1); + tdvmcall::io_write_8(0x71, read1 + 1); - let read2 = tdx::tdvmcall_io_read_8(0x71); + let read2 = tdvmcall::io_read_8(0x71); log::info!("Second time read {}\n", read2); if (read1 + 1 != read2) { diff --git a/tests/test-td-payload/src/testmemmap.rs b/tests/test-td-payload/src/testmemmap.rs index f7b10a51..e141a325 100644 --- a/tests/test-td-payload/src/testmemmap.rs +++ b/tests/test-td-payload/src/testmemmap.rs @@ -88,7 +88,7 @@ impl TestMemoryMap { fn verify_memory_map(&self, e820: Vec) -> TestResult { let mut top = 0; let mut total = 0; - let max_gpa = 1 << 51; //tdx::td_shared_page_mask(); + let max_gpa = 1 << 51; // tdcall::td_shared_page_mask(); for entry in e820 { let entry_end = entry.addr + entry.size; if entry.r#type < E820Type::Memory as u32 diff --git a/tests/test-td-payload/src/testmsrrw.rs b/tests/test-td-payload/src/testmsrrw.rs index d89fa60f..ee43896f 100644 --- a/tests/test-td-payload/src/testmsrrw.rs +++ b/tests/test-td-payload/src/testmsrrw.rs @@ -8,7 +8,7 @@ extern crate alloc; use crate::lib::{TestCase, TestResult}; use alloc::string::String; use core::ffi::c_void; -use tdx_tdcall::tdx; +use tdx_tdcall::tdvmcall; use serde::{Deserialize, Serialize}; @@ -27,16 +27,16 @@ impl Tdmsrrw { const APIC_SVR_MSR: u32 = 0x80f; // APIC Spurious Vector Register MSR address // Read the current value of the APIC SVR MSR - match tdx::tdvmcall_rdmsr(APIC_SVR_MSR) { + match tdvmcall::rdmsr(APIC_SVR_MSR) { Ok(read1) => { // Attempt to write the incremented value back to the APIC SVR MSR - if tdx::tdvmcall_wrmsr(APIC_SVR_MSR, read1 + 1).is_err() { + if tdvmcall::wrmsr(APIC_SVR_MSR, read1 + 1).is_err() { log::info!("Failed to write MSR 0x{:x}", APIC_SVR_MSR); return TestResult::Fail; } // Read the value again to verify the write operation - match tdx::tdvmcall_rdmsr(APIC_SVR_MSR) { + match tdvmcall::rdmsr(APIC_SVR_MSR) { Ok(read2) if read1 + 1 == read2 => TestResult::Pass, Ok(read2) => { log::info!( diff --git a/tests/test-td-payload/src/testtdinfo.rs b/tests/test-td-payload/src/testtdinfo.rs index 0d238e93..01c0b134 100644 --- a/tests/test-td-payload/src/testtdinfo.rs +++ b/tests/test-td-payload/src/testtdinfo.rs @@ -8,7 +8,7 @@ extern crate alloc; use crate::lib::{TestCase, TestResult}; use alloc::string::String; use core::ffi::c_void; -use tdx_tdcall::tdx; +use tdx_tdcall::tdcall; use serde::{Deserialize, Serialize}; @@ -57,7 +57,7 @@ impl TestCase for Tdinfo { * run the test case */ fn run(&mut self) { - let mut td_info = tdx::tdcall_get_td_info().expect("Failt to get td info"); + let mut td_info = tdcall::get_td_info().expect("Failt to get td info"); // Only GPAW values 48 and 52 are possible. if (td_info.gpaw != 52) && (td_info.gpaw != 48) { log::info!(