* [PATCH v2 1/4] x86/virt/tdx: Add SEAMCALL wrappers for IOMMU setup/clear
2026-10-15 1:07 [PATCH v2 0/4] coco/tdx-host: Introduce trusted DMA initialization support Lu Baolu
@ 2026-10-15 1:07 ` Lu Baolu
2026-10-15 1:28 ` sashiko-bot
2026-10-15 1:07 ` [PATCH v2 2/4] x86/virt/tdx: Read global metadata for trusted IOMMU Lu Baolu
` (2 subsequent siblings)
3 siblings, 1 reply; 7+ messages in thread
From: Lu Baolu @ 2026-10-15 1:07 UTC (permalink / raw)
To: iommu, x86, linux-coco, kvm
Cc: Jason Gunthorpe, Kevin Tian, Kiryl Shutsemau, Rick Edgecombe,
yilun.xu, xiaoyao.li, Chao Gao, Binbin Wu, 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.
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 Xu 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..5d31c1b0890a 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.
+ *
+ * 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.
+ */
+u64 tdh_iommu_setup(u64 reg_base, void *root, u64 *tdx_iommu_id)
+{
+ 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;
+}
+EXPORT_SYMBOL_FOR_MODULES(tdh_iommu_setup, "tdx-host");
+
+/*
+ * 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;
+}
+EXPORT_SYMBOL_FOR_MODULES(tdh_iommu_clear, "tdx-host");
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH v2 1/4] x86/virt/tdx: Add SEAMCALL wrappers for IOMMU setup/clear
2026-10-15 1:07 ` [PATCH v2 1/4] x86/virt/tdx: Add SEAMCALL wrappers for IOMMU setup/clear Lu Baolu
@ 2026-10-15 1:28 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-10-15 1:28 UTC (permalink / raw)
To: Lu Baolu, kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] x86/virt/tdx: missing clflush on IOMMU parameter pages
--
commit ea38aa1a74969b72895a018349277b8a5b88cfb3
Author: Lu Baolu <baolu.lu@linux.intel.com>
x86/virt/tdx: Add SEAMCALL wrappers for IOMMU setup/clear
This commit adds SEAMCALL wrappers for TDH.IOMMU.SETUP and TDH.IOMMU.CLEAR.
These functions transition an IOMMU into and out of Secure TDX Mode to
provide the trusted foundation required before enabling TEE-IO security
protocols.
> diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
> index 1b9ff749dd8e0..5d31c1b0890ae 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.
> + *
> + * 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.
> + */
> +u64 tdh_iommu_setup(u64 reg_base, void *root, u64 *tdx_iommu_id)
> +{
> + struct tdx_module_args args = {
> + .rcx = reg_base,
> + .rdx = __pa(root),
> + };
[Severity: High]
Does the root page need to be flushed from the CPU cache before being passed
to the TDX module?
The TDX module accesses memory using physical addresses. If CPU-cached,
unflushed memory pages are passed to the module during trusted DMA
initialization, it can lead to cache paradoxes and machine checks,
potentially causing host kernel panics.
Other TDX page-adding wrappers typically ensure pages are flushed. While the
caller is responsible for populating the parameter page, the caller introduced
in the follow-up patch (tdx_iommu_setup in iommu.c) allocates and populates
the metadata pages but does not appear to perform a cache flush either.
Should tdh_iommu_setup() perform the flush, or does the caller need to be
updated to handle it?
> + u64 ret;
> +
> + /*
> + * Don't loop forever:
> + *
> + * - TDX_INTERRUPTED_RESUMABLE guarantees forward progress between
> + * calls.
> + * - On TDX_INTERRUPTED_RESUMABLE, all registers except RAX remain
> + * unchanged.
> + */
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261015010705.1973284-1-baolu.lu@linux.intel.com?part=1
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 2/4] x86/virt/tdx: Read global metadata for trusted IOMMU
2026-10-15 1:07 [PATCH v2 0/4] coco/tdx-host: Introduce trusted DMA initialization support Lu Baolu
2026-10-15 1:07 ` [PATCH v2 1/4] x86/virt/tdx: Add SEAMCALL wrappers for IOMMU setup/clear Lu Baolu
@ 2026-10-15 1:07 ` Lu Baolu
2026-10-15 1:07 ` [PATCH v2 3/4] iommu/vt-d: Add an API for tdx-host when enabling trusted DMA Lu Baolu
2026-10-15 1:07 ` [PATCH v2 4/4] coco/tdx-host: Initialize trusted DMA on all capable IOMMUs Lu Baolu
3 siblings, 0 replies; 7+ messages in thread
From: Lu Baolu @ 2026-10-15 1:07 UTC (permalink / raw)
To: iommu, x86, linux-coco, kvm
Cc: Jason Gunthorpe, Kevin Tian, Kiryl Shutsemau, Rick Edgecombe,
yilun.xu, xiaoyao.li, Chao Gao, Binbin Wu, 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 Xu 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 related [flat|nested] 7+ messages in thread* [PATCH v2 3/4] iommu/vt-d: Add an API for tdx-host when enabling trusted DMA
2026-10-15 1:07 [PATCH v2 0/4] coco/tdx-host: Introduce trusted DMA initialization support Lu Baolu
2026-10-15 1:07 ` [PATCH v2 1/4] x86/virt/tdx: Add SEAMCALL wrappers for IOMMU setup/clear Lu Baolu
2026-10-15 1:07 ` [PATCH v2 2/4] x86/virt/tdx: Read global metadata for trusted IOMMU Lu Baolu
@ 2026-10-15 1:07 ` Lu Baolu
2026-10-15 1:07 ` [PATCH v2 4/4] coco/tdx-host: Initialize trusted DMA on all capable IOMMUs Lu Baolu
3 siblings, 0 replies; 7+ messages in thread
From: Lu Baolu @ 2026-10-15 1:07 UTC (permalink / raw)
To: iommu, x86, linux-coco, kvm
Cc: Jason Gunthorpe, Kevin Tian, Kiryl Shutsemau, Rick Edgecombe,
yilun.xu, xiaoyao.li, Chao Gao, Binbin Wu, linux-kernel, Lu Baolu
TDX Connect requires each Intel IOMMU that supports the trusted DMA
extension (ECAP.TDXCS) to be transitioned into Secure TDX Mode by the
TDX module. The transition itself is driven by TDH.IOMMU.SETUP, which
is issued by the tdx-host driver. The VT-d driver owns the hardware
state that the TDX module depends on, so it must validate that state
and adjust its resource management around it.
Add dmar_tdxcs_iommu_init_all() and dmar_tdxcs_iommu_release() as the
interface between the two. dmar_tdxcs_iommu_init_all() walks the active
DMAR units, and for every unit advertising ECAP.TDXCS it:
- Verifies the preconditions that TDH.IOMMU.SETUP checks: translation
enabled, queued invalidation enabled, and scalable mode in use. This
allows a misconfiguration to be reported as -EINVAL rather than as
an opaque TDX_IOMMU_INVALID_STATE status later on.
- Splits the domain ID namespace. When an IOMMU runs in Secure TDX
Mode, the TDX module owns the MSB-tagged half of the DID space and
always sets that bit for trusted DMA translations, so the host must
confine itself to the lower half.
- Invokes the caller-supplied setup() callback, which performs the
actual TDX module interaction, and marks the unit as being in TDX
mode on success. On failure the DID cap is restored and the error is
propagated.
dmar_tdxcs_iommu_release() reverses the DID restriction and clears the
TDX mode flag for a single unit.
The DMAR unit is passed to the callback so the tdx-host driver can
obtain the register base address, which is how the TDX module
identifies an IOMMU, and the NUMA node, which it needs in order to
allocate the metadata pages local to that IOMMU.
A DMAR unit in TDX mode cannot be hot-removed, because the TDX module
holds references to its resources. Reject such attempts with -EBUSY.
Conversely, an IOMMU that is hot-added after bring-up is not enrolled
into Secure TDX Mode because no platform currently supports both TDX
Connect and DMAR hotplug, so the flow cannot be validated on hardware.
TDH.IOMMU.SETUP requires that no enhanced command is in progress on
the target IOMMU. Block enhanced command submission on the target
IOMMU for the duration of TDH.IOMMU.SETUP.
The symbols are exported only to the tdx-host module.
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
---
drivers/iommu/intel/iommu.h | 6 +++
include/linux/dmar.h | 5 ++
drivers/iommu/intel/dmar.c | 103 ++++++++++++++++++++++++++++++++++++
drivers/iommu/intel/iommu.c | 19 +++++++
4 files changed, 133 insertions(+)
diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h
index 23dbe6c24439..a334226c4324 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)
@@ -689,6 +690,11 @@ struct intel_iommu {
u64 ecmdcap[DMA_MAX_NUM_ECMDCAP];
u32 gcmd; /* Holds TE, EAFL. Don't need SRTP, SFL, WBF */
raw_spinlock_t register_lock; /* protect register handling */
+ /*
+ * Enhanced command submission is rejected due to TDH.IOMMU.SETUP.
+ * Protected by register_lock.
+ */
+ bool ecmd_blocked;
int seq_id; /* sequence id of the iommu */
int agaw; /* agaw of this iommu */
int msagaw; /* max sagaw of this iommu */
diff --git a/include/linux/dmar.h b/include/linux/dmar.h
index 63e35df2cef4..4205945f099f 100644
--- a/include/linux/dmar.h
+++ b/include/linux/dmar.h
@@ -47,6 +47,8 @@ struct dmar_drhd_unit {
u8 ignored:1; /* ignore drhd */
u8 include_all:1;
u8 gfx_dedicated:1; /* graphic dedicated */
+ bool tdx_mode; /* in Secure TDX Mode */
+ int node;
struct intel_iommu *iommu;
};
@@ -87,6 +89,9 @@ extern struct list_head dmar_drhd_units;
dmar_rcu_check()) \
if (i=drhd->iommu, 0) {} else
+int dmar_tdxcs_iommu_init_all(int (*setup)(struct dmar_drhd_unit *));
+void dmar_tdxcs_iommu_release(struct dmar_drhd_unit *drhd);
+
static inline bool dmar_rcu_check(void)
{
return rwsem_is_locked(&dmar_global_lock) ||
diff --git a/drivers/iommu/intel/dmar.c b/drivers/iommu/intel/dmar.c
index ba675b08cd20..c0eb2e3d94ef 100644
--- a/drivers/iommu/intel/dmar.c
+++ b/drivers/iommu/intel/dmar.c
@@ -431,6 +431,7 @@ static int dmar_parse_one_drhd(struct acpi_dmar_header *header, void *arg)
/* The size of the register set is 2 ^ N 4 KB pages. */
dmaru->reg_size = 1UL << (drhd->size + 12);
dmaru->include_all = drhd->flags & 0x1; /* BIT0: INCLUDE_ALL */
+ dmaru->node = NUMA_NO_NODE;
dmaru->devices = dmar_alloc_dev_scope((void *)(drhd + 1),
((void *)drhd) + drhd->header.length,
&dmaru->devices_cnt);
@@ -499,6 +500,7 @@ static int dmar_parse_one_rhsa(struct acpi_dmar_header *header, void *arg)
if (node != NUMA_NO_NODE && !node_online(node))
node = NUMA_NO_NODE;
+ drhd->node = node;
drhd->iommu->node = node;
return 0;
}
@@ -2296,6 +2298,10 @@ static int dmar_hp_remove_drhd(struct acpi_dmar_header *header, void *arg)
return -EBUSY;
}
+ /* The TDX module holds references to this unit's resources. */
+ if (dmaru->tdx_mode)
+ return -EBUSY;
+
ret = dmar_ir_hotplug(dmaru, false);
if (ret == 0)
ret = dmar_iommu_hotplug(dmaru, false);
@@ -2468,3 +2474,100 @@ bool dmar_platform_optin(void)
return ret;
}
EXPORT_SYMBOL_GPL(dmar_platform_optin);
+
+static int dmar_tdxcs_block_ecmd(struct intel_iommu *iommu)
+{
+ unsigned long flags;
+ int ret = 0;
+
+ if (!cap_ecmds(iommu->cap))
+ return 0;
+
+ raw_spin_lock_irqsave(&iommu->register_lock, flags);
+ if (readq(iommu->reg + DMAR_ECRSP_REG) & DMA_ECMD_ECRSP_IP)
+ ret = -EBUSY;
+ else
+ iommu->ecmd_blocked = true;
+ raw_spin_unlock_irqrestore(&iommu->register_lock, flags);
+
+ return ret;
+}
+
+static void dmar_tdxcs_unblock_ecmd(struct intel_iommu *iommu)
+{
+ unsigned long flags;
+
+ raw_spin_lock_irqsave(&iommu->register_lock, flags);
+ iommu->ecmd_blocked = false;
+ raw_spin_unlock_irqrestore(&iommu->register_lock, flags);
+}
+
+int dmar_tdxcs_iommu_init_all(int (*setup)(struct dmar_drhd_unit *))
+{
+ struct dmar_drhd_unit *drhd;
+ int ret;
+
+ if (!intel_iommu_enabled)
+ return -EOPNOTSUPP;
+
+ guard(rwsem_write)(&dmar_global_lock);
+
+ for_each_active_drhd_unit(drhd) {
+ struct intel_iommu *iommu = drhd->iommu;
+ unsigned long ndoms;
+
+ if (!ecap_tdxcs(iommu->ecap))
+ continue;
+
+ /*
+ * TDH.IOMMU.SETUP requires the unit not to be in Secure TDX
+ * Mode yet, with DMA translation, queued invalidation and
+ * scalable mode enabled. Catch these here so the error is
+ * -EINVAL rather than an opaque TDX_IOMMU_INVALID_STATE.
+ */
+ if (drhd->tdx_mode || !(iommu->gcmd & DMA_GCMD_TE) ||
+ !iommu->qi || !sm_supported(iommu))
+ return -EINVAL;
+
+ ndoms = cap_ndoms(iommu->cap);
+ scoped_guard(mutex, &iommu->did_lock) {
+ if (ida_find_first_range(&iommu->domain_ida, ndoms >> 1,
+ ndoms - 1) > 0)
+ return -EBUSY;
+ iommu->max_domain_id = ndoms >> 1;
+ }
+
+ ret = dmar_tdxcs_block_ecmd(iommu);
+ if (!ret) {
+ ret = setup(drhd);
+ dmar_tdxcs_unblock_ecmd(iommu);
+ }
+
+ if (ret) {
+ scoped_guard(mutex, &iommu->did_lock)
+ iommu->max_domain_id = ndoms;
+
+ /* Not covered by this TDX module. Skip it and keep going. */
+ if (ret == -EOPNOTSUPP)
+ continue;
+
+ return ret;
+ }
+
+ drhd->tdx_mode = true;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_FOR_MODULES(dmar_tdxcs_iommu_init_all, "tdx-host");
+
+void dmar_tdxcs_iommu_release(struct dmar_drhd_unit *drhd)
+{
+ struct intel_iommu *iommu = drhd->iommu;
+
+ guard(rwsem_write)(&dmar_global_lock);
+ scoped_guard(mutex, &iommu->did_lock)
+ iommu->max_domain_id = cap_ndoms(iommu->cap);
+ drhd->tdx_mode = false;
+}
+EXPORT_SYMBOL_FOR_MODULES(dmar_tdxcs_iommu_release, "tdx-host");
diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index 2e3b3ab216f8..37c8c375c020 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -2189,6 +2189,15 @@ int dmar_iommu_hotplug(struct dmar_drhd_unit *dmaru, bool insert)
return -EINVAL;
if (insert) {
+ /*
+ * A hot-added IOMMU is not enrolled into Secure TDX Mode.
+ * Currently, there is no platform that supports both TDX Connect and
+ * DMAR hotplug, so enrolling on hotplug cannot be validated on real
+ * hardware. Log this limitation to make it visible.
+ */
+ if (ecap_tdxcs(iommu->ecap))
+ pr_info("Trusted DMA for TEE is not enabled on hot-added IOMMU %s\n",
+ iommu->name);
ret = intel_iommu_add(dmaru);
} else {
disable_dmar_iommu(iommu);
@@ -4227,6 +4236,16 @@ int ecmd_submit_sync(struct intel_iommu *iommu, u8 ecmd, u64 oa, u64 ob)
raw_spin_lock_irqsave(&iommu->register_lock, flags);
+ /*
+ * TDH.IOMMU.SETUP requires that no enhanced command is in
+ * progress. This may be called from atomic context (e.g. perf
+ * pmu::start/stop), so don't sleep; just reject the request.
+ */
+ if (iommu->ecmd_blocked) {
+ ret = -EBUSY;
+ goto err;
+ }
+
res = readq(iommu->reg + DMAR_ECRSP_REG);
if (res & DMA_ECMD_ECRSP_IP) {
ret = -EBUSY;
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v2 4/4] coco/tdx-host: Initialize trusted DMA on all capable IOMMUs
2026-10-15 1:07 [PATCH v2 0/4] coco/tdx-host: Introduce trusted DMA initialization support Lu Baolu
` (2 preceding siblings ...)
2026-10-15 1:07 ` [PATCH v2 3/4] iommu/vt-d: Add an API for tdx-host when enabling trusted DMA Lu Baolu
@ 2026-10-15 1:07 ` Lu Baolu
2026-10-15 1:31 ` sashiko-bot
3 siblings, 1 reply; 7+ messages in thread
From: Lu Baolu @ 2026-10-15 1:07 UTC (permalink / raw)
To: iommu, x86, linux-coco, kvm
Cc: Jason Gunthorpe, Kevin Tian, Kiryl Shutsemau, Rick Edgecombe,
yilun.xu, xiaoyao.li, Chao Gao, Binbin Wu, linux-kernel, Lu Baolu
Assigning a device to a TD for private memory access requires that DMA
from that device be translated by structures the TDX module controls.
An Intel IOMMU that implements the trusted DMA extension provides a
parallel set of translation and invalidation resources for this purpose.
These become usable only after the TDX module has placed the IOMMU into
TDX mode, at which point the trusted resources belong exclusively to the
TDX module, while the host retains control of the regular translation
path. The transition is performed by TDH.IOMMU.SETUP, which takes the
register base address of the target IOMMU together with a parameter
page describing memory the host has allocated for the TDX module to
manage trusted DMA on that unit.
Perform this initialization from the tdx-host driver by walking every
IOMMU unit through dmar_tdxcs_iommu_init() and initialize the ones whose
IOMMU implements the extension.
For each such unit the driver builds the IOMMU_MT parameter layout that
TDH.IOMMU.SETUP expects: two contiguous buffers for the trusted
invalidation queue, followed by the number of metadata pages the TDX
module reports in its global metadata. The pages are allocated on the
NUMA node of the IOMMU. The root page is passed to the TDX module, which
returns an identifier naming the initialized IOMMU. That identifier and
the pages backing it are tracked so the state can be released later.
Releasing the state is the reverse: TDH.IOMMU.CLEAR tears down the
trusted DMA resources established by TDH.IOMMU.SETUP, after which the
metadata pages can be freed and the VT-d driver told to lift the domain
ID restriction applied during initialization. If TDH.IOMMU.CLEAR fails
the pages are not freed, as the TDX module may still hold references to
them.
Rename tdx-host.c to main.c and build the module from a tdx-host-y
object list instead. New functionality can then be added as separate
source files instead of putting everything in a single file.
The code is built only when its dependencies are present, gated by
CONFIG_TDX_CONNECT.
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
---
drivers/virt/coco/tdx-host/Kconfig | 6 +
drivers/virt/coco/tdx-host/Makefile | 3 +
drivers/virt/coco/tdx-host/private.h | 33 +++
drivers/virt/coco/tdx-host/iommu.c | 255 ++++++++++++++++++
.../virt/coco/tdx-host/{tdx-host.c => main.c} | 21 +-
5 files changed, 317 insertions(+), 1 deletion(-)
create mode 100644 drivers/virt/coco/tdx-host/private.h
create mode 100644 drivers/virt/coco/tdx-host/iommu.c
rename drivers/virt/coco/tdx-host/{tdx-host.c => main.c} (92%)
diff --git a/drivers/virt/coco/tdx-host/Kconfig b/drivers/virt/coco/tdx-host/Kconfig
index 57d0c01a4357..62d91d4b41fa 100644
--- a/drivers/virt/coco/tdx-host/Kconfig
+++ b/drivers/virt/coco/tdx-host/Kconfig
@@ -4,3 +4,9 @@ config TDX_HOST_SERVICES
select FW_LOADER
select FW_UPLOAD
default m
+
+config TDX_CONNECT
+ def_bool y
+ depends on TDX_HOST_SERVICES
+ depends on PCI_TSM
+ depends on INTEL_IOMMU
diff --git a/drivers/virt/coco/tdx-host/Makefile b/drivers/virt/coco/tdx-host/Makefile
index e61e749a8dff..67bfe5a6e290 100644
--- a/drivers/virt/coco/tdx-host/Makefile
+++ b/drivers/virt/coco/tdx-host/Makefile
@@ -1 +1,4 @@
obj-$(CONFIG_TDX_HOST_SERVICES) += tdx-host.o
+
+tdx-host-y := main.o
+tdx-host-$(CONFIG_TDX_CONNECT) += iommu.o
diff --git a/drivers/virt/coco/tdx-host/private.h b/drivers/virt/coco/tdx-host/private.h
new file mode 100644
index 000000000000..131c1782cbc1
--- /dev/null
+++ b/drivers/virt/coco/tdx-host/private.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2026 Intel Corporation */
+#ifndef __TDX_HOST_PRIVATE_H
+#define __TDX_HOST_PRIVATE_H
+
+#include <linux/dmar.h>
+
+/*
+ * 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 tdx_iommu_pages {
+ u64 *root;
+ void **pages;
+ unsigned int nr_entries;
+};
+
+struct tdx_iommu_state {
+ struct dmar_drhd_unit *drhd;
+ u64 tdx_iommu_id;
+ struct tdx_iommu_pages *mt_pages;
+};
+
+#ifdef CONFIG_TDX_CONNECT
+int tdx_iommu_enable_all(struct device *dev);
+#else
+static inline int tdx_iommu_enable_all(struct device *dev)
+{
+ return -EINVAL;
+}
+#endif /* CONFIG_TDX_CONNECT */
+#endif /* __TDX_HOST_PRIVATE_H */
diff --git a/drivers/virt/coco/tdx-host/iommu.c b/drivers/virt/coco/tdx-host/iommu.c
new file mode 100644
index 000000000000..a80e57bf26a1
--- /dev/null
+++ b/drivers/virt/coco/tdx-host/iommu.c
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TDX IOMMU Extensions support
+ *
+ * Copyright (C) 2026 Intel Corporation
+ */
+#define pr_fmt(fmt) "tdx-host: " fmt
+
+#include <linux/pci.h>
+#include <asm/vmx.h>
+#include <asm/tdx.h>
+
+#include "private.h"
+
+/* These describe the trusted invalidation queue buffers. */
+#define IQ_BUFFERS_NUM 2
+#define IQ_BUFFER_PAGES 2
+#define IQ_BUFFER_SIZE SZ_8K
+
+/* The IOMMU_MT root page holds one u64 entry per metadata page. */
+#define MT_ROOT_SIZE SZ_4K
+#define MT_MAX_ENTRIES (MT_ROOT_SIZE / sizeof(u64))
+
+static void *tdx_iommu_alloc_pages(int node, size_t size)
+{
+ struct folio *folio;
+
+ if (node == NUMA_NO_NODE)
+ node = numa_mem_id();
+
+ folio = __folio_alloc_node(GFP_KERNEL | __GFP_ZERO, get_order(size), node);
+ if (!folio)
+ return NULL;
+
+ return folio_address(folio);
+}
+
+static void tdx_iommu_free_pages(void *virt)
+{
+ if (!virt)
+ return;
+
+ folio_put(virt_to_folio(virt));
+}
+
+static void free_mt_pages(struct tdx_iommu_pages *array)
+{
+ unsigned int i;
+
+ if (!array)
+ return;
+
+ for (i = 0; i < array->nr_entries; i++)
+ tdx_iommu_free_pages(array->pages[i]);
+
+ tdx_iommu_free_pages(array->root);
+ kfree(array->pages);
+ kfree(array);
+}
+
+DEFINE_FREE(free_mt_pages, struct tdx_iommu_pages *, free_mt_pages(_T))
+
+static void **alloc_mt_pages(unsigned int nr_entries, int node)
+{
+ unsigned int i;
+ void **pages;
+ void *vaddr;
+
+ pages = kzalloc_objs(*pages, nr_entries);
+ if (!pages)
+ return NULL;
+
+ /* Allocate two contiguous buffers for the invalidation queue. */
+ pages[0] = tdx_iommu_alloc_pages(node, IQ_BUFFER_SIZE);
+ if (!pages[0])
+ goto free_pages;
+
+ pages[1] = tdx_iommu_alloc_pages(node, 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 = tdx_iommu_alloc_pages(node, SZ_4K);
+ if (!vaddr)
+ goto free_pages;
+ pages[i] = vaddr;
+ }
+
+ return pages;
+
+free_pages:
+ for (i = 0; i < nr_entries; i++)
+ tdx_iommu_free_pages(pages[i]);
+ kfree(pages);
+
+ return NULL;
+}
+
+static void populate_mt_pages(struct tdx_iommu_pages *array)
+{
+ unsigned int nr_entries = array->nr_entries;
+ void **pages = array->pages;
+ u64 *entries = array->root;
+ unsigned 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 tdx_iommu_pages *
+tdx_iommu_alloc_mt_pages(struct dmar_drhd_unit *drhd, unsigned int nr_mt_pages)
+{
+ unsigned int nr_entries = nr_mt_pages + IQ_BUFFERS_NUM;
+ struct tdx_iommu_pages *array;
+
+ if (!nr_mt_pages || nr_mt_pages > MT_MAX_ENTRIES - IQ_BUFFERS_NUM)
+ return NULL;
+
+ array = kzalloc_obj(*array);
+ if (!array)
+ return NULL;
+
+ array->root = tdx_iommu_alloc_pages(drhd->node, MT_ROOT_SIZE);
+ if (!array->root)
+ goto free_array;
+
+ array->nr_entries = nr_entries;
+ array->pages = alloc_mt_pages(nr_entries, drhd->node);
+ if (!array->pages)
+ goto free_root;
+
+ populate_mt_pages(array);
+
+ return array;
+
+free_root:
+ tdx_iommu_free_pages(array->root);
+free_array:
+ kfree(array);
+ return NULL;
+}
+
+static DEFINE_XARRAY(tdx_iommu_xa);
+
+static int tdx_iommu_clear(struct tdx_iommu_state *iommu_state)
+{
+ u64 r;
+
+ if (!iommu_state->mt_pages)
+ return 0;
+
+ r = tdh_iommu_clear(iommu_state->tdx_iommu_id);
+ if (r) {
+ pr_err("TDH.IOMMU.CLEAR failed, regbase 0x%llx tdx_iommu_id 0x%llx status 0x%llx\n",
+ iommu_state->drhd->reg_base_addr, iommu_state->tdx_iommu_id, r);
+ return -EFAULT;
+ }
+
+ free_mt_pages(iommu_state->mt_pages);
+ iommu_state->mt_pages = NULL;
+ iommu_state->tdx_iommu_id = 0;
+
+ return 0;
+}
+
+static int tdx_iommu_setup(struct dmar_drhd_unit *drhd)
+{
+ const struct tdx_sys_info *tdx_sysinfo = tdx_get_sysinfo();
+ unsigned int mt_page_count = tdx_sysinfo->tdx_connect.iommu_mt_page_count;
+ struct tdx_iommu_state *state;
+ u64 r, tdx_iommu_id;
+ int ret;
+
+ struct tdx_iommu_state *iommu_state __free(kfree) = kzalloc_obj(*iommu_state);
+ if (!iommu_state)
+ return -ENOMEM;
+
+ struct tdx_iommu_pages *iommu_mt __free(free_mt_pages) =
+ tdx_iommu_alloc_mt_pages(drhd, mt_page_count);
+ if (!iommu_mt)
+ return -ENOMEM;
+
+ 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 -EOPNOTSUPP;
+ if (r) {
+ pr_err("TDH.IOMMU.SETUP failed, regbase 0x%llx status 0x%llx\n",
+ drhd->reg_base_addr, r);
+ return -EFAULT;
+ }
+
+ state = no_free_ptr(iommu_state);
+ state->drhd = drhd;
+ state->tdx_iommu_id = tdx_iommu_id;
+ state->mt_pages = no_free_ptr(iommu_mt);
+ ret = xa_insert(&tdx_iommu_xa, (unsigned long)tdx_iommu_id, state, GFP_KERNEL);
+ if (ret) {
+ WARN_ON(tdx_iommu_clear(state));
+ kfree(state);
+ return ret;
+ }
+
+ pr_info("Trusted DMA for TEE initialized, regbase 0x%llx\n",
+ drhd->reg_base_addr);
+
+ return 0;
+}
+
+static void tdx_iommu_disable_all(void)
+{
+ struct tdx_iommu_state *iommu_state;
+ unsigned long tdx_iommu_id;
+
+ xa_for_each(&tdx_iommu_xa, tdx_iommu_id, iommu_state) {
+ /*
+ * Clear failed: the IOMMU remains in Secure TDX Mode. Keep
+ * the entry and leave drhd->tdx_mode set so the host stays
+ * confined to the lower half of the DID space.
+ */
+ if (tdx_iommu_clear(iommu_state))
+ continue;
+
+ xa_erase(&tdx_iommu_xa, tdx_iommu_id);
+ dmar_tdxcs_iommu_release(iommu_state->drhd);
+ kfree(iommu_state);
+ }
+}
+
+static void tdx_iommu_disable_all_action(void *unused)
+{
+ tdx_iommu_disable_all();
+}
+
+int tdx_iommu_enable_all(struct device *dev)
+{
+ int ret;
+
+ ret = dmar_tdxcs_iommu_init_all(tdx_iommu_setup);
+ if (ret) {
+ tdx_iommu_disable_all();
+ return ret;
+ }
+
+ return devm_add_action_or_reset(dev, tdx_iommu_disable_all_action, NULL);
+}
diff --git a/drivers/virt/coco/tdx-host/tdx-host.c b/drivers/virt/coco/tdx-host/main.c
similarity index 92%
rename from drivers/virt/coco/tdx-host/tdx-host.c
rename to drivers/virt/coco/tdx-host/main.c
index e8ed8dfa526e..018bdc475715 100644
--- a/drivers/virt/coco/tdx-host/tdx-host.c
+++ b/drivers/virt/coco/tdx-host/main.c
@@ -14,6 +14,8 @@
#include <asm/seamldr.h>
#include <asm/tdx.h>
+#include "private.h"
+
static const struct x86_cpu_id tdx_host_ids[] = {
X86_MATCH_FEATURE(X86_FEATURE_TDX_HOST_PLATFORM, NULL),
{}
@@ -202,9 +204,26 @@ static int seamldr_init(struct device *dev)
return devm_add_action_or_reset(dev, seamldr_deinit, tdx_fwl);
}
+static int tdx_connect_init(struct device *dev)
+{
+ const struct tdx_sys_info *tdx_sysinfo = tdx_get_sysinfo();
+
+ if (!IS_ENABLED(CONFIG_TDX_CONNECT) || !tdx_sysinfo ||
+ !(tdx_sysinfo->features.tdx_features0 & TDX_FEATURES0_TDXCONNECT))
+ return 0;
+
+ return tdx_iommu_enable_all(dev);
+}
+
static int tdx_host_probe(struct faux_device *fdev)
{
- return seamldr_init(&fdev->dev);
+ int ret;
+
+ ret = seamldr_init(&fdev->dev);
+ if (ret)
+ return ret;
+
+ return tdx_connect_init(&fdev->dev);
}
static const struct faux_device_ops tdx_host_ops = {
--
2.43.0
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH v2 4/4] coco/tdx-host: Initialize trusted DMA on all capable IOMMUs
2026-10-15 1:07 ` [PATCH v2 4/4] coco/tdx-host: Initialize trusted DMA on all capable IOMMUs Lu Baolu
@ 2026-10-15 1:31 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-10-15 1:31 UTC (permalink / raw)
To: Lu Baolu, kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] virt/coco/tdx-host: desynchronized host/hardware state on xa_insert failure
--
commit 83cda44603ec97cb10640905999d9a617eced100
Author: Lu Baolu <baolu.lu@linux.intel.com>
coco/tdx-host: Initialize trusted DMA on all capable IOMMUs
Assigning a device to a TD for private memory access requires that DMA
from that device be translated by structures the TDX module controls.
An Intel IOMMU that implements the trusted DMA extension provides a
parallel set of translation and invalidation resources for this purpose.
> diff --git a/drivers/virt/coco/tdx-host/iommu.c b/drivers/virt/coco/tdx-host/iommu.c
> new file mode 100644
> index 0000000000000..a80e57bf26a12
> --- /dev/null
> +++ b/drivers/virt/coco/tdx-host/iommu.c
[ ... ]
> +static int tdx_iommu_setup(struct dmar_drhd_unit *drhd)
> +{
[ ... ]
> + state = no_free_ptr(iommu_state);
> + state->drhd = drhd;
> + state->tdx_iommu_id = tdx_iommu_id;
> + state->mt_pages = no_free_ptr(iommu_mt);
> + ret = xa_insert(&tdx_iommu_xa, (unsigned long)tdx_iommu_id, state, GFP_KERNEL);
> + if (ret) {
> + WARN_ON(tdx_iommu_clear(state));
> + kfree(state);
> + return ret;
> + }
[Severity: High]
If xa_insert() fails and tdx_iommu_clear(state) also fails, this code
unconditionally frees state and returns an error. Does this leave the
hardware state desynchronized?
When this function returns an error, the caller dmar_tdxcs_iommu_init_all() in
drivers/iommu/intel/dmar.c restores iommu->max_domain_id to its full un-halved
range and leaves drhd->tdx_mode = false:
ret = setup(drhd);
...
if (ret) {
scoped_guard(mutex, &iommu->did_lock)
iommu->max_domain_id = ndoms;
...
return ret;
}
Because tdx_iommu_clear() failed, the physical hardware is permanently left in
Secure TDX Mode, but the software state now believes it is not. Will this
cause hardware faults or fatal domain ID conflicts when the host attempts to
use domain IDs that now belong exclusively to TDX?
Unlike tdx_iommu_disable_all() which safely detects a clear failure and leaves
tdx_mode true to confine the host to the lower half of the DID space, this
error path completely loses track of the hardware state, breaking isolation.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261015010705.1973284-1-baolu.lu@linux.intel.com?part=4
^ permalink raw reply [flat|nested] 7+ messages in thread