#![no_std]
use hvdef::HV_PAGE_SIZE;
use memory_range::MemoryRange;
use x86defs::tdx::TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT;
use x86defs::tdx::TdCallLeaf;
use x86defs::tdx::TdCallResult;
use x86defs::tdx::TdCallResultCode;
use x86defs::tdx::TdGlaVmAndFlags;
use x86defs::tdx::TdVmCallR10Result;
use x86defs::tdx::TdVmCallSubFunction;
use x86defs::tdx::TdgMemPageAcceptRcx;
use x86defs::tdx::TdgMemPageAttrGpaMappingReadRcxResult;
use x86defs::tdx::TdgMemPageAttrWriteR8;
use x86defs::tdx::TdgMemPageAttrWriteRcx;
use x86defs::tdx::TdgMemPageGpaAttr;
use x86defs::tdx::TdgMemPageLevel;
use x86defs::tdx::TdxExtendedFieldCode;
use x86defs::tdx::TdxGlaListInfo;
#[derive(Debug)]
pub struct TdcallInput {
pub leaf: TdCallLeaf,
pub rcx: u64,
pub rdx: u64,
pub r8: u64,
pub r9: u64,
pub r10: u64,
pub r11: u64,
pub r12: u64,
pub r13: u64,
pub r14: u64,
pub r15: u64,
}
#[derive(Debug)]
pub struct TdcallOutput {
pub rax: TdCallResult,
pub rcx: u64,
pub rdx: u64,
pub r8: u64,
pub r10: u64,
pub r11: u64,
}
pub trait Tdcall {
fn tdcall(&mut self, input: TdcallInput) -> TdcallOutput;
}
pub fn tdcall_rdmsr(
call: &mut impl Tdcall,
msr_index: u32,
msr_value: &mut u64,
) -> Result<(), TdVmCallR10Result> {
let input = TdcallInput {
leaf: TdCallLeaf::VP_VMCALL,
rcx: 0x1c00, rdx: 0,
r8: 0,
r9: 0,
r10: 0, r11: TdVmCallSubFunction::RdMsr as u64,
r12: msr_index as u64,
r13: 0,
r14: 0,
r15: 0,
};
let output = call.tdcall(input);
assert_eq!(
output.rax.code(),
TdCallResultCode::SUCCESS,
"unexpected nonzero rax {:x} returned by tdcall vmcall",
u64::from(output.rax)
);
let result = TdVmCallR10Result(output.r10);
*msr_value = output.r11;
#[cfg(feature = "tracing")]
tracing::trace!(msr_index, msr_value, output.r10, "tdcall_rdmsr");
match result {
TdVmCallR10Result::SUCCESS => Ok(()),
val => Err(val),
}
}
pub fn tdcall_wrmsr(
call: &mut impl Tdcall,
msr_index: u32,
msr_value: u64,
) -> Result<(), TdVmCallR10Result> {
let input = TdcallInput {
leaf: TdCallLeaf::VP_VMCALL,
rcx: 0x3c00, rdx: 0,
r8: 0,
r9: 0,
r10: 0, r11: TdVmCallSubFunction::WrMsr as u64,
r12: msr_index as u64,
r13: msr_value,
r14: 0,
r15: 0,
};
let output = call.tdcall(input);
assert_eq!(
output.rax.code(),
TdCallResultCode::SUCCESS,
"unexpected nonzero rax {:x} returned by tdcall vmcall",
u64::from(output.rax)
);
let result = TdVmCallR10Result(output.r10);
match result {
TdVmCallR10Result::SUCCESS => Ok(()),
val => Err(val),
}
}
pub fn tdcall_io_out(
call: &mut impl Tdcall,
port: u16,
value: u32,
size: u8,
) -> Result<(), TdVmCallR10Result> {
let input = TdcallInput {
leaf: TdCallLeaf::VP_VMCALL,
rcx: 0xFF00, rdx: 0,
r8: 0,
r9: 0,
r10: 0, r11: 30,
r12: size as u64,
r13: 1, r14: port as u64,
r15: value as u64,
};
let output = call.tdcall(input);
assert_eq!(
output.rax.code(),
TdCallResultCode::SUCCESS,
"unexpected nonzero rax {:x} returned by tdcall vmcall",
u64::from(output.rax)
);
if output.rax.code() != TdCallResultCode::SUCCESS {
panic!(
"unexpected nonzero rax {:x} on tdcall_io_out",
u64::from(output.rax)
);
}
let result = TdVmCallR10Result(output.r10);
match result {
TdVmCallR10Result::SUCCESS => Ok(()),
val => Err(val),
}
}
pub fn tdcall_io_in(call: &mut impl Tdcall, port: u16, size: u8) -> Result<u32, TdVmCallR10Result> {
let input = TdcallInput {
leaf: TdCallLeaf::VP_VMCALL,
rcx: 0xFF00, rdx: 0,
r8: 0,
r9: 0,
r10: 0, r11: TdVmCallSubFunction::IoInstr as u64,
r12: size as u64,
r13: 0, r14: port as u64,
r15: 0,
};
let output = call.tdcall(input);
assert_eq!(
output.rax.code(),
TdCallResultCode::SUCCESS,
"unexpected nonzero rax {:x} returned by tdcall vmcall",
u64::from(output.rax)
);
let result = TdVmCallR10Result(output.r10);
match result {
TdVmCallR10Result::SUCCESS => Ok(output.r11 as u32),
val => Err(val),
}
}
pub fn tdcall_accept_pages(
call: &mut impl Tdcall,
gpa_page_number: u64,
as_large_page: bool,
) -> Result<(), TdCallResultCode> {
#[cfg(feature = "tracing")]
tracing::trace!(gpa_page_number, as_large_page, "tdcall_accept_pages");
let rcx = TdgMemPageAcceptRcx::new()
.with_gpa_page_number(gpa_page_number)
.with_level(if as_large_page {
TdgMemPageLevel::Size2Mb
} else {
TdgMemPageLevel::Size4k
});
let input = TdcallInput {
leaf: TdCallLeaf::MEM_PAGE_ACCEPT,
rcx: rcx.into(),
rdx: 0,
r8: 0,
r9: 0,
r10: 0,
r11: 0,
r12: 0,
r13: 0,
r14: 0,
r15: 0,
};
let output = call.tdcall(input);
match output.rax.code() {
TdCallResultCode::SUCCESS => Ok(()),
val => Err(val),
}
}
#[derive(Debug)]
pub struct TdgPageAttrRdResult {
pub mapping: TdgMemPageAttrGpaMappingReadRcxResult,
pub attributes: TdgMemPageGpaAttr,
}
pub fn tdcall_page_attr_rd(
call: &mut impl Tdcall,
gpa: u64,
) -> Result<TdgPageAttrRdResult, TdCallResultCode> {
#[cfg(feature = "tracing")]
tracing::trace!(gpa, "tdcall_page_attr_rd");
let input = TdcallInput {
leaf: TdCallLeaf::MEM_PAGE_ATTR_RD,
rcx: gpa,
rdx: 0,
r8: 0,
r9: 0,
r10: 0,
r11: 0,
r12: 0,
r13: 0,
r14: 0,
r15: 0,
};
let output = call.tdcall(input);
match output.rax.code() {
TdCallResultCode::SUCCESS => Ok(TdgPageAttrRdResult {
mapping: TdgMemPageAttrGpaMappingReadRcxResult::from(output.rcx),
attributes: TdgMemPageGpaAttr::from(output.rdx),
}),
val => Err(val),
}
}
pub fn tdcall_page_attr_wr(
call: &mut impl Tdcall,
mapping: TdgMemPageAttrWriteRcx,
attributes: TdgMemPageGpaAttr,
mask: TdgMemPageAttrWriteR8,
) -> Result<(), TdCallResultCode> {
#[cfg(feature = "tracing")]
tracing::trace!(?mapping, ?attributes, ?mask, "tdcall_page_attr_wr");
let input = TdcallInput {
leaf: TdCallLeaf::MEM_PAGE_ATTR_WR,
rcx: mapping.into(),
rdx: attributes.into(),
r8: mask.into(),
r9: 0,
r10: 0,
r11: 0,
r12: 0,
r13: 0,
r14: 0,
r15: 0,
};
let output = call.tdcall(input);
match output.rax.code() {
TdCallResultCode::SUCCESS => Ok(()),
val => Err(val),
}
}
fn set_page_attr(
call: &mut impl Tdcall,
mapping: TdgMemPageAttrWriteRcx,
attributes: TdgMemPageGpaAttr,
mask: TdgMemPageAttrWriteR8,
) -> Result<(), TdCallResultCode> {
match tdcall_page_attr_wr(call, mapping, attributes, mask) {
Ok(()) => {
#[cfg(debug_assertions)]
{
let result =
tdcall_page_attr_rd(call, mapping.gpa_page_number() * HV_PAGE_SIZE).unwrap();
assert_eq!(u64::from(mapping), result.mapping.into());
assert_eq!(attributes.l1(), result.attributes.l1());
assert_eq!(
attributes.into_bits() ^ mask.with_reserved(0).into_bits(),
result.attributes.into_bits() ^ mask.with_reserved(0).into_bits()
);
}
Ok(())
}
Err(e) => Err(e),
}
}
#[derive(Debug)]
pub enum AcceptPagesError {
Unknown(TdCallResultCode),
Attributes(TdCallResultCode),
Invalid(TdCallResultCode),
Busy(TdCallResultCode),
}
pub enum AcceptPagesAttributes {
None,
Set {
attributes: TdgMemPageGpaAttr,
mask: TdgMemPageAttrWriteR8,
},
}
pub fn accept_pages<T: Tdcall>(
call: &mut T,
range: MemoryRange,
attributes: AcceptPagesAttributes,
) -> Result<(), AcceptPagesError> {
#[cfg(feature = "tracing")]
tracing::trace!(%range, "accept_pages");
let set_attributes = |call: &mut T, mapping| -> Result<(), AcceptPagesError> {
match attributes {
AcceptPagesAttributes::None => Ok(()),
AcceptPagesAttributes::Set { attributes, mask } => {
set_page_attr(call, mapping, attributes, mask).map_err(AcceptPagesError::Attributes)
}
}
};
let mut range = range;
while !range.is_empty() {
if range.start() % x86defs::X64_LARGE_PAGE_SIZE == 0
&& range.len() >= x86defs::X64_LARGE_PAGE_SIZE
{
match tdcall_accept_pages(call, range.start_4k_gpn(), true) {
Ok(_) => {
set_attributes(
call,
TdgMemPageAttrWriteRcx::new()
.with_gpa_page_number(range.start_4k_gpn())
.with_level(TdgMemPageLevel::Size2Mb),
)?;
range =
MemoryRange::new(range.start() + x86defs::X64_LARGE_PAGE_SIZE..range.end());
continue;
}
Err(e) => match e {
TdCallResultCode::OPERAND_BUSY => return Err(AcceptPagesError::Busy(e)),
TdCallResultCode::OPERAND_INVALID => return Err(AcceptPagesError::Invalid(e)),
TdCallResultCode::PAGE_ALREADY_ACCEPTED => {
panic!("page {} already accepted", range.start_4k_gpn());
}
TdCallResultCode::PAGE_SIZE_MISMATCH => {
#[cfg(feature = "tracing")]
tracing::trace!("accept pages size mismatch returned");
}
_ => return Err(AcceptPagesError::Unknown(e)),
},
}
}
match tdcall_accept_pages(call, range.start_4k_gpn(), false) {
Ok(_) => {
set_attributes(
call,
TdgMemPageAttrWriteRcx::new()
.with_gpa_page_number(range.start_4k_gpn())
.with_level(TdgMemPageLevel::Size4k),
)?;
range = MemoryRange::new(range.start() + HV_PAGE_SIZE..range.end());
}
Err(e) => match e {
TdCallResultCode::OPERAND_BUSY => return Err(AcceptPagesError::Busy(e)),
TdCallResultCode::OPERAND_INVALID => return Err(AcceptPagesError::Invalid(e)),
TdCallResultCode::PAGE_ALREADY_ACCEPTED => {
panic!("page {} already accepted", range.start_4k_gpn());
}
_ => return Err(AcceptPagesError::Unknown(e)),
},
}
}
Ok(())
}
pub fn set_page_attributes(
call: &mut impl Tdcall,
range: MemoryRange,
attributes: TdgMemPageGpaAttr,
mask: TdgMemPageAttrWriteR8,
) -> Result<(), TdCallResultCode> {
#[cfg(feature = "tracing")]
tracing::trace!(
%range,
?attributes,
?mask,
"set_page_attributes"
);
let mut range = range;
while !range.is_empty() {
if range.start() % x86defs::X64_LARGE_PAGE_SIZE == 0
&& range.len() >= x86defs::X64_LARGE_PAGE_SIZE
{
let mapping = TdgMemPageAttrWriteRcx::new()
.with_gpa_page_number(range.start_4k_gpn())
.with_level(TdgMemPageLevel::Size2Mb);
match set_page_attr(call, mapping, attributes, mask) {
Ok(()) => {
range =
MemoryRange::new(range.start() + x86defs::X64_LARGE_PAGE_SIZE..range.end());
continue;
}
Err(TdCallResultCode::PAGE_SIZE_MISMATCH) => {
#[cfg(feature = "tracing")]
tracing::trace!("set pages attr size mismatch returned");
}
Err(e) => return Err(e),
}
}
let mapping = TdgMemPageAttrWriteRcx::new()
.with_gpa_page_number(range.start_4k_gpn())
.with_level(TdgMemPageLevel::Size4k);
match set_page_attr(call, mapping, attributes, mask) {
Ok(()) => range = MemoryRange::new(range.start() + HV_PAGE_SIZE..range.end()),
Err(e) => return Err(e),
}
}
Ok(())
}
pub fn tdcall_map_gpa(
call: &mut impl Tdcall,
range: MemoryRange,
host_visible: bool,
) -> Result<(), TdVmCallR10Result> {
let mut gpa = if host_visible {
range.start() | TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT
} else {
range.start() & !TDX_SHARED_GPA_BOUNDARY_ADDRESS_BIT
};
let end = gpa + range.len();
while gpa < end {
let input = TdcallInput {
leaf: TdCallLeaf::VP_VMCALL,
rcx: 0x3c00, rdx: 0,
r8: 0,
r9: 0,
r10: 0, r11: TdVmCallSubFunction::MapGpa as u64,
r12: gpa,
r13: end - gpa,
r14: 0,
r15: 0,
};
let output = call.tdcall(input);
assert_eq!(
output.rax.code(),
TdCallResultCode::SUCCESS,
"unexpected nonzero rax {:x} returned by tdcall vmcall",
u64::from(output.rax)
);
let result = TdVmCallR10Result(output.r10);
match result {
TdVmCallR10Result::SUCCESS => gpa = end,
TdVmCallR10Result::RETRY => gpa = output.r11,
val => return Err(val),
}
}
Ok(())
}
pub fn tdcall_vp_wr(
call: &mut impl Tdcall,
field_code: TdxExtendedFieldCode,
value: u64,
mask: u64,
) -> Result<u64, TdCallResult> {
let input = TdcallInput {
leaf: TdCallLeaf::VP_WR,
rcx: 0,
rdx: field_code.into(),
r8: value,
r9: mask,
r10: 0,
r11: 0,
r12: 0,
r13: 0,
r14: 0,
r15: 0,
};
let output = call.tdcall(input);
match output.rax.code() {
TdCallResultCode::SUCCESS => Ok(output.r8),
_ => Err(output.rax),
}
}
pub fn tdcall_vp_rd(
call: &mut impl Tdcall,
field_code: TdxExtendedFieldCode,
) -> Result<u64, TdCallResult> {
let input = TdcallInput {
leaf: TdCallLeaf::VP_RD,
rcx: 0,
rdx: field_code.into(),
r8: 0,
r9: 0,
r10: 0,
r11: 0,
r12: 0,
r13: 0,
r14: 0,
r15: 0,
};
let output = call.tdcall(input);
match output.rax.code() {
TdCallResultCode::SUCCESS => Ok(output.r8),
_ => Err(output.rax),
}
}
pub fn tdcall_vp_invgla(
call: &mut impl Tdcall,
gla_flags: TdGlaVmAndFlags,
gla_info: TdxGlaListInfo,
) -> Result<(), TdCallResult> {
let input = TdcallInput {
leaf: TdCallLeaf::VP_INVGLA,
rcx: gla_flags.into(),
rdx: gla_info.into(),
r8: 0,
r9: 0,
r10: 0,
r11: 0,
r12: 0,
r13: 0,
r14: 0,
r15: 0,
};
let output = call.tdcall(input);
match output.rax.code() {
TdCallResultCode::SUCCESS => Ok(()),
_ => Err(output.rax),
}
}