* [PATCH 1/5] x86/virt/tdx: Add SEAMCALL wrappers for IOMMU setup/clear
2026-09-15 7:42 [PATCH 0/5] iommu/vt-d: Introduce trusted DMA initialization support Lu Baolu
@ 2026-09-15 7:42 ` Lu Baolu
2026-09-15 7:42 ` [PATCH 2/5] x86/virt/tdx: Read global metadata for trusted IOMMU Lu Baolu
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Lu Baolu @ 2026-09-15 7:42 UTC (permalink / raw)
To: iommu, x86, linux-coco, kvm
Cc: Joerg Roedel, Will Deacon, Robin Murphy, Jason Gunthorpe,
Kevin Tian, Dave Hansen, Kiryl Shutsemau, Rick Edgecombe,
yilun.xu, xiaoyao.li, Chao Gao, linux-kernel, Lu Baolu
Add SEAMCALL wrappers for TDH.IOMMU.SETUP and TDH.IOMMU.CLEAR.
TDH.IOMMU.SETUP transitions an IOMMU and related I/O stack into Secure
TDX Mode. During this transition, the TDX module validates and protects
relevant I/O resources from further untrusted host access, providing the
trusted foundation required before enabling TEE-IO security protocols
(e.g. SPDM/IDE).
TDH.IOMMU.CLEAR performs the reverse operation and tears down the Secure
TDX Mode state for the target IOMMU.
tdh_iommu_setup() takes the IOMMU register base and a host-allocated
parameter page for TDX-module metadata, and returns a unique
tdx_iommu_id for subsequent operations.
tdh_iommu_clear() takes tdx_iommu_id to tear down the Secure TDX Mode
state and return the IOMMU to normal operation.
The caller is responsible for allocating the required pages and
populating the parameter page in the ABI-defined format.
See Intel TDX Connect ABI Specification [1], Section 3.2
(TDX Connect Host-Side (SEAMCALL) Interface Functions).
Thanks to Yilun for the initial draft.
[1] https://cdrdv2.intel.com/v1/dl/getContent/858625
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
---
arch/x86/include/asm/tdx.h | 2 ++
arch/x86/virt/vmx/tdx/tdx.h | 2 ++
arch/x86/virt/vmx/tdx/tdx.c | 56 +++++++++++++++++++++++++++++++++++++
3 files changed, 60 insertions(+)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 89e97d5761d8..8b5411d3eb67 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -176,6 +176,8 @@ 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, void *root, 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 bdfd0e1e337a..d7beb42cf972 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -49,6 +49,8 @@
#define TDH_SYS_SHUTDOWN 52
#define TDH_SYS_UPDATE 53
#define TDH_SYS_DISABLE 69
+#define TDH_IOMMU_SETUP 128
+#define TDH_IOMMU_CLEAR 129
/*
* SEAMCALL leaf:
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 1b9ff749dd8e..012825fe1c3c 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -2034,3 +2034,59 @@ void tdx_sys_disable(void)
if (ret && (ret & TDX_SW_ERROR) != TDX_SW_ERROR)
pr_err("TDH.SYS.DISABLE failed: 0x%016llx\n", ret);
}
+
+/*
+ * Wrapper for TDH.IOMMU.SETUP leaf, which transitions the IOMMU and related
+ * I/O stack into Secure TDX Mode.
+ */
+u64 tdh_iommu_setup(u64 reg_base, void *root, u64 *tdx_iommu_id)
+{
+ /*
+ * The @root page format is defined by the TDX Connect ABI spec
+ * (Table 3.35, Structure of IOMMU_MT Parameter): the first two
+ * entries describe the size and HPA (Host Physical Address) of
+ * the two contiguous buffers for the invalidation queue; the
+ * remaining entries provide HPAs of IOMMU_MT_PAGES_COUNT metadata
+ * pages. IOMMU_MT_PAGES_COUNT is obtained from trusted IOMMU global
+ * metadata.
+ */
+ struct tdx_module_args args = {
+ .rcx = reg_base,
+ .rdx = __pa(root),
+ };
+ u64 ret;
+
+ /*
+ * Don't loop forever:
+ *
+ * - TDX_INTERRUPTED_RESUMABLE guarantees forward progress between
+ * calls.
+ * - On TDX_INTERRUPTED_RESUMABLE, all registers except RAX remain
+ * unchanged.
+ */
+ do {
+ ret = seamcall_ret(TDH_IOMMU_SETUP, &args);
+ } while (ret == TDX_INTERRUPTED_RESUMABLE);
+
+ *tdx_iommu_id = args.rcx;
+
+ return ret;
+}
+
+/*
+ * Wrapper for TDH.IOMMU.CLEAR leaf, which terminates Secure TDX Mode for the
+ * target IOMMU and restores control of related hardware resources to the host.
+ */
+u64 tdh_iommu_clear(u64 tdx_iommu_id)
+{
+ struct tdx_module_args args = {
+ .rcx = tdx_iommu_id,
+ };
+ u64 ret;
+
+ do {
+ ret = seamcall(TDH_IOMMU_CLEAR, &args);
+ } while (ret == TDX_INTERRUPTED_RESUMABLE);
+
+ return ret;
+}
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 2/5] x86/virt/tdx: Read global metadata for trusted IOMMU
2026-09-15 7:42 [PATCH 0/5] iommu/vt-d: Introduce trusted DMA initialization support Lu Baolu
2026-09-15 7:42 ` [PATCH 1/5] x86/virt/tdx: Add SEAMCALL wrappers for IOMMU setup/clear Lu Baolu
@ 2026-09-15 7:42 ` Lu Baolu
2026-09-15 7:42 ` [PATCH 3/5] iommu/vt-d: Add interfaces for trusted DMA initialization Lu Baolu
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Lu Baolu @ 2026-09-15 7:42 UTC (permalink / raw)
To: iommu, x86, linux-coco, kvm
Cc: Joerg Roedel, Will Deacon, Robin Murphy, Jason Gunthorpe,
Kevin Tian, Dave Hansen, Kiryl Shutsemau, Rick Edgecombe,
yilun.xu, xiaoyao.li, Chao Gao, linux-kernel, Lu Baolu
Add support for reading the global metadata of the trusted IOMMU.
The trusted IOMMU feature is optionally enumerated via TDX_FEATURES0.
Check this feature bit before reading the metadata to avoid causing
the entire TDX initialization to fail on platforms that do not support
it.
The read value represents the number of pages required for the IOMMU
metadata. The host will allocate this count of pages and provide them to
the TDX module when it configures the IOMMU to operate in Secure TDX mode
via the TDH.IOMMU.SETUP SEAMCALL.
Thanks to Yilun for the initial draft.
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
---
arch/x86/include/asm/tdx.h | 1 +
arch/x86/include/asm/tdx_global_metadata.h | 5 +++++
arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 20 ++++++++++++++++++++
3 files changed, 26 insertions(+)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 8b5411d3eb67..9c9168822274 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -35,6 +35,7 @@
/* Bit definitions of TDX_FEATURES0 metadata field */
#define TDX_FEATURES0_TD_PRESERVING BIT_ULL(1)
+#define TDX_FEATURES0_TDXCONNECT BIT_ULL(6)
#define TDX_FEATURES0_NO_RBP_MOD BIT_ULL(18)
#ifndef __ASSEMBLER__
diff --git a/arch/x86/include/asm/tdx_global_metadata.h b/arch/x86/include/asm/tdx_global_metadata.h
index 41150d546589..cf64452fe94f 100644
--- a/arch/x86/include/asm/tdx_global_metadata.h
+++ b/arch/x86/include/asm/tdx_global_metadata.h
@@ -44,12 +44,17 @@ struct tdx_sys_info_handoff {
u16 module_hv;
};
+struct tdx_sys_info_connect {
+ u16 iommu_mt_page_count;
+};
+
struct tdx_sys_info {
struct tdx_sys_info_version version;
struct tdx_sys_info_features features;
struct tdx_sys_info_tdmr tdmr;
struct tdx_sys_info_td_ctrl td_ctrl;
struct tdx_sys_info_td_conf td_conf;
+ struct tdx_sys_info_connect tdx_connect;
};
#endif
diff --git a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
index e49c300f23d4..2ba1c5dcdbe6 100644
--- a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
+++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
@@ -113,6 +113,20 @@ static int get_tdx_sys_info_handoff(struct tdx_sys_info_handoff *sysinfo_handoff
return 0;
}
+static __init int get_tdx_sys_info_connect(struct tdx_sys_info_connect *sysinfo_connect)
+{
+ int ret;
+ u64 val;
+
+ ret = read_sys_metadata_field(0x3000000100000003, &val);
+ if (ret)
+ return ret;
+
+ sysinfo_connect->iommu_mt_page_count = val;
+
+ return 0;
+}
+
static __init int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
{
int ret = 0;
@@ -129,5 +143,11 @@ static __init int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
ret = ret ?: get_tdx_sys_info_td_ctrl(&sysinfo->td_ctrl);
ret = ret ?: get_tdx_sys_info_td_conf(&sysinfo->td_conf);
+ if (ret)
+ return ret;
+
+ if (sysinfo->features.tdx_features0 & TDX_FEATURES0_TDXCONNECT)
+ ret = get_tdx_sys_info_connect(&sysinfo->tdx_connect);
+
return ret;
}
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 3/5] iommu/vt-d: Add interfaces for trusted DMA initialization
2026-09-15 7:42 [PATCH 0/5] iommu/vt-d: Introduce trusted DMA initialization support Lu Baolu
2026-09-15 7:42 ` [PATCH 1/5] x86/virt/tdx: Add SEAMCALL wrappers for IOMMU setup/clear Lu Baolu
2026-09-15 7:42 ` [PATCH 2/5] x86/virt/tdx: Read global metadata for trusted IOMMU Lu Baolu
@ 2026-09-15 7:42 ` Lu Baolu
2026-09-15 7:42 ` [PATCH 4/5] iommu/vt-d: Add helpers to set up and tear down TDX extensions Lu Baolu
2026-09-15 7:42 ` [PATCH 5/5] iommu/vt-d: Reserve MSB of domain ID space for TDX module Lu Baolu
4 siblings, 0 replies; 6+ messages in thread
From: Lu Baolu @ 2026-09-15 7:42 UTC (permalink / raw)
To: iommu, x86, linux-coco, kvm
Cc: Joerg Roedel, Will Deacon, Robin Murphy, Jason Gunthorpe,
Kevin Tian, Dave Hansen, Kiryl Shutsemau, Rick Edgecombe,
yilun.xu, xiaoyao.li, Chao Gao, linux-kernel, Lu Baolu
Add Intel VT-d interfaces to initialize and tear down TDX Connect trusted
DMA support across active IOMMUs.
Trusted DMA depends on trusted-IOMMU hardware extensions, advertised by
the TDXCS bit in the VT-d Extended Capability Register (ECAP). When
present, the TDX module can transition an IOMMU into Secure TDX Mode via
TDH.IOMMU.SETUP, and return it to normal host operation via
TDH.IOMMU.CLEAR.
In this mode, VT-d provides TDX-managed DMA translation and invalidation
resources (trusted translation root and invalidation queue), and splits
domain ID ownership so the TDX module can use its reserved namespace
independently from host-managed DIDs.
Introduce the following interfaces:
- intel_tdxc_init(): bring up TDX Connect support on all active IOMMUs
that advertise TDXCS and have DMA translation enabled.
- intel_tdxc_exit(): tear down per-IOMMU TDX Connect state.
Initialization is best-effort at system scope: IOMMUs without required
support are skipped, while failures on attempted bring-up are treated as
errors and trigger teardown of previously initialized units.
These entry points are intended to be called by the Intel TDX Connect
platform TSM driver during module init/exit.
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
---
drivers/iommu/intel/Kconfig | 14 ++++++
drivers/iommu/intel/Makefile | 1 +
drivers/iommu/intel/iommu.h | 7 +++
include/linux/dmar.h | 12 ++++++
drivers/iommu/intel/iommu.c | 11 +++++
drivers/iommu/intel/tdxc.c | 82 ++++++++++++++++++++++++++++++++++++
6 files changed, 127 insertions(+)
create mode 100644 drivers/iommu/intel/tdxc.c
diff --git a/drivers/iommu/intel/Kconfig b/drivers/iommu/intel/Kconfig
index 5471f814e073..e8c0ea79a31d 100644
--- a/drivers/iommu/intel/Kconfig
+++ b/drivers/iommu/intel/Kconfig
@@ -100,4 +100,18 @@ config INTEL_IOMMU_PERF_EVENTS
to aid performance tuning and debug. These are available on modern
processors which support Intel VT-d 4.0 and later.
+config INTEL_IOMMU_TDX_CONNECT
+ bool "Intel IOMMU support for TDX Connect"
+ depends on INTEL_TDX_HOST
+ help
+ Enable Intel VT-d support required by TDX Connect on TDX host systems.
+
+ Select this if the host will run TDX Connect workloads that require
+ trusted assignment/sharing of devices with TDX guests. If enabled,
+ the Intel IOMMU driver integrates with the TDX host module so DMA
+ translation state used by those workloads is managed in the TDX trust
+ domain.
+
+ If unsure, say N.
+
endif # INTEL_IOMMU
diff --git a/drivers/iommu/intel/Makefile b/drivers/iommu/intel/Makefile
index ada651c4a01b..25bf3b970acb 100644
--- a/drivers/iommu/intel/Makefile
+++ b/drivers/iommu/intel/Makefile
@@ -6,3 +6,4 @@ obj-$(CONFIG_INTEL_IOMMU_DEBUGFS) += debugfs.o
obj-$(CONFIG_INTEL_IOMMU_SVM) += svm.o
obj-$(CONFIG_IRQ_REMAP) += irq_remapping.o
obj-$(CONFIG_INTEL_IOMMU_PERF_EVENTS) += perfmon.o
+obj-$(CONFIG_INTEL_IOMMU_TDX_CONNECT) += tdxc.o
diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h
index 23dbe6c24439..452a381e6a40 100644
--- a/drivers/iommu/intel/iommu.h
+++ b/drivers/iommu/intel/iommu.h
@@ -187,6 +187,7 @@
*/
#define ecap_pms(e) (((e) >> 51) & 0x1)
+#define ecap_tdxcs(e) (((e) >> 50) & 0x1)
#define ecap_rps(e) (((e) >> 49) & 0x1)
#define ecap_smpwc(e) (((e) >> 48) & 0x1)
#define ecap_flts(e) (((e) >> 47) & 0x1)
@@ -1323,6 +1324,12 @@ static inline void intel_iommu_debugfs_create_dev_pasid(struct dev_pasid_info *d
static inline void intel_iommu_debugfs_remove_dev_pasid(struct dev_pasid_info *dev_pasid) {}
#endif /* CONFIG_INTEL_IOMMU_DEBUGFS */
+#ifdef CONFIG_INTEL_IOMMU_TDX_CONNECT
+extern bool intel_tdxc_initialized;
+#else
+#define intel_tdxc_initialized (0)
+#endif /* CONFIG_INTEL_IOMMU_TDX_CONNECT */
+
extern const struct attribute_group *intel_iommu_groups[];
struct context_entry *iommu_context_addr(struct intel_iommu *iommu, u8 bus,
u8 devfn, int alloc);
diff --git a/include/linux/dmar.h b/include/linux/dmar.h
index 63e35df2cef4..361c0d4a45fb 100644
--- a/include/linux/dmar.h
+++ b/include/linux/dmar.h
@@ -167,6 +167,18 @@ static inline int dmar_iommu_hotplug(struct dmar_drhd_unit *dmaru, bool insert)
}
#endif /* CONFIG_INTEL_IOMMU */
+#ifdef CONFIG_INTEL_IOMMU_TDX_CONNECT
+int intel_tdxc_init(void);
+void intel_tdxc_exit(void);
+#else
+static inline int intel_tdxc_init(void)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void intel_tdxc_exit(void) { }
+#endif /* CONFIG_INTEL_IOMMU_TDX_CONNECT */
+
#ifdef CONFIG_IRQ_REMAP
extern int dmar_ir_hotplug(struct dmar_drhd_unit *dmaru, bool insert);
#else /* CONFIG_IRQ_REMAP */
diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index 2e3b3ab216f8..e88457d96b53 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -2168,6 +2168,17 @@ static int intel_iommu_add(struct dmar_drhd_unit *dmaru)
iommu_set_root_entry(iommu);
iommu_enable_translation(iommu);
+ /*
+ * If an IOMMU is hot-added after intel_tdxc_initialized is set, it is
+ * not enrolled into TDX secure mode. Ideally this should be integrated
+ * with dmar_iommu_hotplug() so intel_iommu_bringup_tdxc() can run on
+ * hotplug. This is currently skipped due to lack of hardware validation.
+ * Log this limitation to make it visible.
+ */
+ if (intel_tdxc_initialized && ecap_tdxcs(iommu->ecap))
+ pr_info("Trusted DMA for TEE is not enabled on hot-added IOMMU %s\n",
+ iommu->name);
+
iommu_disable_protect_mem_regions(iommu);
return 0;
diff --git a/drivers/iommu/intel/tdxc.c b/drivers/iommu/intel/tdxc.c
new file mode 100644
index 000000000000..559c752a1527
--- /dev/null
+++ b/drivers/iommu/intel/tdxc.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * tdxc.c - Intel TDX Connect Extensions support
+ *
+ * Copyright (C) 2026 Intel Corporation
+ */
+
+#define pr_fmt(fmt) "DMAR: " fmt
+
+#include <linux/pci.h>
+#include <asm/vmx.h>
+#include <asm/tdx.h>
+#include "iommu.h"
+
+bool intel_tdxc_initialized;
+
+static int intel_iommu_bringup_tdxc(struct intel_iommu *iommu, unsigned int nr_pages)
+{
+ /*
+ * Nothing to do if the iommu doesn't support TDX extension or the
+ * DMA translation has not been enabled.
+ */
+ if (!ecap_tdxcs(iommu->ecap) || !(iommu->gcmd & DMA_GCMD_TE))
+ return 0;
+
+ /* Bring-up is not complete yet; report as unsupported for now. */
+ return -EOPNOTSUPP;
+}
+
+static void intel_iommu_teardown_tdxc(struct intel_iommu *iommu)
+{
+}
+
+void intel_tdxc_exit(void)
+{
+ struct dmar_drhd_unit *drhd;
+ struct intel_iommu *iommu;
+
+ guard(rwsem_write)(&dmar_global_lock);
+ if (!intel_tdxc_initialized)
+ return;
+
+ for_each_active_iommu(iommu, drhd)
+ intel_iommu_teardown_tdxc(iommu);
+ intel_tdxc_initialized = false;
+}
+EXPORT_SYMBOL_GPL(intel_tdxc_exit);
+
+int intel_tdxc_init(void)
+{
+ const struct tdx_sys_info *tdx_sysinfo = tdx_get_sysinfo();
+ struct dmar_drhd_unit *drhd;
+ unsigned int mt_page_count;
+ struct intel_iommu *iommu;
+ int ret;
+
+ if (!intel_iommu_enabled)
+ return -EOPNOTSUPP;
+
+ if (!tdx_sysinfo ||
+ !(tdx_sysinfo->features.tdx_features0 & TDX_FEATURES0_TDXCONNECT))
+ return -EOPNOTSUPP;
+
+ mt_page_count = tdx_sysinfo->tdx_connect.iommu_mt_page_count;
+ guard(rwsem_write)(&dmar_global_lock);
+ if (intel_tdxc_initialized)
+ return 0;
+
+ for_each_active_iommu(iommu, drhd) {
+ ret = intel_iommu_bringup_tdxc(iommu, mt_page_count);
+ if (ret) {
+ for_each_active_iommu(iommu, drhd)
+ intel_iommu_teardown_tdxc(iommu);
+
+ return ret;
+ }
+ }
+ intel_tdxc_initialized = true;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(intel_tdxc_init);
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 4/5] iommu/vt-d: Add helpers to set up and tear down TDX extensions
2026-09-15 7:42 [PATCH 0/5] iommu/vt-d: Introduce trusted DMA initialization support Lu Baolu
` (2 preceding siblings ...)
2026-09-15 7:42 ` [PATCH 3/5] iommu/vt-d: Add interfaces for trusted DMA initialization Lu Baolu
@ 2026-09-15 7:42 ` Lu Baolu
2026-09-15 7:42 ` [PATCH 5/5] iommu/vt-d: Reserve MSB of domain ID space for TDX module Lu Baolu
4 siblings, 0 replies; 6+ messages in thread
From: Lu Baolu @ 2026-09-15 7:42 UTC (permalink / raw)
To: iommu, x86, linux-coco, kvm
Cc: Joerg Roedel, Will Deacon, Robin Murphy, Jason Gunthorpe,
Kevin Tian, Dave Hansen, Kiryl Shutsemau, Rick Edgecombe,
yilun.xu, xiaoyao.li, Chao Gao, linux-kernel, Lu Baolu
Implement per-IOMMU TDX Connect bring-up/tear-down helpers.
For each active IOMMU that advertises TDXCS and has DMA translation
enabled, the driver allocates and populates the IOMMU_MT parameter
layout required by TDH.IOMMU.SETUP:
- two contiguous invalidation-queue buffers, and
- IOMMU_MT_PAGES_COUNT metadata pages.
The driver invokes tdh_iommu_setup() to transition the IOMMU into Secure
TDX Mode, then stores the returned tdx_iommu_id and allocation state for
later cleanup.
On teardown, or on partial-init rollback, the driver calls
tdh_iommu_clear() for each initialized IOMMU and frees all associated
IOMMU_MT pages.
Completion handling:
- TDX_SUCCESS: setup completed successfully.
- TDX_OPERAND_INVALID: treated as "not supported on this IOMMU" and
skipped.
- all other return codes: treated as failures and abort initialization
with rollback.
No explicit clflush is needed for pages shared with the TDX module in
this flow, as cache coherency is handled architecturally.
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
---
drivers/iommu/intel/iommu.h | 17 ++++
drivers/iommu/intel/dmar.c | 3 +
drivers/iommu/intel/tdxc.c | 150 ++++++++++++++++++++++++++++++++++++
3 files changed, 170 insertions(+)
diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h
index 452a381e6a40..7bf70113c4c8 100644
--- a/drivers/iommu/intel/iommu.h
+++ b/drivers/iommu/intel/iommu.h
@@ -680,6 +680,17 @@ struct iommu_pmu {
#define IOMMU_IRQ_ID_OFFSET_PRQ (DMAR_UNITS_SUPPORTED)
#define IOMMU_IRQ_ID_OFFSET_PERF (2 * DMAR_UNITS_SUPPORTED)
+/*
+ * Represents a list of pages for TDX Module defined IOMMU_MT object.
+ * Typically it uses a "root page" as the medium to exchange a list of
+ * data pages between host and TDX Module.
+ */
+struct tdxc_pages {
+ u64 *root;
+ void **pages;
+ unsigned int nr_entries;
+};
+
struct intel_iommu {
void __iomem *reg; /* Pointer to hardware regs, virtual addr */
u64 reg_phys; /* physical address of hw register set */
@@ -736,6 +747,12 @@ struct intel_iommu {
void *perf_statistic;
struct iommu_pmu *pmu;
+#ifdef CONFIG_INTEL_IOMMU_TDX_CONNECT
+ /* mutex to protect below tdx state data */
+ struct mutex tdx_lock;
+ u64 tdx_iommu_id;
+ struct tdxc_pages *mt_pages;
+#endif
};
/* PCI domain-device relationship */
diff --git a/drivers/iommu/intel/dmar.c b/drivers/iommu/intel/dmar.c
index ba675b08cd20..310b8d0c4132 100644
--- a/drivers/iommu/intel/dmar.c
+++ b/drivers/iommu/intel/dmar.c
@@ -1175,6 +1175,9 @@ static int alloc_iommu(struct dmar_drhd_unit *drhd)
ida_init(&iommu->domain_ida);
mutex_init(&iommu->did_lock);
iommu->max_domain_id = cap_ndoms(iommu->cap);
+#ifdef CONFIG_INTEL_IOMMU_TDX_CONNECT
+ mutex_init(&iommu->tdx_lock);
+#endif
ver = readl(iommu->reg + DMAR_VER_REG);
pr_info("%s: reg_base_addr %llx ver %d:%d cap %llx ecap %llx\n",
diff --git a/drivers/iommu/intel/tdxc.c b/drivers/iommu/intel/tdxc.c
index 559c752a1527..b5dfdeeb23db 100644
--- a/drivers/iommu/intel/tdxc.c
+++ b/drivers/iommu/intel/tdxc.c
@@ -10,12 +10,128 @@
#include <linux/pci.h>
#include <asm/vmx.h>
#include <asm/tdx.h>
+
+#include "../iommu-pages.h"
#include "iommu.h"
+#define IQ_BUFFERS_NUM 2
+#define IQ_BUFFER_PAGES 2
+#define IQ_BUFFER_SIZE SZ_8K
+
bool intel_tdxc_initialized;
+static void free_mt_pages(struct tdxc_pages *array)
+{
+ if (!array)
+ return;
+
+ for (int i = 0; i < array->nr_entries; i++)
+ iommu_free_pages(array->pages[i]);
+
+ iommu_free_pages(array->root);
+ kfree(array->pages);
+ kfree(array);
+}
+
+DEFINE_FREE(free_mt_pages, struct tdxc_pages *, free_mt_pages(_T))
+
+static void **alloc_mt_pages(unsigned int nr_entries, int node)
+{
+ void **pages;
+ void *vaddr;
+ int i;
+
+ pages = kzalloc_objs(*pages, nr_entries);
+ if (!pages)
+ return NULL;
+
+ /* Allocate two contiguous buffers for the invalidation queue. */
+ pages[0] = iommu_alloc_pages_node_sz(node, GFP_KERNEL, IQ_BUFFER_SIZE);
+ if (!pages[0])
+ goto free_pages;
+
+ pages[1] = iommu_alloc_pages_node_sz(node, GFP_KERNEL, IQ_BUFFER_SIZE);
+ if (!pages[1])
+ goto free_pages;
+
+ /* Allocate the required number of pages for the IOMMU metadata. */
+ for (i = IQ_BUFFERS_NUM; i < nr_entries; i++) {
+ vaddr = iommu_alloc_pages_node_sz(node, GFP_KERNEL, SZ_4K);
+ if (!vaddr)
+ goto free_pages;
+ pages[i] = vaddr;
+ }
+
+ return pages;
+free_pages:
+ for (i = 0; i < nr_entries; i++) {
+ if (!pages[i])
+ break;
+
+ iommu_free_pages(pages[i]);
+ }
+ kfree(pages);
+
+ return NULL;
+}
+
+static void populate_mt_pages(struct tdxc_pages *array)
+{
+ unsigned int nr_entries = array->nr_entries;
+ void **pages = array->pages;
+ u64 *entries = array->root;
+ int i;
+
+ /*
+ * Populate the parameter for the TDH_IOMMU_SETUP SEAMCALL according to
+ * the format defined in "Table 3.35: Structure of IOMMU_MT Parameter"
+ * of the ABI reference specification.
+ */
+ for (i = 0; i < nr_entries; i++) {
+ entries[i] = __pa(pages[i]);
+ if (i < IQ_BUFFERS_NUM)
+ entries[i] |= IQ_BUFFER_PAGES;
+ }
+}
+
+static struct tdxc_pages *tdxc_alloc_mt_pages(struct intel_iommu *iommu,
+ unsigned int nr_mt_pages)
+{
+ unsigned int nr_entries = nr_mt_pages + IQ_BUFFERS_NUM;
+ struct tdxc_pages *array;
+
+ if (!nr_mt_pages || nr_mt_pages > (PAGE_SIZE / sizeof(u64) - IQ_BUFFERS_NUM))
+ return NULL;
+
+ array = kzalloc_obj(*array);
+ if (!array)
+ return NULL;
+
+ array->root = iommu_alloc_pages_node_sz(iommu->node, GFP_KERNEL, SZ_4K);
+ if (!array->root)
+ goto free_array;
+
+ array->nr_entries = nr_entries;
+ array->pages = alloc_mt_pages(nr_entries, iommu->node);
+ if (!array->pages)
+ goto free_root;
+
+ populate_mt_pages(array);
+
+ return array;
+
+free_root:
+ iommu_free_pages(array->root);
+free_array:
+ kfree(array);
+ return NULL;
+}
+
static int intel_iommu_bringup_tdxc(struct intel_iommu *iommu, unsigned int nr_pages)
{
+ struct dmar_drhd_unit *drhd = iommu->drhd;
+ u64 r, tdx_iommu_id;
+
/*
* Nothing to do if the iommu doesn't support TDX extension or the
* DMA translation has not been enabled.
@@ -23,12 +139,46 @@ static int intel_iommu_bringup_tdxc(struct intel_iommu *iommu, unsigned int nr_p
if (!ecap_tdxcs(iommu->ecap) || !(iommu->gcmd & DMA_GCMD_TE))
return 0;
+ struct tdxc_pages *iommu_mt __free(free_mt_pages) =
+ tdxc_alloc_mt_pages(iommu, nr_pages);
+ if (!iommu_mt)
+ return -ENOMEM;
+
+ guard(mutex)(&iommu->tdx_lock);
+ r = tdh_iommu_setup(drhd->reg_base_addr, iommu_mt->root, &tdx_iommu_id);
+ /* TDX Extension is not supported on this iommu. Nothing to do. */
+ if ((r & TDX_SEAMCALL_STATUS_MASK) == TDX_OPERAND_INVALID)
+ return 0;
+ if (r) {
+ pr_err("%s: TDH.IOMMU.SETUP failed, status 0x%llx\n", iommu->name, r);
+ return -EFAULT;
+ }
+
+ iommu->tdx_iommu_id = tdx_iommu_id;
+ iommu->mt_pages = no_free_ptr(iommu_mt);
+
/* Bring-up is not complete yet; report as unsupported for now. */
return -EOPNOTSUPP;
}
static void intel_iommu_teardown_tdxc(struct intel_iommu *iommu)
{
+ u64 r;
+
+ guard(mutex)(&iommu->tdx_lock);
+
+ if (!iommu->mt_pages)
+ return;
+
+ r = tdh_iommu_clear(iommu->tdx_iommu_id);
+ if (r) {
+ pr_err("%s: TDH.IOMMU.CLEAR failed, status 0x%llx\n", iommu->name, r);
+ return;
+ }
+
+ free_mt_pages(iommu->mt_pages);
+ iommu->mt_pages = NULL;
+ iommu->tdx_iommu_id = 0;
}
void intel_tdxc_exit(void)
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 5/5] iommu/vt-d: Reserve MSB of domain ID space for TDX module
2026-09-15 7:42 [PATCH 0/5] iommu/vt-d: Introduce trusted DMA initialization support Lu Baolu
` (3 preceding siblings ...)
2026-09-15 7:42 ` [PATCH 4/5] iommu/vt-d: Add helpers to set up and tear down TDX extensions Lu Baolu
@ 2026-09-15 7:42 ` Lu Baolu
4 siblings, 0 replies; 6+ messages in thread
From: Lu Baolu @ 2026-09-15 7:42 UTC (permalink / raw)
To: iommu, x86, linux-coco, kvm
Cc: Joerg Roedel, Will Deacon, Robin Murphy, Jason Gunthorpe,
Kevin Tian, Dave Hansen, Kiryl Shutsemau, Rick Edgecombe,
yilun.xu, xiaoyao.li, Chao Gao, linux-kernel, Lu Baolu
When an Intel IOMMU is enabled for TDX Connect, the VT-d DID namespace
must be split so the TDX module can use the MSB-tagged half for trusted
DMA translations, while the host/VMM uses the lower half.
After TDH.IOMMU.SETUP succeeds, restrict host domain ID allocation to the
lower half of the DID space by capping max_domain_id to ndoms / 2. Before
applying the cap, verify that no allocated domain IDs already exist in
the upper half; if they do, abort bring-up and roll back the per-IOMMU
TDX setup.
On teardown, restore max_domain_id to the full DID range.
This matches Intel TDX Connect architecture requirements for IOTLB/DID
isolation between TEE and non-TEE translations.
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
---
drivers/iommu/intel/tdxc.c | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/drivers/iommu/intel/tdxc.c b/drivers/iommu/intel/tdxc.c
index b5dfdeeb23db..1ca03257d456 100644
--- a/drivers/iommu/intel/tdxc.c
+++ b/drivers/iommu/intel/tdxc.c
@@ -129,6 +129,7 @@ static struct tdxc_pages *tdxc_alloc_mt_pages(struct intel_iommu *iommu,
static int intel_iommu_bringup_tdxc(struct intel_iommu *iommu, unsigned int nr_pages)
{
+ unsigned long ndoms = cap_ndoms(iommu->cap);
struct dmar_drhd_unit *drhd = iommu->drhd;
u64 r, tdx_iommu_id;
@@ -144,6 +145,10 @@ static int intel_iommu_bringup_tdxc(struct intel_iommu *iommu, unsigned int nr_p
if (!iommu_mt)
return -ENOMEM;
+ guard(mutex)(&iommu->did_lock);
+ if (ida_find_first_range(&iommu->domain_ida, ndoms >> 1, ndoms - 1) > 0)
+ return -EBUSY;
+
guard(mutex)(&iommu->tdx_lock);
r = tdh_iommu_setup(drhd->reg_base_addr, iommu_mt->root, &tdx_iommu_id);
/* TDX Extension is not supported on this iommu. Nothing to do. */
@@ -154,17 +159,27 @@ static int intel_iommu_bringup_tdxc(struct intel_iommu *iommu, unsigned int nr_p
return -EFAULT;
}
+ /*
+ * Intel TDX Connect Architecture Specification, Section 2.2 Trusted DMA
+ *
+ * When IOMMU is enabled to support TDX Connect, the IOMMU restricts
+ * the VMM’s DID setting, reserving the MSB bit for the TDX module. The
+ * TDX module always sets this reserved bit on the trusted DMA table.
+ */
+ iommu->max_domain_id = ndoms >> 1;
iommu->tdx_iommu_id = tdx_iommu_id;
iommu->mt_pages = no_free_ptr(iommu_mt);
- /* Bring-up is not complete yet; report as unsupported for now. */
- return -EOPNOTSUPP;
+ pr_info("%s: trusted DMA for TEE/IO initialized\n", iommu->name);
+
+ return 0;
}
static void intel_iommu_teardown_tdxc(struct intel_iommu *iommu)
{
u64 r;
+ guard(mutex)(&iommu->did_lock);
guard(mutex)(&iommu->tdx_lock);
if (!iommu->mt_pages)
@@ -179,6 +194,7 @@ static void intel_iommu_teardown_tdxc(struct intel_iommu *iommu)
free_mt_pages(iommu->mt_pages);
iommu->mt_pages = NULL;
iommu->tdx_iommu_id = 0;
+ iommu->max_domain_id = cap_ndoms(iommu->cap);
}
void intel_tdxc_exit(void)
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread