* [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 6:34 ` Xu Yilun
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; 8+ 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 [flat|nested] 8+ 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 6:34 ` Xu Yilun
0 siblings, 0 replies; 8+ messages in thread
From: Xu Yilun @ 2026-10-15 6:34 UTC (permalink / raw)
To: Lu Baolu
Cc: iommu, x86, linux-coco, kvm, Jason Gunthorpe, Kevin Tian,
Kiryl Shutsemau, Rick Edgecombe, xiaoyao.li, Chao Gao, Binbin Wu,
linux-kernel
On Thu, Oct 15, 2026 at 09:07:02AM +0800, Lu Baolu wrote:
> Add SEAMCALL wrappers for TDH.IOMMU.SETUP and TDH.IOMMU.CLEAR.
We need this patch been reviewed by x86 reviewers. x86 convention
doesn't start with the implementation, so remove it.
We can start with a generic description of what the 2 SEAMCALLs are
doing in general and why. Maybe move some of your description in next
paragraph ahead.
>
> 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.
Feel free to drop it. Not that important.
>
> [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.
Other tdh_*() wrappers don't have the general descriptions. We all know
these are wrappers for some SEAMCALL leafs.
So if there is no special thing to note, remove it.
> + *
> + * 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)
IIUC, the void *root is a vague type, no type safty. Is it better we
define an explicit structure that mirrors the in-memory ABI? Something
like:
struct tdx_iommu_metadata {
u64 iq1_pa;
u64 iq2_pa;
DECLARE_FLEX_ARRAY(u64, mt_pa);
};
Or,
struct tdx_iommu_metadata {
u64 iq1_pa;
u64 iq2_pa;
u64 mt_pa[510];
};
static_assert(sizeof(struct tdx_iommu_metadata) == 4096);
> +{
> + 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 [flat|nested] 8+ 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 6:47 ` Xu Yilun
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, 1 reply; 8+ 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 [flat|nested] 8+ messages in thread* Re: [PATCH v2 2/4] x86/virt/tdx: Read global metadata for trusted IOMMU
2026-10-15 1:07 ` [PATCH v2 2/4] x86/virt/tdx: Read global metadata for trusted IOMMU Lu Baolu
@ 2026-10-15 6:47 ` Xu Yilun
0 siblings, 0 replies; 8+ messages in thread
From: Xu Yilun @ 2026-10-15 6:47 UTC (permalink / raw)
To: Lu Baolu
Cc: iommu, x86, linux-coco, kvm, Jason Gunthorpe, Kevin Tian,
Kiryl Shutsemau, Rick Edgecombe, xiaoyao.li, Chao Gao, Binbin Wu,
linux-kernel
On Thu, Oct 15, 2026 at 09:07:03AM +0800, Lu Baolu wrote:
> Add support for reading the global metadata of the trusted IOMMU.
>
> The trusted IOMMU feature is optionally enumerated via TDX_FEATURES0.
The feature name is not trusted IOMMU, the name in the spec is
TDX_CONNECT, but I believe people want a name with technical meaning,
maybe TDX TDISP? Open question for all reviewers.
> 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)
TDX_FEATURES0_TDX_CONNECT?
My actual point is that the name XX_TDX_CONNECT is good here.
^ permalink raw reply [flat|nested] 8+ 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; 8+ 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 [flat|nested] 8+ 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 8:31 ` Xu Yilun
3 siblings, 1 reply; 8+ 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 [flat|nested] 8+ 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 8:31 ` Xu Yilun
0 siblings, 0 replies; 8+ messages in thread
From: Xu Yilun @ 2026-10-15 8:31 UTC (permalink / raw)
To: Lu Baolu
Cc: iommu, x86, linux-coco, kvm, Jason Gunthorpe, Kevin Tian,
Kiryl Shutsemau, Rick Edgecombe, xiaoyao.li, Chao Gao, Binbin Wu,
linux-kernel
On Thu, Oct 15, 2026 at 09:07:05AM +0800, Lu Baolu wrote:
> 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
I think at this point we first need to reason about the connection between
IOMMU TDX mode and the PCIe link encryption. The device private memory
access can be placed in cover-letter as the big picture of TDISP.
I can see we may need to touch a bit about trusted DMA translation at
this phase, because the TDH.IOMMU.SETUP requires the VMM to provide 2
contiguous Invalidation Queue buffers, which leaks the trusted DMA
internals a bit. But I feel this should not be a first topic.
> 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
Maybe we don't need the configuration. Now we turn on all TDX features
that Linux knows about all the time.
Architecturally, TDX Connect is an default-off feature. But Linux enables
it regardlessly. It is questionable we then have a configuration that
disable the driver that supports the feature.
> + def_bool y
> + depends on TDX_HOST_SERVICES
> + depends on PCI_TSM
> + depends on INTEL_IOMMU
[..]
> +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;
Enable all IOMMUs regardless brings this problem. We need to try on
every IOMMU, check the TDX error code to see if it is not supported (then
skip) or failed. It's also sad to me that -EOPNOTSUPP is defined as the
API between tdx-host and intel iommu driver.
An alternative could be, bringing up one IOMMU when an associated
device requests link encryption. Then a no-tdx-support iommu fails the
encryption request naturally.
Open to discussion.
^ permalink raw reply [flat|nested] 8+ messages in thread