Intel VT-d hardware gains a second set of DMA translation structures, managed by the TDX module and used only for devices assigned to TDs. Each IOMMU has a trusted root-table address register and a trusted invalidation queue, both accessible only from SEAM. The existing untrusted DMA translation path of the IOMMU stays under host control. Before trusted DMA can be used through an IOMMU, the TDX module must initialize these resources. TDH.IOMMU.SETUP does this: it puts the IOMMU into Secure TDX Mode, programs the trusted root table and the trusted invalidation queue, and protects the related I/O resources from further untrusted host access. The host provides the memory for this through a parameter page that lists the invalidation queue buffers and the metadata pages the TDX module needs for the IOMMU. TDH.IOMMU.CLEAR does the reverse. It tears down the Secure TDX Mode state of the IOMMU, after which the host can reclaim the memory. Add wrappers for both SEAMCALLs and export them to the tdx-host driver, which allocates the memory and issues them. TDH.IOMMU.SETUP and TDH.IOMMU.CLEAR are defined in the Intel TDX Connect ABI Specification [1]. [1] https://cdrdv2.intel.com/v1/dl/getContent/858625 Signed-off-by: Lu Baolu --- arch/x86/include/asm/tdx.h | 14 ++++++++++ arch/x86/virt/vmx/tdx/tdx.h | 2 ++ arch/x86/virt/vmx/tdx/tdx.c | 53 +++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h index 89e7628796b0..d077990802b9 100644 --- a/arch/x86/include/asm/tdx.h +++ b/arch/x86/include/asm/tdx.h @@ -162,6 +162,17 @@ struct tdx_td { int tdcx_nr_pages; }; +#define TDX_IOMMU_MT_IQ_NR 2 +#define TDX_IOMMU_MT_MAX_PAGES (PAGE_SIZE / sizeof(u64) - TDX_IOMMU_MT_IQ_NR) + +/* IOMMU_MT parameter page for TDH.IOMMU.SETUP (ABI spec Table 3.35). */ +struct tdx_iommu_mt_param { + u64 iq[TDX_IOMMU_MT_IQ_NR]; /* HPA | number of 4K pages */ + u64 mt[TDX_IOMMU_MT_MAX_PAGES]; /* HPA of each metadata page */ +}; + +static_assert(sizeof(struct tdx_iommu_mt_param) == PAGE_SIZE); + struct tdx_vp { /* TDVP root page */ struct page *tdvpr_page; @@ -202,6 +213,9 @@ u64 tdh_mem_page_remove(struct tdx_td *td, u64 gpa, enum pg_level level, u64 *ex u64 tdh_phymem_cache_wb(bool resume); u64 tdh_phymem_page_wbinvd_tdr(struct tdx_td *td); u64 tdh_phymem_page_wbinvd_hkid(u64 hkid, kvm_pfn_t pfn); +u64 tdh_iommu_setup(u64 reg_base, struct tdx_iommu_mt_param *param, + u64 *tdx_iommu_id); +u64 tdh_iommu_clear(u64 tdx_iommu_id); #else static inline void tdx_init(void) { } static inline u32 tdx_get_nr_guest_keyids(void) { return 0; } diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h index cac0a5abbff1..693adda89311 100644 --- a/arch/x86/virt/vmx/tdx/tdx.h +++ b/arch/x86/virt/vmx/tdx/tdx.h @@ -53,6 +53,8 @@ #define TDH_EXT_INIT 60 #define TDH_EXT_MEM_ADD 61 #define TDH_SYS_DISABLE 69 +#define TDH_IOMMU_SETUP 128 +#define TDH_IOMMU_CLEAR 129 /* TDX page types */ #define PT_NDA 0x0 diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index 4cce77b6a31f..a1f35188a272 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -2567,3 +2567,56 @@ void tdx_sys_disable(void) if (ret && (ret & TDX_SW_ERROR) != TDX_SW_ERROR) pr_err("TDH.SYS.DISABLE failed: 0x%016llx\n", ret); } + +/* No cache flush is needed; hardware keeps caches coherent across KeyIDs. */ +u64 tdh_iommu_setup(u64 reg_base, struct tdx_iommu_mt_param *param, + u64 *tdx_iommu_id) +{ + struct tdx_module_args args = { + .rcx = reg_base, + .rdx = __pa(param), + }; + u64 ret; + + /* + * Retry only on TDX_INTERRUPTED_RESUMABLE: + * + * - TDX_INTERRUPTED_RESUMABLE guarantees forward progress between + * calls. + * - On TDX_INTERRUPTED_RESUMABLE, all registers except RAX remain + * unchanged. + * + * The ABI spec also allows retrying on TDX_OPERAND_BUSY, but that + * status doesn't guarantee forward progress, so retrying could spin + * forever. The host serializes all TDH.IOMMU.SETUP and + * TDH.IOMMU.CLEAR calls for an IOMMU, so the TDX module should never + * report the operand as busy. + */ + do { + ret = seamcall_ret(TDH_IOMMU_SETUP, &args); + } while (ret == TDX_INTERRUPTED_RESUMABLE); + + *tdx_iommu_id = args.rcx; + + return ret; +} +EXPORT_SYMBOL_FOR_MODULES(tdh_iommu_setup, "tdx-host"); + +u64 tdh_iommu_clear(u64 tdx_iommu_id) +{ + struct tdx_module_args args = { + .rcx = tdx_iommu_id, + }; + u64 ret; + + /* + * Don't retry on TDX_OPERAND_BUSY. See the comment in + * tdh_iommu_setup(). + */ + do { + ret = seamcall(TDH_IOMMU_CLEAR, &args); + } while (ret == TDX_INTERRUPTED_RESUMABLE); + + return ret; +} +EXPORT_SYMBOL_FOR_MODULES(tdh_iommu_clear, "tdx-host"); -- 2.43.0