Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [PATCH v2 0/4] coco/tdx-host: Introduce trusted DMA initialization support
@ 2026-10-15  1:07 Lu Baolu
  2026-10-15  1:07 ` [PATCH v2 1/4] x86/virt/tdx: Add SEAMCALL wrappers for IOMMU setup/clear Lu Baolu
                   ` (3 more replies)
  0 siblings, 4 replies; 12+ 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

Hi,

This series adds trusted DMA initialization support to the tdx-host and
intel-iommu drivers, as groundwork for TEE I/O trusted DMA support.

Following Jason Gunthorpe's suggestion on v1, this version moves the
host/TDX-module interactions out of the IOMMU driver and into the TDX
host (TSM) driver. It is posted to collect review feedback from TDX, TSM
and IOMMU stakeholders; it is not yet intended for merging.

This is part of the effort to implement the TDX PCIe Link Encryption
feature, which includes the following components:

[ ] TDX module extensions initialization, posted in [3]
[X] Introduce trusted DMA initialization support
[ ] SEAMCALL helpers for TDX PCIe Link Encryption
[ ] PCIe Link Encryption Establishment via TDX platform services

Background:

Intel VT-d hardware gains a second set of DMA translation structures,
owned by the TDX module and used only for devices assigned to
confidential guests.

From the TDX Connect architecture perspective, VT-d is enhanced with:

  1) Trusted DMA translation root table
     - A second root-table address register, restricted to SEAM SAI.
     - Programmed by the TDX module with the root of TDX-managed DMA
       translation tables for TD-assigned devices.

  2) Trusted invalidation queue
     - A second invalidation-queue address register plus dedicated
       head/tail registers, also restricted to SEAM SAI.

  3) IOTLB / DID isolation
     - When TDX Connect is enabled, the TDX module owns the half of the
       domain ID space with the MSB set; the VMM must confine itself to
       the lower half.

This posting covers initialization only: the host hands the TDX module
the memory it needs and asks it to take over trusted DMA management on
each capable IOMMU. The non-trusted DMA path is unchanged, except that
the host's usable domain ID space on TDX-capable IOMMUs is halved. The
TDX guest and device flows come in a later series.

Breakdown:

- Patch 1: Adds the TDH.IOMMU.SETUP and TDH.IOMMU.CLEAR SEAMCALL
  wrappers, exported only to the tdx-host module.

- Patch 2: Reads the IOMMU_MT_PAGES_COUNT global metadata field when
  TDX_FEATURES0 advertises TDX Connect. It tells the host how many
  metadata pages the TDX module needs per IOMMU.

- Patch 3: Adds dmar_tdxcs_iommu_init_all() and
  dmar_tdxcs_iommu_release() to the VT-d driver. init_all() walks the
  active DMAR units, checks the preconditions TDH.IOMMU.SETUP requires,
  confines the host to the lower half of the DID space, and hands each
  TDX-capable unit to a tdx-host callback.

- Patch 4: Implements the setup() callback in the tdx-host driver:
  allocates the trusted invalidation-queue buffers and metadata pages
  on the IOMMU's NUMA node, builds the IOMMU_MT parameter page, and
  issues TDH.IOMMU.SETUP. Units the TDX module does not cover are
  skipped. Teardown uses TDH.IOMMU.CLEAR; if that fails, the pages are
  intentionally leaked.

Change log:

v2:
- Moved the TDX module interactions (page allocation, IOMMU_MT
  construction, SEAMCALLs) from the VT-d driver to the tdx-host driver.
  The VT-d driver now only does the DRHD walk, precondition checks and
  DID reservation, and exposes them via a callback-based API.
- Renamed tdx-host.c to main.c and added iommu.c.

v1:
- https://lore.kernel.org/r/20260915074235.1219183-1-baolu.lu@linux.intel.com

ABI History:

TDH.IOMMU.CLEAR previously required the host to provide, as input, the
parameter page used for TDH.IOMMU.SETUP. The ABI has changed to take
only the TDX IOMMU ID. This change is not yet reflected in the public
ABI specification [1], so the two may differ.

Reference:

Implementation details follow the interfaces and behavior defined in the
TDX Connect Application Binary Interface (ABI) Reference Specification
and Intel TDX Connect Architecture Specification [1].

A full preview of these dependencies and follow-up changes is available
at [4].

Link: https://cdrdv2.intel.com/v1/dl/getContent/858625 # [1]
Link: https://www.intel.com/content/www/us/en/content-details/862706/intel-tdx-connect-architecture-specification.html # [2]
Link: https://lore.kernel.org/r/20261013-tdx-module-ext-v4-0-de5ffac65470@linux.intel.com # [3]
Link: https://github.com/intel-staging/tdx/tree/tdisp_phase1 # [4]

Please review. Any feedback is highly appreciated.

Thanks,
Baolu

Lu Baolu (4):
  x86/virt/tdx: Add SEAMCALL wrappers for IOMMU setup/clear
  x86/virt/tdx: Read global metadata for trusted IOMMU
  iommu/vt-d: Add an API for tdx-host when enabling trusted DMA
  coco/tdx-host: Initialize trusted DMA on all capable IOMMUs

 drivers/virt/coco/tdx-host/Kconfig            |   6 +
 drivers/virt/coco/tdx-host/Makefile           |   3 +
 arch/x86/include/asm/tdx.h                    |   3 +
 arch/x86/include/asm/tdx_global_metadata.h    |   5 +
 arch/x86/virt/vmx/tdx/tdx.h                   |   2 +
 drivers/iommu/intel/iommu.h                   |   6 +
 drivers/virt/coco/tdx-host/private.h          |  33 +++
 include/linux/dmar.h                          |   5 +
 arch/x86/virt/vmx/tdx/tdx.c                   |  56 ++++
 arch/x86/virt/vmx/tdx/tdx_global_metadata.c   |  20 ++
 drivers/iommu/intel/dmar.c                    | 103 +++++++
 drivers/iommu/intel/iommu.c                   |  19 ++
 drivers/virt/coco/tdx-host/iommu.c            | 255 ++++++++++++++++++
 .../virt/coco/tdx-host/{tdx-host.c => main.c} |  21 +-
 14 files changed, 536 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%)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [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  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, 2 replies; 12+ 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] 12+ 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; 12+ 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] 12+ 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; 12+ 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] 12+ 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
  2026-10-15  8:31   ` Xu Yilun
  3 siblings, 2 replies; 12+ 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] 12+ 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
  2026-10-15  5:36     ` Baolu Lu
  2026-10-15  6:34   ` Xu Yilun
  1 sibling, 1 reply; 12+ 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] 12+ 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
  2026-10-15  5:58     ` Baolu Lu
  2026-10-15  8:31   ` Xu Yilun
  1 sibling, 1 reply; 12+ 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] 12+ messages in thread

* Re: [PATCH v2 1/4] x86/virt/tdx: Add SEAMCALL wrappers for IOMMU setup/clear
  2026-10-15  1:28   ` sashiko-bot
@ 2026-10-15  5:36     ` Baolu Lu
  0 siblings, 0 replies; 12+ messages in thread
From: Baolu Lu @ 2026-10-15  5:36 UTC (permalink / raw)
  To: sashiko-reviews, kvm; +Cc: baolu.lu

On 10/15/2026 9:28 AM, sashiko-bot@kernel.org wrote:
> 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?

No. The hardware handles cache coherency when memory moves between
KeyIDs, so the VMM doesn't need to flush pages before handing them to
the TDX module. The TDX architecture is being updated to drop the
requirement for VMM-assisted flushing, and the existing flushes in the
TDX code are being removed by this series:

https://lore.kernel.org/r/20260922205215.870563-1-rick.p.edgecombe@intel.com

Thanks,
baolu

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH v2 4/4] coco/tdx-host: Initialize trusted DMA on all capable IOMMUs
  2026-10-15  1:31   ` sashiko-bot
@ 2026-10-15  5:58     ` Baolu Lu
  0 siblings, 0 replies; 12+ messages in thread
From: Baolu Lu @ 2026-10-15  5:58 UTC (permalink / raw)
  To: sashiko-reviews, kvm; +Cc: baolu.lu

On 10/15/2026 9:31 AM, sashiko-bot@kernel.org wrote:
> 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.

That's correct in theory, but I don't think the extra complexity is
worth it here.

The path needs two independent failures happening together:

  - xa_insert() has to fail. In practice that means a small GFP_KERNEL
    allocation failing, or the TDX module returning a duplicate
    tdx_iommu_id, which would be a TDX module bug.

  - TDH.IOMMU.CLEAR then has to fail on an IOMMU the TDX module set up
    successfully moments earlier, with nothing in between.

If both happen, the system is already badly broken, and the WARN_ON()
makes it loud. Adding more tracking just for this case would complicate
the code without making a working system more robust.

Thanks,
baolu

^ permalink raw reply	[flat|nested] 12+ 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
@ 2026-10-15  6:34   ` Xu Yilun
  1 sibling, 0 replies; 12+ 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] 12+ 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; 12+ 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] 12+ 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
@ 2026-10-15  8:31   ` Xu Yilun
  1 sibling, 0 replies; 12+ 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] 12+ messages in thread

end of thread, other threads:[~2026-10-15  8:35 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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:28   ` sashiko-bot
2026-10-15  5:36     ` Baolu Lu
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
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
2026-10-15  1:31   ` sashiko-bot
2026-10-15  5:58     ` Baolu Lu
2026-10-15  8:31   ` Xu Yilun

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox