mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-25 02:13:24 +00:00
Add tdx-guest crate
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
f540345bfd
commit
12fc074f56
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -649,6 +649,7 @@ dependencies = [
|
||||
"jinux-framebuffer",
|
||||
"jinux-std",
|
||||
"jinux-time",
|
||||
"tdx-guest",
|
||||
"x86_64",
|
||||
]
|
||||
|
||||
@ -690,6 +691,7 @@ dependencies = [
|
||||
"multiboot2",
|
||||
"pod",
|
||||
"spin 0.9.8",
|
||||
"tdx-guest",
|
||||
"trapframe",
|
||||
"volatile",
|
||||
"x86",
|
||||
@ -792,6 +794,7 @@ dependencies = [
|
||||
"ringbuf",
|
||||
"smoltcp",
|
||||
"spin 0.9.8",
|
||||
"tdx-guest",
|
||||
"time",
|
||||
"typeflags",
|
||||
"typeflags-util",
|
||||
@ -1295,6 +1298,16 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tdx-guest"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"lazy_static",
|
||||
"raw-cpuid",
|
||||
"x86_64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.44"
|
||||
|
@ -11,6 +11,7 @@ path = "kernel/main.rs"
|
||||
jinux-frame = { path = "framework/jinux-frame" }
|
||||
jinux-std = { path = "services/libs/jinux-std" }
|
||||
component = { path = "services/libs/comp-sys/component" }
|
||||
tdx-guest = { path = "framework/libs/tdx-guest", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
x86_64 = "0.14.2"
|
||||
@ -38,3 +39,6 @@ members = [
|
||||
]
|
||||
|
||||
exclude = ["services/libs/comp-sys/controlled", "services/libs/comp-sys/cargo-component"]
|
||||
|
||||
[features]
|
||||
intel_tdx = ["dep:tdx-guest", "jinux-frame/intel_tdx", "jinux-std/intel_tdx"]
|
||||
|
4
Makefile
4
Makefile
@ -41,6 +41,10 @@ build:
|
||||
@make --no-print-directory -C regression
|
||||
@cargo kbuild
|
||||
|
||||
build_td:
|
||||
@make --no-print-directory -C regression
|
||||
@cargo kbuild --features intel_tdx
|
||||
|
||||
tools:
|
||||
@cd services/libs/comp-sys && cargo install --path cargo-component
|
||||
|
||||
|
@ -18,6 +18,7 @@ log = "0.4"
|
||||
lazy_static = { version = "1.0", features = ["spin_no_std"] }
|
||||
trapframe = { git = "https://github.com/sdww0/trapframe-rs", rev = "e886763" }
|
||||
inherit-methods-macro = { git = "https://github.com/jinzhao-dev/inherit-methods-macro", rev = "98f7e3e" }
|
||||
tdx-guest = { path = "../libs/tdx-guest", optional = true }
|
||||
|
||||
[target.x86_64-custom.dependencies]
|
||||
x86_64 = "0.14.2"
|
||||
@ -27,3 +28,4 @@ aml = "0.16.3"
|
||||
multiboot2 = "0.16.0"
|
||||
|
||||
[features]
|
||||
intel_tdx = ["dep:tdx-guest"]
|
||||
|
2
framework/libs/tdx-guest/.gitignore
vendored
Normal file
2
framework/libs/tdx-guest/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
/Cargo.lock
|
13
framework/libs/tdx-guest/Cargo.toml
Normal file
13
framework/libs/tdx-guest/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "tdx-guest"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
x86_64 = "0.14.10"
|
||||
bitflags = "1.3"
|
||||
raw-cpuid = "10"
|
||||
lazy_static = "1.4.0"
|
||||
|
11
framework/libs/tdx-guest/src/asm/mod.rs
Normal file
11
framework/libs/tdx-guest/src/asm/mod.rs
Normal file
@ -0,0 +1,11 @@
|
||||
use crate::{tdcall::TdcallArgs, tdvmcall::TdVmcallArgs};
|
||||
use core::arch::global_asm;
|
||||
|
||||
global_asm!(include_str!("tdcall.asm"));
|
||||
global_asm!(include_str!("tdvmcall.asm"));
|
||||
|
||||
// TODO: Use sysv64
|
||||
extern "win64" {
|
||||
pub(crate) fn asm_td_call(args: *mut TdcallArgs) -> u64;
|
||||
pub(crate) fn asm_td_vmcall(args: *mut TdVmcallArgs) -> u64;
|
||||
}
|
78
framework/libs/tdx-guest/src/asm/tdcall.asm
Normal file
78
framework/libs/tdx-guest/src/asm/tdcall.asm
Normal file
@ -0,0 +1,78 @@
|
||||
.section .text
|
||||
|
||||
# Arguments offsets in TdVmcallArgs struct
|
||||
.equ TDCALL_ARG_RAX, 0x0
|
||||
.equ TDCALL_ARG_RCX, 0x8
|
||||
.equ TDCALL_ARG_RDX, 0x10
|
||||
.equ TDCALL_ARG_R8, 0x18
|
||||
.equ TDCALL_ARG_R9, 0x20
|
||||
.equ TDCALL_ARG_R10, 0x28
|
||||
.equ TDCALL_ARG_R11, 0x30
|
||||
.equ TDCALL_ARG_R12, 0x38
|
||||
.equ TDCALL_ARG_R13, 0x40
|
||||
|
||||
# asm_td_call -> u64 (
|
||||
# args: *mut TdcallArgs, //rcx
|
||||
# )
|
||||
.global asm_td_call
|
||||
asm_td_call:
|
||||
endbr64
|
||||
# Save the registers accroding to MS x64 calling convention
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
push r15
|
||||
push r14
|
||||
push r13
|
||||
push r12
|
||||
push rbx
|
||||
push rsi
|
||||
push rdi
|
||||
|
||||
# Use RDI to save RCX value
|
||||
mov rdi, rcx
|
||||
|
||||
# Test if input pointer is valid
|
||||
test rdi, rdi
|
||||
jz td_call_exit
|
||||
|
||||
# Copy the input operands from memory to registers
|
||||
mov rax, [rdi + TDCALL_ARG_RAX]
|
||||
mov rcx, [rdi + TDCALL_ARG_RCX]
|
||||
mov rdx, [rdi + TDCALL_ARG_RDX]
|
||||
mov r8, [rdi + TDCALL_ARG_R8]
|
||||
mov r9, [rdi + TDCALL_ARG_R9]
|
||||
mov r10, [rdi + TDCALL_ARG_R10]
|
||||
mov r11, [rdi + TDCALL_ARG_R11]
|
||||
mov r12, [rdi + TDCALL_ARG_R12]
|
||||
mov r13, [rdi + TDCALL_ARG_R13]
|
||||
|
||||
# tdcall
|
||||
.byte 0x66,0x0f,0x01,0xcc
|
||||
|
||||
# Exit if tdcall reports failure.
|
||||
test rax, rax
|
||||
jnz td_call_exit
|
||||
|
||||
# Copy the output operands from registers to the struct
|
||||
mov [rdi + TDCALL_ARG_RAX], rax
|
||||
mov [rdi + TDCALL_ARG_RCX], rcx
|
||||
mov [rdi + TDCALL_ARG_RDX], rdx
|
||||
mov [rdi + TDCALL_ARG_R8], r8
|
||||
mov [rdi + TDCALL_ARG_R9], r9
|
||||
mov [rdi + TDCALL_ARG_R10], r10
|
||||
mov [rdi + TDCALL_ARG_R11], r11
|
||||
mov [rdi + TDCALL_ARG_R12], r12
|
||||
mov [rdi + TDCALL_ARG_R13], r13
|
||||
|
||||
td_call_exit:
|
||||
# Pop out saved registers from stack
|
||||
pop rdi
|
||||
pop rsi
|
||||
pop rbx
|
||||
pop r12
|
||||
pop r13
|
||||
pop r14
|
||||
pop r15
|
||||
pop rbp
|
||||
|
||||
ret
|
96
framework/libs/tdx-guest/src/asm/tdvmcall.asm
Normal file
96
framework/libs/tdx-guest/src/asm/tdvmcall.asm
Normal file
@ -0,0 +1,96 @@
|
||||
.section .text
|
||||
|
||||
# Mask used to control which part of the guest TD GPR and XMM
|
||||
# state is exposed to the VMM. A bit value of 1 indicates the
|
||||
# corresponding register is passed to VMM. Refer to TDX Module
|
||||
# ABI specification section TDG.VP.VMCALL for detail.
|
||||
# Here we expose R10 - R15 to VMM in td_vm_call()
|
||||
.equ TDVMCALL_EXPOSE_REGS_MASK, 0xfc00
|
||||
|
||||
# TDG.VP.VMCALL leaf number
|
||||
.equ TDVMCALL, 0
|
||||
|
||||
# Arguments offsets in TdVmcallArgs struct
|
||||
.equ VMCALL_ARG_R10, 0x0
|
||||
.equ VMCALL_ARG_R11, 0x8
|
||||
.equ VMCALL_ARG_R12, 0x10
|
||||
.equ VMCALL_ARG_R13, 0x18
|
||||
.equ VMCALL_ARG_R14, 0x20
|
||||
.equ VMCALL_ARG_R15, 0x28
|
||||
|
||||
# asm_td_vmcall -> u64 (
|
||||
# args: *mut TdVmcallArgs,
|
||||
# )
|
||||
.global asm_td_vmcall
|
||||
asm_td_vmcall:
|
||||
endbr64
|
||||
# Save the registers accroding to MS x64 calling convention
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
push r15
|
||||
push r14
|
||||
push r13
|
||||
push r12
|
||||
push rbx
|
||||
push rsi
|
||||
push rdi
|
||||
|
||||
# Use RDI to save RCX value
|
||||
mov rdi, rcx
|
||||
|
||||
# Test if input pointer is valid
|
||||
test rdi, rdi
|
||||
jz vmcall_exit
|
||||
|
||||
# Copy the input operands from memory to registers
|
||||
mov r10, [rdi + VMCALL_ARG_R10]
|
||||
mov r11, [rdi + VMCALL_ARG_R11]
|
||||
mov r12, [rdi + VMCALL_ARG_R12]
|
||||
mov r13, [rdi + VMCALL_ARG_R13]
|
||||
mov r14, [rdi + VMCALL_ARG_R14]
|
||||
mov r15, [rdi + VMCALL_ARG_R15]
|
||||
|
||||
# Set TDCALL leaf number
|
||||
mov rax, TDVMCALL
|
||||
|
||||
# Set exposed register mask
|
||||
mov ecx, TDVMCALL_EXPOSE_REGS_MASK
|
||||
|
||||
# TDCALL
|
||||
.byte 0x66,0x0f,0x01,0xcc
|
||||
|
||||
# RAX should always be zero for TDVMCALL, panic if it is not.
|
||||
test rax, rax
|
||||
jnz vmcall_panic
|
||||
|
||||
# Copy the output operands from registers to the struct
|
||||
mov [rdi + VMCALL_ARG_R10], r10
|
||||
mov [rdi + VMCALL_ARG_R11], r11
|
||||
mov [rdi + VMCALL_ARG_R12], r12
|
||||
mov [rdi + VMCALL_ARG_R13], r13
|
||||
mov [rdi + VMCALL_ARG_R14], r14
|
||||
mov [rdi + VMCALL_ARG_R15], r15
|
||||
|
||||
mov rax, r10
|
||||
|
||||
vmcall_exit:
|
||||
# Clean the registers that are exposed to VMM to
|
||||
# protect against speculative attack, others will
|
||||
# be restored to the values saved in stack
|
||||
xor r10, r10
|
||||
xor r11, r11
|
||||
|
||||
# Pop out saved registers from stack
|
||||
pop rdi
|
||||
pop rsi
|
||||
pop rbx
|
||||
pop r12
|
||||
pop r13
|
||||
pop r14
|
||||
pop r15
|
||||
pop rbp
|
||||
|
||||
ret
|
||||
|
||||
vmcall_panic:
|
||||
ud2
|
81
framework/libs/tdx-guest/src/lib.rs
Normal file
81
framework/libs/tdx-guest/src/lib.rs
Normal file
@ -0,0 +1,81 @@
|
||||
#![no_std]
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_variables)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod asm;
|
||||
pub mod tdcall;
|
||||
pub mod tdvmcall;
|
||||
|
||||
pub use self::tdcall::{deadloop, tdg_vp_veinfo_get, TdxVirtualExceptionType};
|
||||
pub use self::tdvmcall::print;
|
||||
|
||||
use alloc::string::{String, ToString};
|
||||
use raw_cpuid::{native_cpuid::cpuid_count, CpuIdResult};
|
||||
use tdcall::{tdg_vp_vmcall, InitError, TdgVeInfo, TdgVpInfo};
|
||||
|
||||
const TDX_CPUID_LEAF_ID: u64 = 0x21;
|
||||
|
||||
pub fn tdx_early_init() -> Result<TdgVpInfo, InitError> {
|
||||
match is_tdx_guest() {
|
||||
Ok(_) => Ok(tdcall::tdg_vp_info()?),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_tdx_guest() -> Result<(), InitError> {
|
||||
let cpuid_leaf = cpuid_count(0, 0).eax as u64;
|
||||
if cpuid_leaf < TDX_CPUID_LEAF_ID {
|
||||
return Err(InitError::TdxCpuLeafIdError);
|
||||
}
|
||||
let cpuid_result: CpuIdResult = cpuid_count(TDX_CPUID_LEAF_ID as u32, 0);
|
||||
if convert_ascii(cpuid_result.ebx) == "Inte"
|
||||
&& convert_ascii(cpuid_result.edx) == "lTDX"
|
||||
&& convert_ascii(cpuid_result.ecx) == " "
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
Err(InitError::TdxVendorIdError)
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_ascii(reg: u32) -> String {
|
||||
let bytes = [
|
||||
(reg & 0xFF) as u8,
|
||||
((reg >> 8) & 0xFF) as u8,
|
||||
((reg >> 16) & 0xFF) as u8,
|
||||
((reg >> 24) & 0xFF) as u8,
|
||||
];
|
||||
String::from_utf8_lossy(&bytes).to_string()
|
||||
}
|
||||
|
||||
pub trait TdxTrapFrame {
|
||||
fn rax(&self) -> usize;
|
||||
fn set_rax(&mut self, rax: usize);
|
||||
fn rbx(&self) -> usize;
|
||||
fn set_rbx(&mut self, rbx: usize);
|
||||
fn rcx(&self) -> usize;
|
||||
fn set_rcx(&mut self, rcx: usize);
|
||||
fn rdx(&self) -> usize;
|
||||
fn set_rdx(&mut self, rdx: usize);
|
||||
fn rsi(&self) -> usize;
|
||||
fn set_rsi(&mut self, rsi: usize);
|
||||
fn rdi(&self) -> usize;
|
||||
fn set_rdi(&mut self, rdi: usize);
|
||||
fn rip(&self) -> usize;
|
||||
fn set_rip(&mut self, rip: usize);
|
||||
}
|
||||
|
||||
pub fn virtual_exception_handler(trapframe: &mut impl TdxTrapFrame, ve_info: &TdgVeInfo) {
|
||||
match ve_info.exit_reason.into() {
|
||||
TdxVirtualExceptionType::Hlt
|
||||
| TdxVirtualExceptionType::Io
|
||||
| TdxVirtualExceptionType::MsrRead
|
||||
| TdxVirtualExceptionType::MsrWrite
|
||||
| TdxVirtualExceptionType::CpuId => tdg_vp_vmcall(trapframe, ve_info),
|
||||
TdxVirtualExceptionType::Other => panic!("Unknown TDX vitrual exception type"),
|
||||
_ => return,
|
||||
};
|
||||
trapframe.set_rip(trapframe.rip() + ve_info.exit_instruction_length as usize);
|
||||
}
|
596
framework/libs/tdx-guest/src/tdcall.rs
Normal file
596
framework/libs/tdx-guest/src/tdcall.rs
Normal file
@ -0,0 +1,596 @@
|
||||
use super::tdvmcall::*;
|
||||
use crate::{asm::asm_td_call, serial_println, TdxTrapFrame};
|
||||
use bitflags::bitflags;
|
||||
use core::fmt;
|
||||
|
||||
const TDCALL_VP_INFO: u64 = 1;
|
||||
const TDCALL_MR_RTMR_EXTEND: u64 = 2;
|
||||
const TDCALL_VP_VEINFO_GET: u64 = 3;
|
||||
const TDCALL_MR_REPORT: u64 = 4;
|
||||
const TDCALL_VP_CPUIDVE_SET: u64 = 5;
|
||||
const TDCALL_MEM_PAGE_ACCEPT: u64 = 6;
|
||||
const TDCALL_VM_RD: u64 = 7;
|
||||
const TDCALL_VM_WR: u64 = 8;
|
||||
const TDCALL_MR_VERIFYREPORT: u64 = 22;
|
||||
const TDCALL_MEM_PAGE_ATTR_RD: u64 = 23;
|
||||
const TDCALL_MEM_PAGE_ATTR_WR: u64 = 24;
|
||||
|
||||
bitflags! {
|
||||
pub struct Attributes: u64 {
|
||||
/// Guest TD runs in off-TD debug mode.
|
||||
/// Its VCPU state and private memory are accessible by the host VMM.
|
||||
const DEBUG = 1;
|
||||
/// TD is migratable (using a Migration TD).
|
||||
const MIGRATABLE = 1 << 29;
|
||||
/// TD is allowed to use Supervisor Protection Keys.
|
||||
const PKS = 1 << 30;
|
||||
/// TD is allowed to use Key Locker. Must be 0.
|
||||
const KL = 1 << 31;
|
||||
/// TD is allowed to use Perfmon and PERF_METRICS capabilities.
|
||||
const PERFMON = 1 << 63;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// Controls whether CPUID executed by the guest TD will cause #VE unconditionally.
|
||||
struct CpuidveFlag: u64 {
|
||||
/// Flags that when CPL is 0, a CPUID executed
|
||||
/// by the guest TD will cause a #VE unconditionally.
|
||||
const SUPERVISOR = 1;
|
||||
/// Flags that when CPL > 0, a CPUID executed
|
||||
/// by the guest TD will cause a #VE unconditionally.
|
||||
const USER = 1 << 1;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// GPA Attributes (Single VM) Definition.
|
||||
pub struct GpaAttr: u16 {
|
||||
/// Read.
|
||||
const R = 1;
|
||||
/// Write.
|
||||
const W = 1 << 1;
|
||||
/// Execute (Supervisor).
|
||||
const XS = 1 << 2;
|
||||
/// Execute (User).
|
||||
const XU = 1 << 3;
|
||||
/// Verify Guest Paging.
|
||||
const VGP = 1 << 4;
|
||||
/// Paging-Write Access.
|
||||
const PWA = 1 << 5;
|
||||
/// Supervisor Shadow Stack.
|
||||
const SSS = 1 << 6;
|
||||
/// Suppress #VE.
|
||||
const SVE = 1 << 7;
|
||||
/// Indicates that the other bits are valid.
|
||||
/// If its value is 0, other fields are reserved and must be 0.
|
||||
const VALID = 1 << 15;
|
||||
}
|
||||
}
|
||||
pub struct PageAttr {
|
||||
/// Actual GPA mapping of the page.
|
||||
gpa_mapping: u64,
|
||||
/// Guest-visible page attributes.
|
||||
gpa_attr: GpaAttrAll,
|
||||
}
|
||||
|
||||
/// GPA Attributes (all VMs) Definition.
|
||||
pub struct GpaAttrAll {
|
||||
/// L1 GPA attributes.
|
||||
l1_attr: GpaAttr,
|
||||
/// GPA attributes for L2 VM #1.
|
||||
vm1_attr: GpaAttr,
|
||||
/// GPA attributes for L2 VM #2.
|
||||
vm2_attr: GpaAttr,
|
||||
/// GPA attributes for L2 VM #3.
|
||||
vm3_attr: GpaAttr,
|
||||
}
|
||||
|
||||
impl From<u64> for GpaAttrAll {
|
||||
fn from(val: u64) -> Self {
|
||||
GpaAttrAll {
|
||||
l1_attr: GpaAttr::from_bits_truncate((val & 0xFFFF) as u16),
|
||||
vm1_attr: GpaAttr::from_bits_truncate(((val >> 16) & 0xFFFF) as u16),
|
||||
vm2_attr: GpaAttr::from_bits_truncate(((val >> 32) & 0xFFFF) as u16),
|
||||
vm3_attr: GpaAttr::from_bits_truncate(((val >> 48) & 0xFFFF) as u16),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GpaAttrAll> for u64 {
|
||||
fn from(s: GpaAttrAll) -> Self {
|
||||
let field1 = s.l1_attr.bits() as u64;
|
||||
let field2 = (s.vm1_attr.bits() as u64) << 16;
|
||||
let field3 = (s.vm2_attr.bits() as u64) << 32;
|
||||
let field4 = (s.vm3_attr.bits() as u64) << 48;
|
||||
field4 | field3 | field2 | field1
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct TdReport {
|
||||
/// REPORTMACSTRUCT for the TDG.MR.REPORT.
|
||||
pub report_mac: ReportMac,
|
||||
/// Additional attestable elements in the TD’s TCB are not reflected in the
|
||||
/// REPORTMACSTRUCT.CPUSVN – includes the Intel TDX module measurements.
|
||||
pub tee_tcb_info: [u8; 239],
|
||||
pub reserved: [u8; 17],
|
||||
/// TD’s attestable properties.
|
||||
pub tdinfo: TdInfo,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct ReportMac {
|
||||
/// Type Header Structure.
|
||||
pub report_type: ReportType,
|
||||
pub cpu_svn: [u8; 16],
|
||||
/// SHA384 of TEE_TCB_INFO for TEEs implemented using Intel TDX.
|
||||
pub tee_tcb_info_hash: [u8; 48],
|
||||
/// SHA384 of TEE_INFO: a TEE-specific info structure (TDINFO_STRUCT or SGXINFO)
|
||||
/// or 0 if no TEE is represented.
|
||||
pub tee_info_hash: [u8; 48],
|
||||
/// A set of data used for communication between the caller and the target.
|
||||
pub report_data: [u8; 64],
|
||||
pub reserved: [u8; 32],
|
||||
/// The MAC over the REPORTMACSTRUCT with model-specific MAC.
|
||||
pub mac: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TeeType {
|
||||
SGX,
|
||||
TDX,
|
||||
}
|
||||
|
||||
/// REPORTTYPE indicates the reported Trusted Execution Environment (TEE) type,
|
||||
/// sub-type and version.
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct ReportType {
|
||||
/// Trusted Execution Environment (TEE) Type. 0x00: SGX, 0x81: TDX.
|
||||
pub tee_type: TeeType,
|
||||
/// TYPE-specific subtype.
|
||||
pub sub_type: u8,
|
||||
/// TYPE-specific version.
|
||||
pub version: u8,
|
||||
pub reserved: u8,
|
||||
}
|
||||
|
||||
/// TDINFO_STRUCT is defined as the TDX-specific TEE_INFO part of TDG.MR.REPORT.
|
||||
/// It contains the measurements and initial configuration of the TD that was
|
||||
/// locked at initialization and a set of measurement registers that are run-time
|
||||
/// extendable. These values are copied from the TDCS by the TDG.MR.REPORT function.
|
||||
/// Refer to the [TDX Module Base Spec] for additional details.
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct TdInfo {
|
||||
/// TD’s ATTRIBUTES.
|
||||
pub attributes: u64,
|
||||
/// TD’s XFAM.
|
||||
pub xfam: u64,
|
||||
/// Measurement of the initial contents of the TD.
|
||||
pub mrtd: [u8; 48],
|
||||
/// Software-defined ID for non-owner-defined configuration of the
|
||||
/// guest TD – e.g., run-time or OS configuration.
|
||||
pub mr_config_id: [u8; 48],
|
||||
/// Software-defined ID for the guest TD’s owner.
|
||||
pub mr_owner: [u8; 48],
|
||||
/// Software-defined ID for owner-defined configuration of the
|
||||
/// guest TD – e.g., specific to the workload rather than the run-time or OS.
|
||||
pub mr_owner_config: [u8; 48],
|
||||
/// Array of NUM_RTMRS (4) run-time extendable measurement registers.
|
||||
pub rtmr0: [u8; 48],
|
||||
pub rtmr1: [u8; 48],
|
||||
pub rtmr2: [u8; 48],
|
||||
pub rtmr3: [u8; 48],
|
||||
/// If is one or more bound or pre-bound service TDs, SERVTD_HASH is the SHA384 hash of the
|
||||
/// TDINFO_STRUCTs of those service TDs bound. Else, SERVTD_HASH is 0.
|
||||
pub servtd_hash: [u8; 48],
|
||||
pub reserved: [u8; 64],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct TdgVeInfo {
|
||||
pub exit_reason: u32,
|
||||
/// the 64-bit value that would have been saved into the VMCS as an exit qualification
|
||||
/// if a legacy VM exit had occurred instead of the virtualization exception.
|
||||
pub exit_qualification: u64,
|
||||
/// the 64-bit value that would have been saved into the VMCS as a guestlinear address
|
||||
/// if a legacy VM exit had occurred instead of the virtualization exception.
|
||||
pub guest_linear_address: u64,
|
||||
/// the 64-bit value that would have been saved into the VMCS as a guestphysical address
|
||||
/// if a legacy VM exit had occurred instead of the virtualization exception.
|
||||
pub guest_physical_address: u64,
|
||||
/// The 32-bit value that would have been saved into the VMCS as VM-exit instruction
|
||||
/// length if a legacy VM exit had occurred instead of the virtualization exception.
|
||||
pub exit_instruction_length: u32,
|
||||
/// The 32-bit value that would have been saved into the VMCS as VM-exit instruction
|
||||
/// information if a legacy VM exit had occurred instead of the virtualization exception.
|
||||
pub exit_instruction_info: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Gpaw {
|
||||
Bit48,
|
||||
Bit52,
|
||||
}
|
||||
|
||||
impl From<u64> for Gpaw {
|
||||
fn from(val: u64) -> Self {
|
||||
match val {
|
||||
48 => Self::Bit48,
|
||||
52 => Self::Bit52,
|
||||
_ => panic!("Invalid gpaw"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Gpaw> for u64 {
|
||||
fn from(s: Gpaw) -> Self {
|
||||
match s {
|
||||
Gpaw::Bit48 => 48,
|
||||
Gpaw::Bit52 => 52,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Gpaw {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Gpaw::Bit48 => write!(f, "48-bit"),
|
||||
Gpaw::Bit52 => write!(f, "52-bit"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TdgVpInfo {
|
||||
/// The effective GPA width (in bits) for this TD (do not confuse with MAXPA).
|
||||
/// SHARED bit is at GPA bit GPAW-1.
|
||||
///
|
||||
/// Only GPAW values 48 and 52 are possible.
|
||||
pub gpaw: Gpaw,
|
||||
/// The TD's ATTRIBUTES (provided as input to TDH.MNG.INIT)
|
||||
pub attributes: Attributes,
|
||||
pub num_vcpus: u32,
|
||||
pub max_vcpus: u32,
|
||||
pub vcpu_index: u32,
|
||||
/// Indicates that the TDG.SYS.RD/RDM/RDCALL function are avaliable.
|
||||
pub sys_rd: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum TdCallError {
|
||||
/// There is no valid #VE information.
|
||||
TdxNoValidVeInfo,
|
||||
/// Operand is invalid.
|
||||
TdxOperandInvalid,
|
||||
/// The operand is busy (e.g., it is locked in Exclusive mode).
|
||||
TdxOperandBusy,
|
||||
/// Page has already been accepted.
|
||||
TdxPageAlreadyAccepted,
|
||||
/// Requested page size does not match the current GPA mapping size.
|
||||
TdxPageSizeMismatch,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl From<u64> for TdCallError {
|
||||
fn from(val: u64) -> Self {
|
||||
match val {
|
||||
0xC000_0704 => Self::TdxNoValidVeInfo,
|
||||
0xC000_0100 => Self::TdxOperandInvalid,
|
||||
0x8000_0200 => Self::TdxOperandBusy,
|
||||
0x0000_0B0A => Self::TdxPageAlreadyAccepted,
|
||||
0xC000_0B0B => Self::TdxPageSizeMismatch,
|
||||
_ => Self::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub(crate) struct TdcallArgs {
|
||||
rax: u64,
|
||||
rcx: u64,
|
||||
rdx: u64,
|
||||
r8: u64,
|
||||
r9: u64,
|
||||
r10: u64,
|
||||
r11: u64,
|
||||
r12: u64,
|
||||
r13: u64,
|
||||
}
|
||||
|
||||
pub enum TdxVirtualExceptionType {
|
||||
Hlt,
|
||||
Io,
|
||||
MsrRead,
|
||||
MsrWrite,
|
||||
CpuId,
|
||||
VmCall,
|
||||
Mwait,
|
||||
Monitor,
|
||||
Wbinvd,
|
||||
Rdpmc,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl From<u32> for TdxVirtualExceptionType {
|
||||
fn from(val: u32) -> Self {
|
||||
match val {
|
||||
10 => Self::CpuId,
|
||||
12 => Self::Hlt,
|
||||
15 => Self::Rdpmc,
|
||||
18 => Self::VmCall,
|
||||
30 => Self::Io,
|
||||
31 => Self::MsrRead,
|
||||
32 => Self::MsrWrite,
|
||||
36 => Self::Mwait,
|
||||
39 => Self::Monitor,
|
||||
54 => Self::Wbinvd,
|
||||
_ => Self::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InitError {
|
||||
TdxVendorIdError,
|
||||
TdxCpuLeafIdError,
|
||||
TdxGetVpInfoError(TdCallError),
|
||||
}
|
||||
|
||||
impl From<TdCallError> for InitError {
|
||||
fn from(error: TdCallError) -> Self {
|
||||
InitError::TdxGetVpInfoError(error)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get guest TD execution environment information.
|
||||
pub(crate) fn tdg_vp_info() -> Result<TdgVpInfo, TdCallError> {
|
||||
let mut args = TdcallArgs {
|
||||
rax: TDCALL_VP_INFO,
|
||||
..Default::default()
|
||||
};
|
||||
match td_call(&mut args) {
|
||||
Ok(()) => {
|
||||
let td_info = TdgVpInfo {
|
||||
gpaw: Gpaw::from(args.rcx),
|
||||
attributes: Attributes::from_bits_truncate(args.rdx),
|
||||
num_vcpus: args.r8 as u32,
|
||||
max_vcpus: (args.r8 >> 32) as u32,
|
||||
vcpu_index: args.r9 as u32,
|
||||
sys_rd: args.r10 as u32,
|
||||
};
|
||||
Ok(td_info)
|
||||
}
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get Virtualization Exception Information for the recent #VE exception.
|
||||
pub fn tdg_vp_veinfo_get() -> Result<TdgVeInfo, TdCallError> {
|
||||
let mut args = TdcallArgs {
|
||||
rax: TDCALL_VP_VEINFO_GET,
|
||||
..Default::default()
|
||||
};
|
||||
match td_call(&mut args) {
|
||||
Ok(()) => {
|
||||
let ve_info = TdgVeInfo {
|
||||
exit_reason: args.rcx as u32,
|
||||
exit_qualification: args.rdx,
|
||||
guest_linear_address: args.r8,
|
||||
guest_physical_address: args.r9,
|
||||
exit_instruction_length: args.r10 as u32,
|
||||
exit_instruction_info: (args.r10 >> 32) as u32,
|
||||
};
|
||||
Ok(ve_info)
|
||||
}
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform a TD Exit to the host VMM.
|
||||
pub(crate) fn tdg_vp_vmcall(trapframe: &mut impl TdxTrapFrame, ve_info: &TdgVeInfo) {
|
||||
match ve_info.exit_reason.into() {
|
||||
TdxVirtualExceptionType::Hlt => {
|
||||
serial_println!("Ready to halt");
|
||||
tdvmcall_hlt();
|
||||
}
|
||||
TdxVirtualExceptionType::Io => {
|
||||
if !tdvmcall_io(trapframe, ve_info) {
|
||||
serial_println!("Handle tdx ioexit errors, ready to halt");
|
||||
tdvmcall_hlt();
|
||||
}
|
||||
}
|
||||
TdxVirtualExceptionType::MsrRead => {
|
||||
let msr = tdvmcall_rdmsr(trapframe.rcx() as u32).unwrap();
|
||||
trapframe.set_rax((msr as u32 & u32::MAX) as usize);
|
||||
trapframe.set_rdx(((msr >> 32) as u32 & u32::MAX) as usize);
|
||||
}
|
||||
TdxVirtualExceptionType::MsrWrite => {
|
||||
let data = trapframe.rax() as u64 | ((trapframe.rdx() as u64) << 32);
|
||||
tdvmcall_wrmsr(trapframe.rcx() as u32, data).unwrap();
|
||||
}
|
||||
TdxVirtualExceptionType::CpuId => {
|
||||
let cpuid = tdvmcall_cpuid(trapframe.rax() as u32, trapframe.rcx() as u32).unwrap();
|
||||
let mask = 0xFFFF_FFFF_0000_0000_usize;
|
||||
trapframe.set_rax((trapframe.rax() & mask) | cpuid.eax);
|
||||
trapframe.set_rbx((trapframe.rbx() & mask) | cpuid.ebx);
|
||||
trapframe.set_rcx((trapframe.rcx() & mask) | cpuid.ecx);
|
||||
trapframe.set_rdx((trapframe.rdx() & mask) | cpuid.edx);
|
||||
}
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn deadloop() {
|
||||
#[allow(clippy::empty_loop)]
|
||||
loop {
|
||||
x86_64::instructions::interrupts::enable();
|
||||
x86_64::instructions::hlt();
|
||||
}
|
||||
}
|
||||
|
||||
fn td_call(args: &mut TdcallArgs) -> Result<(), TdCallError> {
|
||||
let td_call_result = unsafe { asm_td_call(args) };
|
||||
if td_call_result == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(td_call_result.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Extend a TDCS.RTMR measurement register.
|
||||
fn tdg_mr_rtmr_extend() -> Result<(), TdCallError> {
|
||||
let mut args = TdcallArgs {
|
||||
rax: TDCALL_MR_RTMR_EXTEND,
|
||||
..Default::default()
|
||||
};
|
||||
match td_call(&mut args) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
/// TDG.MR.REPORT creates a TDREPORT_STRUCT structure that contains the measurements/configuration
|
||||
/// information of the guest TD that called the function, measurements/configuration information
|
||||
/// of the Intel TDX module and a REPORTMACSTRUCT.
|
||||
fn tdg_mr_report(report_addr: u64, data_addr: u64) -> Result<(), TdCallError> {
|
||||
let mut args = TdcallArgs {
|
||||
rax: TDCALL_MR_REPORT,
|
||||
rcx: report_addr,
|
||||
rdx: data_addr,
|
||||
..Default::default()
|
||||
};
|
||||
match td_call(&mut args) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify a cryptographic REPORTMACSTRUCT that describes the contents of a TD,
|
||||
/// to determine that it was created on the current TEE on the current platform.
|
||||
fn tdg_mr_verifyreport(report_mac_addr: u64) -> Result<(), TdCallError> {
|
||||
let mut args = TdcallArgs {
|
||||
rax: TDCALL_MR_VERIFYREPORT,
|
||||
rcx: report_mac_addr,
|
||||
..Default::default()
|
||||
};
|
||||
match td_call(&mut args) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept a pending private page and initialize it to all-0 using the TD ephemeral private key.
|
||||
fn tdg_mem_page_accept(sept_level: u64, addr: u64) -> Result<(), TdCallError> {
|
||||
let mut args = TdcallArgs {
|
||||
rax: TDCALL_MEM_PAGE_ACCEPT,
|
||||
rcx: sept_level | (addr << 12),
|
||||
..Default::default()
|
||||
};
|
||||
match td_call(&mut args) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read the GPA mapping and attributes of a TD private page.
|
||||
fn tdg_mem_page_attr_rd(phy_addr: u64) -> Result<PageAttr, TdCallError> {
|
||||
let mut args = TdcallArgs {
|
||||
rax: TDCALL_MEM_PAGE_ATTR_RD,
|
||||
rcx: phy_addr,
|
||||
..Default::default()
|
||||
};
|
||||
match td_call(&mut args) {
|
||||
Ok(()) => {
|
||||
let page_attr = PageAttr {
|
||||
gpa_mapping: args.rcx,
|
||||
gpa_attr: GpaAttrAll::from(args.rdx),
|
||||
};
|
||||
Ok(page_attr)
|
||||
}
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
/// Write the attributes of a private page. Create or remove L2 page aliases as required.
|
||||
fn tdg_mem_page_attr_wr(page_attr: PageAttr, attr_flags: u64) -> Result<PageAttr, TdCallError> {
|
||||
let mut args = TdcallArgs {
|
||||
rax: TDCALL_MEM_PAGE_ATTR_WR,
|
||||
rcx: page_attr.gpa_mapping,
|
||||
rdx: u64::from(page_attr.gpa_attr),
|
||||
r8: attr_flags,
|
||||
..Default::default()
|
||||
};
|
||||
match td_call(&mut args) {
|
||||
Ok(()) => {
|
||||
let page_attr = PageAttr {
|
||||
gpa_mapping: args.rcx,
|
||||
gpa_attr: GpaAttrAll::from(args.rdx),
|
||||
};
|
||||
Ok(page_attr)
|
||||
}
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a TD-scope metadata field (control structure field) of a TD.
|
||||
fn tdg_vm_rd(field_identifier: u64) -> Result<u64, TdCallError> {
|
||||
let mut args = TdcallArgs {
|
||||
rax: TDCALL_VM_RD,
|
||||
rdx: field_identifier,
|
||||
..Default::default()
|
||||
};
|
||||
match td_call(&mut args) {
|
||||
Ok(()) => Ok(args.r8),
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a TD-scope metadata field (control structure field) of a TD.
|
||||
///
|
||||
/// - data: data to write to the field
|
||||
///
|
||||
/// - write_mask: a 64b write mask to indicate which bits of the value
|
||||
/// in R8 are to be written to the field.
|
||||
///
|
||||
/// It returns previous contents of the field.
|
||||
fn tdg_vm_wr(field_identifier: u64, data: u64, write_mask: u64) -> Result<u64, TdCallError> {
|
||||
let mut args = TdcallArgs {
|
||||
rax: TDCALL_VM_WR,
|
||||
rdx: field_identifier,
|
||||
r8: data,
|
||||
r9: write_mask,
|
||||
..Default::default()
|
||||
};
|
||||
match td_call(&mut args) {
|
||||
Ok(()) => Ok(args.r8),
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
/// TDG.VP.CPUIDVE.SET controls unconditional #VE on CPUID execution by the guest TD.
|
||||
///
|
||||
/// Note: TDG.VP.CPUIDVE.SET is provided for backward compatibility.
|
||||
///
|
||||
/// The guest TD may control the same settings by writing to the
|
||||
/// VCPU-scope metadata fields CPUID_SUPERVISOR_VE and CPUID_USER_VE using TDG.VP.WR.
|
||||
fn tdg_vp_cpuidve_set(cpuidve_flag: u64) -> Result<(), TdCallError> {
|
||||
let mut args = TdcallArgs {
|
||||
rax: TDCALL_VP_CPUIDVE_SET,
|
||||
rcx: cpuidve_flag,
|
||||
..Default::default()
|
||||
};
|
||||
match td_call(&mut args) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
340
framework/libs/tdx-guest/src/tdvmcall.rs
Normal file
340
framework/libs/tdx-guest/src/tdvmcall.rs
Normal file
@ -0,0 +1,340 @@
|
||||
extern crate alloc;
|
||||
|
||||
use crate::{asm::asm_td_vmcall, tdcall::TdgVeInfo, TdxTrapFrame};
|
||||
use alloc::fmt;
|
||||
use bitflags::bitflags;
|
||||
use core::fmt::Write;
|
||||
use x86_64::{
|
||||
registers::rflags::{self, RFlags},
|
||||
structures::port::PortRead,
|
||||
};
|
||||
|
||||
const TDVMCALL_CPUID: u64 = 0x0000a;
|
||||
const TDVMCALL_HLT: u64 = 0x0000c;
|
||||
const TDVMCALL_IO: u64 = 0x0001e;
|
||||
const TDVMCALL_RDMSR: u64 = 0x0001f;
|
||||
const TDVMCALL_WRMSR: u64 = 0x00020;
|
||||
const TDVMCALL_REQUEST_MMIO: u64 = 0x00030;
|
||||
const TDVMCALL_WBINVD: u64 = 0x00036;
|
||||
const TDVMCALL_PCONFIG: u64 = 0x00041;
|
||||
const TDVMCALL_MAPGPA: u64 = 0x10001;
|
||||
|
||||
const SERIAL_IO_PORT: u16 = 0x3F8;
|
||||
const SERIAL_LINE_STS: u16 = 0x3FD;
|
||||
const IO_READ: u64 = 0;
|
||||
const IO_WRITE: u64 = 1;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum TdVmcallError {
|
||||
/// TDCALL[TDG.VP.VMCALL] sub-function invocation must be retried.
|
||||
TdxRetry,
|
||||
/// Invalid operand to TDG.VP.VMCALL sub-function.
|
||||
TdxInvalidOperand,
|
||||
/// GPA already mapped.
|
||||
TdxGpaInuse,
|
||||
/// Operand (address) aligned error.
|
||||
TdxAlignError,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl From<u64> for TdVmcallError {
|
||||
fn from(val: u64) -> Self {
|
||||
match val {
|
||||
0x1 => Self::TdxRetry,
|
||||
0x8000_0000_0000_0000 => Self::TdxInvalidOperand,
|
||||
0x8000_0000_0000_0001 => Self::TdxGpaInuse,
|
||||
0x8000_0000_0000_0002 => Self::TdxAlignError,
|
||||
_ => Self::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
pub(crate) struct TdVmcallArgs {
|
||||
r10: u64,
|
||||
r11: u64,
|
||||
r12: u64,
|
||||
r13: u64,
|
||||
r14: u64,
|
||||
r15: u64,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct CpuIdInfo {
|
||||
pub eax: usize,
|
||||
pub ebx: usize,
|
||||
pub ecx: usize,
|
||||
pub edx: usize,
|
||||
}
|
||||
|
||||
enum Direction {
|
||||
In,
|
||||
Out,
|
||||
}
|
||||
|
||||
enum Operand {
|
||||
Dx,
|
||||
Immediate,
|
||||
}
|
||||
|
||||
pub(crate) fn tdvmcall_cpuid(eax: u32, ecx: u32) -> Result<CpuIdInfo, TdVmcallError> {
|
||||
let mut args = TdVmcallArgs {
|
||||
r11: TDVMCALL_CPUID,
|
||||
r12: eax as u64,
|
||||
r13: ecx as u64,
|
||||
..Default::default()
|
||||
};
|
||||
match td_vmcall(&mut args) {
|
||||
Ok(()) => Ok(CpuIdInfo {
|
||||
eax: args.r12 as usize,
|
||||
ebx: args.r13 as usize,
|
||||
ecx: args.r14 as usize,
|
||||
edx: args.r15 as usize,
|
||||
}),
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn tdvmcall_hlt() {
|
||||
let interrupt_blocked = !rflags::read().contains(RFlags::INTERRUPT_FLAG);
|
||||
let mut args = TdVmcallArgs {
|
||||
r11: TDVMCALL_HLT,
|
||||
r12: interrupt_blocked as u64,
|
||||
..Default::default()
|
||||
};
|
||||
let _ = td_vmcall(&mut args);
|
||||
}
|
||||
|
||||
pub(crate) fn tdvmcall_rdmsr(index: u32) -> Result<u64, TdVmcallError> {
|
||||
let mut args = TdVmcallArgs {
|
||||
r11: TDVMCALL_RDMSR,
|
||||
r12: index as u64,
|
||||
..Default::default()
|
||||
};
|
||||
match td_vmcall(&mut args) {
|
||||
Ok(()) => Ok(args.r11),
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn tdvmcall_wrmsr(index: u32, value: u64) -> Result<(), TdVmcallError> {
|
||||
let mut args = TdVmcallArgs {
|
||||
r11: TDVMCALL_WRMSR,
|
||||
r12: index as u64,
|
||||
r13: value,
|
||||
..Default::default()
|
||||
};
|
||||
match td_vmcall(&mut args) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to help perform WBINVD operation.
|
||||
pub(crate) fn tdvmcall_wbinvd(wbinvd: u64) -> Result<(), TdVmcallError> {
|
||||
let mut args = TdVmcallArgs {
|
||||
r11: TDVMCALL_WBINVD,
|
||||
r12: wbinvd,
|
||||
..Default::default()
|
||||
};
|
||||
match td_vmcall(&mut args) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn tdvmcall_read_mmio(size: u64, mmio_addr: u64) -> Result<u64, TdVmcallError> {
|
||||
match size {
|
||||
1 | 2 | 4 | 8 => {}
|
||||
_ => return Err(TdVmcallError::TdxInvalidOperand),
|
||||
}
|
||||
let mut args = TdVmcallArgs {
|
||||
r11: TDVMCALL_REQUEST_MMIO,
|
||||
r12: size,
|
||||
r13: 0,
|
||||
r14: mmio_addr,
|
||||
..Default::default()
|
||||
};
|
||||
match td_vmcall(&mut args) {
|
||||
Ok(()) => Ok(args.r11),
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn tdvmcall_write_mmio(
|
||||
size: u64,
|
||||
mmio_addr: u64,
|
||||
data: u64,
|
||||
) -> Result<(), TdVmcallError> {
|
||||
match size {
|
||||
1 | 2 | 4 | 8 => {}
|
||||
_ => {
|
||||
return Err(TdVmcallError::TdxInvalidOperand);
|
||||
}
|
||||
}
|
||||
let mut args = TdVmcallArgs {
|
||||
r11: TDVMCALL_REQUEST_MMIO,
|
||||
r12: size,
|
||||
r13: 1,
|
||||
r14: mmio_addr,
|
||||
r15: data,
|
||||
..Default::default()
|
||||
};
|
||||
match td_vmcall(&mut args) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn tdvmcall_io(trapframe: &mut impl TdxTrapFrame, ve_info: &TdgVeInfo) -> bool {
|
||||
let size = match ve_info.exit_qualification & 0x3 {
|
||||
0 => 1,
|
||||
1 => 2,
|
||||
3 => 4,
|
||||
_ => panic!("Invalid size value"),
|
||||
};
|
||||
let direction = if (ve_info.exit_qualification >> 3) & 0x1 == 0 {
|
||||
Direction::Out
|
||||
} else {
|
||||
Direction::In
|
||||
};
|
||||
let string = (ve_info.exit_qualification >> 4) & 0x1 == 1;
|
||||
let repeat = (ve_info.exit_qualification >> 5) & 0x1 == 1;
|
||||
let operand = if (ve_info.exit_qualification >> 6) & 0x1 == 0 {
|
||||
Operand::Dx
|
||||
} else {
|
||||
Operand::Immediate
|
||||
};
|
||||
let port = (ve_info.exit_qualification >> 16) as u16;
|
||||
|
||||
match direction {
|
||||
Direction::In => {
|
||||
trapframe.set_rax(io_read(size, port).unwrap() as usize);
|
||||
}
|
||||
Direction::Out => {
|
||||
io_write(size, port, trapframe.rax() as u32).unwrap();
|
||||
}
|
||||
};
|
||||
true
|
||||
}
|
||||
|
||||
macro_rules! tdvmcall_io_read {
|
||||
($port:expr, $ty:ty) => {{
|
||||
let mut args = TdVmcallArgs {
|
||||
r11: TDVMCALL_IO,
|
||||
r12: core::mem::size_of::<$ty>() as u64,
|
||||
r13: IO_READ,
|
||||
r14: $port as u64,
|
||||
..Default::default()
|
||||
};
|
||||
match td_vmcall(&mut args) {
|
||||
Ok(()) => Ok(args.r11 as u32),
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
fn io_read(size: usize, port: u16) -> Result<u32, TdVmcallError> {
|
||||
match size {
|
||||
1 => tdvmcall_io_read!(port, u8),
|
||||
2 => tdvmcall_io_read!(port, u16),
|
||||
4 => tdvmcall_io_read!(port, u32),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! tdvmcall_io_write {
|
||||
($port:expr, $byte:expr, $size:expr) => {{
|
||||
let mut args = TdVmcallArgs {
|
||||
r11: TDVMCALL_IO,
|
||||
r12: core::mem::size_of_val(&$byte) as u64,
|
||||
r13: IO_WRITE,
|
||||
r14: $port as u64,
|
||||
r15: $byte as u64,
|
||||
..Default::default()
|
||||
};
|
||||
match td_vmcall(&mut args) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(res) => Err(res),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
fn io_write(size: usize, port: u16, byte: u32) -> Result<(), TdVmcallError> {
|
||||
match size {
|
||||
1 => tdvmcall_io_write!(port, byte, u8),
|
||||
2 => tdvmcall_io_write!(port, byte, u16),
|
||||
4 => tdvmcall_io_write!(port, byte, u32),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn td_vmcall(args: &mut TdVmcallArgs) -> Result<(), TdVmcallError> {
|
||||
let td_vmcall_result = unsafe { asm_td_vmcall(args) };
|
||||
if td_vmcall_result == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(td_vmcall_result.into())
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
struct LineSts: u8 {
|
||||
const INPUT_FULL = 1;
|
||||
const OUTPUT_EMPTY = 1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
fn line_sts() -> LineSts {
|
||||
LineSts::from_bits_truncate(unsafe { PortRead::read_from_port(SERIAL_LINE_STS) })
|
||||
}
|
||||
|
||||
struct Serial;
|
||||
|
||||
impl Write for Serial {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
for &c in s.as_bytes() {
|
||||
serial_write_byte(c);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print(args: fmt::Arguments) {
|
||||
Serial
|
||||
.write_fmt(args)
|
||||
.expect("Failed to write to serial port");
|
||||
}
|
||||
|
||||
fn serial_write_byte(byte: u8) {
|
||||
match byte {
|
||||
8 | 0x7F => {
|
||||
while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
|
||||
tdvmcall_io_write!(SERIAL_IO_PORT, 8, u8).unwrap();
|
||||
while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
|
||||
tdvmcall_io_write!(SERIAL_IO_PORT, b' ', u8).unwrap();
|
||||
while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
|
||||
tdvmcall_io_write!(SERIAL_IO_PORT, 8, u8).unwrap();
|
||||
}
|
||||
_ => {
|
||||
while !line_sts().contains(LineSts::OUTPUT_EMPTY) {}
|
||||
tdvmcall_io_write!(SERIAL_IO_PORT, byte, u8).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! serial_print {
|
||||
($fmt: literal $(, $($arg: tt)+)?) => {
|
||||
$crate::tdvmcall::print(format_args!($fmt $(, $($arg)+)?));
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! serial_println {
|
||||
($fmt: literal $(, $($arg: tt)+)?) => {
|
||||
$crate::tdvmcall::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?))
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ smoltcp = { version = "0.9.1", default-features = false, features = [
|
||||
"socket-raw",
|
||||
"socket-dhcpv4",
|
||||
] }
|
||||
tdx-guest = { path = "../../../framework/libs/tdx-guest", optional = true }
|
||||
|
||||
# parse elf file
|
||||
xmas-elf = "0.8.0"
|
||||
@ -65,3 +66,6 @@ getrandom = { version = "0.2.10", default-features = false, features = [
|
||||
[dependencies.lazy_static]
|
||||
version = "1.0"
|
||||
features = ["spin_no_std"]
|
||||
|
||||
[features]
|
||||
intel_tdx = ["dep:tdx-guest"]
|
||||
|
Reference in New Issue
Block a user