* [RFC PATCH 01/15] x86/tdx: Export tdg_vm_rd() for tdx-guest module
2026-09-24 4:10 [RFC PATCH 00/15] PCI/TSM: coco/tdx-guest: Implement TDX-Connect PCIe TDISP (phase2) Zhenzhong Duan
@ 2026-09-24 4:10 ` Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 02/15] x86/tdx: Add TDCM hypercall wrapper for TDX Connect Zhenzhong Duan
` (13 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Zhenzhong Duan @ 2026-09-24 4:10 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: dave.hansen, tglx, mingo, bp, hpa, dave.hansen, kas,
rick.p.edgecombe, jgg, nicolinc, aik, aneesh.kumar, seanjc,
pbonzini, yilun.xu, chao.gao, vishal.l.verma, xiaoyao.li,
kevin.tian, chao.p.peng
Export tdg_vm_rd() to allow the tdx-guest driver module to directly
read TD-scoped metadata fields from the Trust Domain Control Structure
(TDCS) via TDG.VM.RD TDCALL.
Since tdg_vm_rd() is read-only and cannot cause harm, exporting it
directly is simpler and more flexible. This allows the tdx-guest driver
to read any TDCS field it needs.
In a following patch, the tdx-guest driver will use tdg_vm_rd() to read
TDCS_CONFIG_FLAGS field directly.
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
arch/x86/coco/tdx/tdx.c | 3 ++-
arch/x86/include/asm/tdx.h | 2 ++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c
index f904a636d449..22cc177a6db4 100644
--- a/arch/x86/coco/tdx/tdx.c
+++ b/arch/x86/coco/tdx/tdx.c
@@ -79,7 +79,7 @@ static inline void tdcall(u64 fn, struct tdx_module_args *args)
}
/* Read TD-scoped metadata */
-static inline u64 tdg_vm_rd(u64 field, u64 *value)
+u64 tdg_vm_rd(u64 field, u64 *value)
{
struct tdx_module_args args = {
.rdx = field,
@@ -91,6 +91,7 @@ static inline u64 tdg_vm_rd(u64 field, u64 *value)
return ret;
}
+EXPORT_SYMBOL_FOR_MODULES(tdg_vm_rd, "tdx-guest");
/* Write TD-scoped metadata */
static inline u64 tdg_vm_wr(u64 field, u64 value, u64 mask)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 89e97d5761d8..884bb8067521 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -77,6 +77,8 @@ void tdx_halt(void);
bool tdx_early_handle_ve(struct pt_regs *regs);
+u64 tdg_vm_rd(u64 field, u64 *value);
+
int tdx_mcall_get_report0(u8 *reportdata, u8 *tdreport);
int tdx_mcall_extend_rtmr(u8 index, u8 *data);
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [RFC PATCH 02/15] x86/tdx: Add TDCM hypercall wrapper for TDX Connect
2026-09-24 4:10 [RFC PATCH 00/15] PCI/TSM: coco/tdx-guest: Implement TDX-Connect PCIe TDISP (phase2) Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 01/15] x86/tdx: Export tdg_vm_rd() for tdx-guest module Zhenzhong Duan
@ 2026-09-24 4:10 ` Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 03/15] x86/tdx: Add TDG.TDI.RD module call " Zhenzhong Duan
` (12 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Zhenzhong Duan @ 2026-09-24 4:10 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: dave.hansen, tglx, mingo, bp, hpa, dave.hansen, kas,
rick.p.edgecombe, jgg, nicolinc, aik, aneesh.kumar, seanjc,
pbonzini, yilun.xu, chao.gao, vishal.l.verma, xiaoyao.li,
kevin.tian, chao.p.peng
TDX Connect is a TDX feature that enables secure device assignment,
allowing a TDX guest to directly interact with TEE-IO capable devices.
The TEE-IO Device Control and Management (TDCM) hypercall
(TDG.VP.VMCALL<TDCM>, defined in TDX GHCI v2.0 specification) is the
primary interface through which a TDX guest issues device management
commands to the host. The host processes these commands and returns
responses via a shared memory buffer.
Abstract the underlying leaf call and pass the shared buffer physical
address as a decrypted mapping. Add a runtime page alignment check
for the buffer to enforce correct usage.
Place the implementation in a separate tdx_connect.c file to keep TDX
Connect specific code isolated from core tdx.c.
CONFIG_TDX_CONNECT_GUEST referenced in the header guard will be
introduced in a later patch in this series.
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
arch/x86/coco/tdx/Makefile | 2 ++
arch/x86/coco/tdx/tdx_connect.c | 32 +++++++++++++++++++++++++++++++
arch/x86/include/asm/shared/tdx.h | 1 +
arch/x86/include/asm/tdx.h | 4 ++++
4 files changed, 39 insertions(+)
create mode 100644 arch/x86/coco/tdx/tdx_connect.c
diff --git a/arch/x86/coco/tdx/Makefile b/arch/x86/coco/tdx/Makefile
index b3c47d3700e2..2ab2dbbdd3d2 100644
--- a/arch/x86/coco/tdx/Makefile
+++ b/arch/x86/coco/tdx/Makefile
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-y += debug.o tdcall.o tdx.o tdx-shared.o
+
+obj-$(CONFIG_TDX_CONNECT_GUEST) += tdx_connect.o
diff --git a/arch/x86/coco/tdx/tdx_connect.c b/arch/x86/coco/tdx/tdx_connect.c
new file mode 100644
index 000000000000..0c6ca650771a
--- /dev/null
+++ b/arch/x86/coco/tdx/tdx_connect.c
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2026 Intel Corporation */
+
+#undef pr_fmt
+#define pr_fmt(fmt) "tdx_connect: " fmt
+
+#include <linux/io.h>
+#include <asm/tdx.h>
+#include <linux/mm.h>
+
+/*
+ * tdx_hcall_tdcm - Send a TDCM command to the host via TDG.VP.VMCALL<TDCM>.
+ *
+ * @devid: Device identifier of the target TEE-IO device.
+ * @buf: Shared, directly mapped buffer containing the TDCM command on
+ * input and the host response on output.
+ * @size: Size of @buf in bytes.
+ * @vector: Event notification interrupt vector, or 0 for synchronous operation.
+ *
+ * The buffer physical address is passed to the host with decrypted/shared
+ * mark.
+ *
+ * Returns 0 on success or a TDX VMCALL status code on failure. See
+ * TDG.VP.VMCALL<TDCM> in the TDX GHCI v2.0 specification for status codes.
+ */
+u64 tdx_hcall_tdcm(u16 devid, void *buf, size_t size, u8 vector)
+{
+ WARN_ON_ONCE(!PAGE_ALIGNED(buf) || !PAGE_ALIGNED(size));
+
+ return _tdx_hypercall(TDVMCALL_TDCM, devid, cc_mkdec(virt_to_phys(buf)), size, vector);
+}
+EXPORT_SYMBOL_FOR_MODULES(tdx_hcall_tdcm, "tdx-guest");
diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h
index f20e91d7ac35..f46a3171a512 100644
--- a/arch/x86/include/asm/shared/tdx.h
+++ b/arch/x86/include/asm/shared/tdx.h
@@ -74,6 +74,7 @@
#define TDVMCALL_GET_QUOTE 0x10002
#define TDVMCALL_REPORT_FATAL_ERROR 0x10003
#define TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT 0x10004ULL
+#define TDVMCALL_TDCM 0x10007
/*
* TDG.VP.VMCALL Status Codes (returned in R10)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 884bb8067521..df4496ef8a6a 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -85,6 +85,10 @@ int tdx_mcall_extend_rtmr(u8 index, u8 *data);
u64 tdx_hcall_get_quote(u8 *buf, size_t size);
+#ifdef CONFIG_TDX_CONNECT_GUEST
+u64 tdx_hcall_tdcm(u16 devid, void *buf, size_t size, u8 vector);
+#endif
+
void __init tdx_dump_attributes(u64 td_attr);
void __init tdx_dump_td_ctls(u64 td_ctls);
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [RFC PATCH 03/15] x86/tdx: Add TDG.TDI.RD module call wrapper for TDX Connect
2026-09-24 4:10 [RFC PATCH 00/15] PCI/TSM: coco/tdx-guest: Implement TDX-Connect PCIe TDISP (phase2) Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 01/15] x86/tdx: Export tdg_vm_rd() for tdx-guest module Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 02/15] x86/tdx: Add TDCM hypercall wrapper for TDX Connect Zhenzhong Duan
@ 2026-09-24 4:10 ` Zhenzhong Duan
2026-09-25 0:06 ` Edgecombe, Rick P
2026-09-24 4:10 ` [RFC PATCH 04/15] virt: tdx-guest: Support devsec TSM for secure devices Zhenzhong Duan
` (11 subsequent siblings)
14 siblings, 1 reply; 19+ messages in thread
From: Zhenzhong Duan @ 2026-09-24 4:10 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: dave.hansen, tglx, mingo, bp, hpa, dave.hansen, kas,
rick.p.edgecombe, jgg, nicolinc, aik, aneesh.kumar, seanjc,
pbonzini, yilun.xu, chao.gao, vishal.l.verma, xiaoyao.li,
kevin.tian, chao.p.peng
Introduce wrapper tdx_mcall_tdi_read() to provide tdx-guest driver with a
standard interface for querying specific metadata fields from a target
Trust Device Interface Control Structure (TDI_CS).
Explicitly mapping architectural TDX error codes into Linux POSIX errno
with two helpers, introducing tdx_mcall_to_errno() to handle general
module calls, while introducing tdx_mcall_tdi_to_errno() specifically for
TDI related module calls.
Unlike TDG.VP.VMCALL based hypercalls which communicate with the host,
TDG.TDI.RD is a module call (mcall) that communicates directly with the
TDX module. This distinction is reflected in the tdx_mcall_ naming
prefix, as opposed to tdx_hcall_ used for host-directed hypercalls.
Note that while host-bound hypercalls (such as tdx_hcall_tdcm()) pass a
platform-specific Device Identifier (devid), the func_id parameter used
here is defined by the TDISP standard (refer to Section 11.2 "TDISP
rules"). Although these values result in the same underlying bit format
when the PCI segment is zero, they represent separate structural
abstractions in their respective specifications.
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
arch/x86/coco/tdx/tdx.c | 5 ----
arch/x86/coco/tdx/tdx_connect.c | 41 +++++++++++++++++++++++++++++++
arch/x86/include/asm/shared/tdx.h | 1 +
arch/x86/include/asm/tdx.h | 20 +++++++++++++++
4 files changed, 62 insertions(+), 5 deletions(-)
diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c
index 22cc177a6db4..c3db2451c078 100644
--- a/arch/x86/coco/tdx/tdx.c
+++ b/arch/x86/coco/tdx/tdx.c
@@ -34,11 +34,6 @@
#define VE_GET_PORT_NUM(e) ((e) >> 16)
#define VE_IS_IO_STRING(e) ((e) & BIT(4))
-/* TDX Module call error codes */
-#define TDCALL_RETURN_CODE(a) ((a) >> 32)
-#define TDCALL_INVALID_OPERAND 0xc0000100
-#define TDCALL_OPERAND_BUSY 0x80000200
-
#define TDREPORT_SUBTYPE_0 0
static atomic_long_t nr_shared;
diff --git a/arch/x86/coco/tdx/tdx_connect.c b/arch/x86/coco/tdx/tdx_connect.c
index 0c6ca650771a..1409b1a30cc6 100644
--- a/arch/x86/coco/tdx/tdx_connect.c
+++ b/arch/x86/coco/tdx/tdx_connect.c
@@ -8,6 +8,17 @@
#include <asm/tdx.h>
#include <linux/mm.h>
+static inline int tdx_mcall_tdi_to_errno(u64 ret)
+{
+ switch (TDCALL_RETURN_CODE(ret)) {
+ case TDCALL_TDI_NOT_PRESENT:
+ case TDCALL_TDI_INVALID_METADATA:
+ return -ENODEV;
+ default:
+ return tdx_mcall_to_errno(ret);
+ }
+}
+
/*
* tdx_hcall_tdcm - Send a TDCM command to the host via TDG.VP.VMCALL<TDCM>.
*
@@ -30,3 +41,33 @@ u64 tdx_hcall_tdcm(u16 devid, void *buf, size_t size, u8 vector)
return _tdx_hypercall(TDVMCALL_TDCM, devid, cc_mkdec(virt_to_phys(buf)), size, vector);
}
EXPORT_SYMBOL_FOR_MODULES(tdx_hcall_tdcm, "tdx-guest");
+
+/**
+ * tdx_mcall_tdi_read() - Read field from a Trust Device Interface Control Structure (TDI_CS)
+ * @func_id: Function identifier specifying the TDI_CS
+ * @field: Field identifier within the specified TDI_CS
+ * @value: Storage for the successfully retrieved field value
+ *
+ * Invokes the TDG.TDI.RD TDCALL to read the value of @field within the TDI_CS specified
+ * by @func_id.
+ *
+ * Return 0 on success, -ENXIO for invalid operands, -EBUSY for busy operation,
+ * -ENODEV for TDI not present or invalid metadata, or -EIO on other TDCALL failures.
+ */
+int tdx_mcall_tdi_read(u64 func_id, u64 field, u64 *value)
+{
+ struct tdx_module_args args = {
+ .rcx = func_id,
+ .rdx = field,
+ };
+ u64 ret;
+
+ ret = __tdcall_ret(TDG_TDI_READ, &args);
+ if (!ret) {
+ *value = args.rcx;
+ return 0;
+ }
+
+ return tdx_mcall_tdi_to_errno(ret);
+}
+EXPORT_SYMBOL_FOR_MODULES(tdx_mcall_tdi_read, "tdx-guest");
diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h
index f46a3171a512..665c12925ff6 100644
--- a/arch/x86/include/asm/shared/tdx.h
+++ b/arch/x86/include/asm/shared/tdx.h
@@ -20,6 +20,7 @@
#define TDG_MEM_PAGE_ACCEPT 6
#define TDG_VM_RD 7
#define TDG_VM_WR 8
+#define TDG_TDI_READ 67
/* TDX TD attributes */
#define TDX_TD_ATTR_DEBUG_BIT 0
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index df4496ef8a6a..4937e23d5483 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -77,6 +77,25 @@ void tdx_halt(void);
bool tdx_early_handle_ve(struct pt_regs *regs);
+/* TDX Module call error codes */
+#define TDCALL_RETURN_CODE(a) ((a) >> 32)
+#define TDCALL_INVALID_OPERAND 0xc0000100
+#define TDCALL_OPERAND_BUSY 0x80000200
+#define TDCALL_TDI_NOT_PRESENT 0xc0000f40
+#define TDCALL_TDI_INVALID_METADATA 0xc0000f41
+
+static inline int tdx_mcall_to_errno(u64 ret)
+{
+ switch (TDCALL_RETURN_CODE(ret)) {
+ case TDCALL_INVALID_OPERAND:
+ return -ENXIO;
+ case TDCALL_OPERAND_BUSY:
+ return -EBUSY;
+ default:
+ return -EIO;
+ }
+}
+
u64 tdg_vm_rd(u64 field, u64 *value);
int tdx_mcall_get_report0(u8 *reportdata, u8 *tdreport);
@@ -87,6 +106,7 @@ u64 tdx_hcall_get_quote(u8 *buf, size_t size);
#ifdef CONFIG_TDX_CONNECT_GUEST
u64 tdx_hcall_tdcm(u16 devid, void *buf, size_t size, u8 vector);
+int tdx_mcall_tdi_read(u64 func_id, u64 field, u64 *value);
#endif
void __init tdx_dump_attributes(u64 td_attr);
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [RFC PATCH 03/15] x86/tdx: Add TDG.TDI.RD module call wrapper for TDX Connect
2026-09-24 4:10 ` [RFC PATCH 03/15] x86/tdx: Add TDG.TDI.RD module call " Zhenzhong Duan
@ 2026-09-25 0:06 ` Edgecombe, Rick P
0 siblings, 0 replies; 19+ messages in thread
From: Edgecombe, Rick P @ 2026-09-25 0:06 UTC (permalink / raw)
To: linux-coco, linux-kernel, Duan, Zhenzhong, x86
Cc: Li, Xiaoyao, Hansen, Dave, dave.hansen, kas, mingo, seanjc,
pbonzini, Peng, Chao P, Verma, Vishal L, jgg, aneesh.kumar, Tian,
Kevin, hpa, tglx, nicolinc, bp, Gao, Chao, aik, yilun.xu
On Thu, 2026-09-24 at 12:10 +0800, Zhenzhong Duan wrote:
> -/* TDX Module call error codes */
> -#define TDCALL_RETURN_CODE(a) ((a) >> 32)
> -#define TDCALL_INVALID_OPERAND 0xc0000100
> -#define TDCALL_OPERAND_BUSY 0x80000200
> -
> #define TDREPORT_SUBTYPE_0 0
>
> static atomic_long_t nr_shared;
> diff --git a/arch/x86/coco/tdx/tdx_connect.c b/arch/x86/coco/tdx/tdx_connect.c
> index 0c6ca650771a..1409b1a30cc6 100644
> --- a/arch/x86/coco/tdx/tdx_connect.c
> +++ b/arch/x86/coco/tdx/tdx_connect.c
> @@ -8,6 +8,17 @@
> #include <asm/tdx.h>
> #include <linux/mm.h>
>
> +static inline int tdx_mcall_tdi_to_errno(u64 ret)
> +{
> + switch (TDCALL_RETURN_CODE(ret)) {
> + case TDCALL_TDI_NOT_PRESENT:
> + case TDCALL_TDI_INVALID_METADATA:
> + return -ENODEV;
> + default:
> + return tdx_mcall_to_errno(ret);
> + }
> +}
> +
>
...
>
> +/* TDX Module call error codes */
> +#define TDCALL_RETURN_CODE(a) ((a) >> 32)
> +#define TDCALL_INVALID_OPERAND 0xc0000100
> +#define TDCALL_OPERAND_BUSY 0x80000200
> +#define TDCALL_TDI_NOT_PRESENT 0xc0000f40
> +#define TDCALL_TDI_INVALID_METADATA 0xc0000f41
> +
> +static inline int tdx_mcall_to_errno(u64 ret)
> +{
> + switch (TDCALL_RETURN_CODE(ret)) {
> + case TDCALL_INVALID_OPERAND:
> + return -ENXIO;
> + case TDCALL_OPERAND_BUSY:
> + return -EBUSY;
> + default:
> + return -EIO;
> + }
> +}
Very few of the other tdcalls check the errors, which is a bit surprising. This
now introduces a generic helper to handle them, but just uses it for the TDI
calls. Is the intention to use this for the other "mcalls"?
But I wonder if something is lost in handling these all by default. Don't we
need to consider each error? For example, if accept gets a busy and we toss this
back. Is the caller supposed to retry? Does it expect to? Or should we do
something to avoid the busy in the first place? We need to at least examine each
possible error condition as part of the implementation.
^ permalink raw reply [flat|nested] 19+ messages in thread
* [RFC PATCH 04/15] virt: tdx-guest: Support devsec TSM for secure devices
2026-09-24 4:10 [RFC PATCH 00/15] PCI/TSM: coco/tdx-guest: Implement TDX-Connect PCIe TDISP (phase2) Zhenzhong Duan
` (2 preceding siblings ...)
2026-09-24 4:10 ` [RFC PATCH 03/15] x86/tdx: Add TDG.TDI.RD module call " Zhenzhong Duan
@ 2026-09-24 4:10 ` Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 05/15] virt: tdx-guest: Add TDCM helpers and TEE-IO support check Zhenzhong Duan
` (10 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Zhenzhong Duan @ 2026-09-24 4:10 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: dave.hansen, tglx, mingo, bp, hpa, dave.hansen, kas,
rick.p.edgecombe, jgg, nicolinc, aik, aneesh.kumar, seanjc,
pbonzini, yilun.xu, chao.gao, vishal.l.verma, xiaoyao.li,
kevin.tian, chao.p.peng
Register Intel TDX secure device (devsec TSM) to the TSM framework and
supply the tdx_devsec_ops callback operations. This enables control and
management of the Trust Device Interface (TDI) through those hooks in
accordance with the TDISP protocol.
Check the TDCS configuration flags during driver initialization to
ensure the hardware interface is supported before attempting device
registration.
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
arch/x86/include/asm/shared/tdx.h | 1 +
drivers/virt/coco/tdx-guest/Kconfig | 15 ++++
drivers/virt/coco/tdx-guest/Makefile | 3 +
drivers/virt/coco/tdx-guest/connect.c | 81 +++++++++++++++++++
.../coco/tdx-guest/{tdx-guest.c => main.c} | 6 ++
drivers/virt/coco/tdx-guest/tdx-guest.h | 20 +++++
6 files changed, 126 insertions(+)
create mode 100644 drivers/virt/coco/tdx-guest/connect.c
rename drivers/virt/coco/tdx-guest/{tdx-guest.c => main.c} (98%)
create mode 100644 drivers/virt/coco/tdx-guest/tdx-guest.h
diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h
index 665c12925ff6..499103f7a01b 100644
--- a/arch/x86/include/asm/shared/tdx.h
+++ b/arch/x86/include/asm/shared/tdx.h
@@ -56,6 +56,7 @@
/* TDCS_CONFIG_FLAGS bits */
#define TDCS_CONFIG_FLEXIBLE_PENDING_VE BIT_ULL(1)
+#define TDCS_CONFIG_TDX_CONNECT BIT_ULL(5)
/* TDCS_TD_CTLS bits */
#define TD_CTLS_PENDING_VE_DISABLE_BIT 0
diff --git a/drivers/virt/coco/tdx-guest/Kconfig b/drivers/virt/coco/tdx-guest/Kconfig
index dbbdc14383b1..2e9407f8bc70 100644
--- a/drivers/virt/coco/tdx-guest/Kconfig
+++ b/drivers/virt/coco/tdx-guest/Kconfig
@@ -10,3 +10,18 @@ config TDX_GUEST_DRIVER
To compile this driver as module, choose M here. The module will
be called tdx-guest.
+
+config TDX_CONNECT_GUEST
+ bool "Intel TDX Connect Guest Support"
+ depends on TDX_GUEST_DRIVER
+ depends on PCI_TSM
+ default y
+ help
+ Support Trust Device Interface (TDI) feature enumeration,
+ validation, and activation within an Intel TDX guest.
+
+ This option enables the guest kernel to establish secure,
+ isolated connections with trusted devices assigned to the TD,
+ validating MMIO maps and authorizing DMA remapping tables.
+
+ If unsure, say Y.
diff --git a/drivers/virt/coco/tdx-guest/Makefile b/drivers/virt/coco/tdx-guest/Makefile
index 775cb463f9c8..cfa991c7aeb5 100644
--- a/drivers/virt/coco/tdx-guest/Makefile
+++ b/drivers/virt/coco/tdx-guest/Makefile
@@ -1,2 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_TDX_GUEST_DRIVER) += tdx-guest.o
+
+tdx-guest-y := main.o
+tdx-guest-$(CONFIG_TDX_CONNECT_GUEST) += connect.o
diff --git a/drivers/virt/coco/tdx-guest/connect.c b/drivers/virt/coco/tdx-guest/connect.c
new file mode 100644
index 000000000000..80ae7c19e179
--- /dev/null
+++ b/drivers/virt/coco/tdx-guest/connect.c
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TDX Connect guest driver
+ *
+ * Copyright (C) 2026 Intel Corporation
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": tdx_connect: " fmt
+
+#include <linux/pci.h>
+#include <linux/pci-tsm.h>
+#include <linux/tsm.h>
+#include <asm/tdx.h>
+
+#include "tdx-guest.h"
+
+struct tdx_devsec {
+ struct pci_tsm_devsec pci;
+};
+
+static struct tdx_devsec *to_tdx_devsec(struct pci_tsm *tsm)
+{
+ return container_of(tsm, struct tdx_devsec, pci.base_tsm);
+}
+
+static struct pci_tsm *tdx_devsec_lock(struct tsm_dev *tsm_dev, struct pci_dev *pdev)
+{
+ int ret;
+
+ struct tdx_devsec *tdevsec __free(kfree) = kzalloc(sizeof(*tdevsec), GFP_KERNEL);
+ if (!tdevsec)
+ return ERR_PTR(-ENOMEM);
+
+ ret = pci_tsm_devsec_constructor(pdev, &tdevsec->pci, tsm_dev);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return &no_free_ptr(tdevsec)->pci.base_tsm;
+}
+
+static void tdx_devsec_unlock(struct pci_tsm *tsm)
+{
+ struct tdx_devsec *tdevsec = to_tdx_devsec(tsm);
+
+ kfree(tdevsec);
+}
+
+static int tdx_devsec_run(struct pci_dev *pdev)
+{
+ return -EOPNOTSUPP;
+}
+
+static struct pci_tsm_ops tdx_devsec_ops = {
+ .lock = tdx_devsec_lock,
+ .unlock = tdx_devsec_unlock,
+ .run = tdx_devsec_run,
+};
+
+static void devsec_tsm_remove(void *tsm_dev)
+{
+ tsm_unregister(tsm_dev);
+}
+
+int tdx_connect_init(struct device *dev)
+{
+ struct tsm_dev *tsm_dev;
+ u64 config, ret;
+
+ ret = tdg_vm_rd(TDCS_CONFIG_FLAGS, &config);
+ if (ret)
+ return -EIO;
+
+ if (!(config & TDCS_CONFIG_TDX_CONNECT))
+ return 0;
+
+ tsm_dev = tsm_register(dev, &tdx_devsec_ops);
+ if (IS_ERR(tsm_dev))
+ return PTR_ERR(tsm_dev);
+
+ return devm_add_action_or_reset(dev, devsec_tsm_remove, tsm_dev);
+}
diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.c b/drivers/virt/coco/tdx-guest/main.c
similarity index 98%
rename from drivers/virt/coco/tdx-guest/tdx-guest.c
rename to drivers/virt/coco/tdx-guest/main.c
index d0303e31e816..801a1f2b5f3d 100644
--- a/drivers/virt/coco/tdx-guest/tdx-guest.c
+++ b/drivers/virt/coco/tdx-guest/main.c
@@ -25,6 +25,8 @@
#include <asm/cpu_device_id.h>
#include <asm/tdx.h>
+#include "tdx-guest.h"
+
/* TDREPORT buffer */
static u8 *tdx_report_buf;
@@ -428,6 +430,10 @@ static int __init tdx_guest_init(void)
if (ret)
goto free_quote;
+ ret = tdx_connect_init(tdx_misc_dev.this_device);
+ if (ret)
+ pr_warn("Failed to enable TDX Connect: %d\n", ret);
+
return 0;
free_quote:
diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.h b/drivers/virt/coco/tdx-guest/tdx-guest.h
new file mode 100644
index 000000000000..4218541c7f3b
--- /dev/null
+++ b/drivers/virt/coco/tdx-guest/tdx-guest.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * TDX guest driver private declarations
+ *
+ * Copyright (C) 2026 Intel Corporation
+ */
+
+#ifndef __TDX_GUEST_H
+#define __TDX_GUEST_H
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+
+#ifdef CONFIG_TDX_CONNECT_GUEST
+extern int tdx_connect_init(struct device *dev);
+#else
+static inline int tdx_connect_init(struct device *dev) { return 0; }
+#endif /* CONFIG_TDX_CONNECT_GUEST */
+
+#endif /* __TDX_GUEST_H */
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [RFC PATCH 05/15] virt: tdx-guest: Add TDCM helpers and TEE-IO support check
2026-09-24 4:10 [RFC PATCH 00/15] PCI/TSM: coco/tdx-guest: Implement TDX-Connect PCIe TDISP (phase2) Zhenzhong Duan
` (3 preceding siblings ...)
2026-09-24 4:10 ` [RFC PATCH 04/15] virt: tdx-guest: Support devsec TSM for secure devices Zhenzhong Duan
@ 2026-09-24 4:10 ` Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 06/15] virt: tdx-guest: Support TDI bind and unbind operations Zhenzhong Duan
` (9 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Zhenzhong Duan @ 2026-09-24 4:10 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: dave.hansen, tglx, mingo, bp, hpa, dave.hansen, kas,
rick.p.edgecombe, jgg, nicolinc, aik, aneesh.kumar, seanjc,
pbonzini, yilun.xu, chao.gao, vishal.l.verma, xiaoyao.li,
kevin.tian, chao.p.peng
Implement internal helper wrappers tdx_tdcm_alloc(), tdx_tdcm_run(),
and tdx_tdcm_free() to manage execution context lifecycle for TDX Connect
TEE-IO Device Control and Management (TDCM) commands.
Use these infrastructures to deploy all TDCM commands to orchestrate
trusted device management, beginning with the first command,
TDCM_OP_CHECK_TEEIO_SUPP, executed during the secure device locking
sequence.
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
arch/x86/include/asm/tdx.h | 48 +++++++++
drivers/virt/coco/tdx-guest/connect.c | 139 ++++++++++++++++++++++++++
2 files changed, 187 insertions(+)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 4937e23d5483..71cd701d346f 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -105,6 +105,54 @@ int tdx_mcall_extend_rtmr(u8 index, u8 *data);
u64 tdx_hcall_get_quote(u8 *buf, size_t size);
#ifdef CONFIG_TDX_CONNECT_GUEST
+enum tdcm_operation {
+ TDCM_OP_CHECK_TEEIO_SUPP = 1,
+ TDCM_OP_BIND = 2,
+ TDCM_OP_GET_DEV_INFO = 3,
+ TDCM_OP_GET_TDI_REPORT = 4,
+ TDCM_OP_START_TDI = 5,
+ TDCM_OP_GET_TDI_STATE = 6,
+ TDCM_OP_UNBIND = 7,
+};
+
+enum tdcm_status {
+ TDCM_STATUS_WAIT = 0,
+ TDCM_STATUS_COMPLETED = 1,
+ TDCM_STATUS_ERROR = 2,
+};
+
+enum tdcm_error {
+ TDCM_ERROR_TDX_MODULE = 10,
+ TDCM_ERROR_TDXIO_DEVICE = 11,
+ TDCM_ERROR_SPDM_MESSAGE = 12,
+ TDCM_ERROR_IDE_KM_MESSAGE = 13,
+ TDCM_ERROR_TDISP_MESSAGE = 14,
+ TDCM_ERROR_INVALID_STATE = 15,
+};
+
+struct tdvmcall_tdcm {
+ __u16 operation; /* See enum tdcm_operation */
+ __u8 version;
+ __u8 rsvd0;
+ __u32 rsvd1;
+
+ __u8 status; /* See enum tdcm_status */
+ __u8 error; /* See enum tdcm_error */
+ __u16 rsvd2;
+ __u32 rsvd3;
+
+ __u32 data_length;
+
+ __u8 data[];
+} __packed;
+
+struct tdcm_rsp_check_teeio_supp {
+ __u8 is_supported;
+ __u8 data[];
+} __packed;
+
+#define MAX_TDI_REPORT_SIZE (16 * PAGE_SIZE)
+
u64 tdx_hcall_tdcm(u16 devid, void *buf, size_t size, u8 vector);
int tdx_mcall_tdi_read(u64 func_id, u64 field, u64 *value);
#endif
diff --git a/drivers/virt/coco/tdx-guest/connect.c b/drivers/virt/coco/tdx-guest/connect.c
index 80ae7c19e179..1f52a187b30f 100644
--- a/drivers/virt/coco/tdx-guest/connect.c
+++ b/drivers/virt/coco/tdx-guest/connect.c
@@ -7,13 +7,127 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": tdx_connect: " fmt
+#include <linux/iopoll.h>
#include <linux/pci.h>
#include <linux/pci-tsm.h>
+#include <linux/set_memory.h>
#include <linux/tsm.h>
#include <asm/tdx.h>
#include "tdx-guest.h"
+/**
+ * struct tdcm_ctx - TEE-IO Device Configuration and Management (TDCM) context
+ * @buf: Pointer to 4KB-aligned shared memory (>= one page) used as the command
+ * buffer on input and the response buffer on output.
+ * @buf_size: Total size of the shared memory buffer.
+ * @max_rsp_data_sz: Maximum expected size of the response data.
+ * @rsp_data_sz: Actual size of the response data populated by the VMM.
+ * @devid: Device Identifier.
+ */
+struct tdcm_ctx {
+ struct tdvmcall_tdcm *buf;
+ size_t buf_size;
+ u32 max_rsp_data_sz;
+ u32 rsp_data_sz;
+ u16 devid;
+};
+
+#define to_tdcm_rsp_data(tdcm) ((tdcm)->buf->data)
+
+#define TDCM_POLL_DELAY_US 10000
+#define TDCM_POLL_TIMEOUT_US (10 * USEC_PER_SEC)
+
+static int tdx_tdcm_run(struct tdcm_ctx *tdcm)
+{
+ struct tdvmcall_tdcm *buf = tdcm->buf;
+ u8 status;
+ int ret;
+ u64 r;
+
+ r = tdx_hcall_tdcm(tdcm->devid, buf, tdcm->buf_size, 0);
+ if (r)
+ return -EFAULT;
+
+ ret = read_poll_timeout(READ_ONCE, status, status != TDCM_STATUS_WAIT,
+ TDCM_POLL_DELAY_US, TDCM_POLL_TIMEOUT_US, false, buf->status);
+ if (ret) {
+ pr_err("TDCM request %d timed out\n", buf->operation);
+ return ret;
+ }
+
+ /* Ensure subsequent data reads are ordered after the status observation */
+ virt_rmb();
+
+ /*
+ * Cache VMM response data length to avoid referencing buf->data_length
+ * directly in case a malicious VMM changes buf->data_length between the
+ * validation below and the subsequent reads.
+ */
+ tdcm->rsp_data_sz = READ_ONCE(buf->data_length);
+
+ if (status != TDCM_STATUS_COMPLETED || tdcm->rsp_data_sz > tdcm->max_rsp_data_sz ||
+ (tdcm->max_rsp_data_sz && !tdcm->rsp_data_sz)) {
+ pr_err("TDCM request %d failed: status 0x%x, error 0x%x, max_rsp_data_sz 0x%x, returned 0x%x\n",
+ buf->operation, status, buf->error, tdcm->max_rsp_data_sz,
+ tdcm->rsp_data_sz);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static struct tdcm_ctx *tdx_tdcm_alloc(struct pci_dev *pdev, u8 operation,
+ size_t cmd_data_sz, size_t rsp_data_sz)
+{
+ struct tdvmcall_tdcm *buf;
+ size_t buf_size;
+ int ret;
+
+ buf_size = max(cmd_data_sz, rsp_data_sz);
+ buf_size = struct_size_t(struct tdvmcall_tdcm, data, buf_size);
+ buf_size = PAGE_ALIGN(buf_size);
+
+ struct tdcm_ctx *tdcm __free(kfree) = kzalloc(sizeof(*tdcm), GFP_KERNEL);
+ if (!tdcm)
+ return ERR_PTR(-ENOMEM);
+
+ buf = alloc_pages_exact(buf_size, GFP_KERNEL);
+ if (!buf)
+ return ERR_PTR(-ENOMEM);
+
+ ret = set_memory_decrypted((unsigned long)buf, PHYS_PFN(buf_size));
+ if (ret) {
+ free_pages_exact(buf, buf_size);
+ return ERR_PTR(ret);
+ }
+
+ memset(buf, 0, buf_size);
+ buf->operation = operation;
+ buf->status = TDCM_STATUS_WAIT;
+ buf->data_length = cmd_data_sz;
+
+ tdcm->buf = buf;
+ tdcm->buf_size = buf_size;
+ tdcm->max_rsp_data_sz = rsp_data_sz;
+ tdcm->devid = pci_dev_id(pdev);
+
+ return_ptr(tdcm);
+}
+
+static void tdx_tdcm_free(struct tdcm_ctx *tdcm)
+{
+ if (set_memory_encrypted((unsigned long)tdcm->buf, PHYS_PFN(tdcm->buf_size)))
+ pr_err("Failed to encrypt TDCM buf, leak it\n");
+ else
+ free_pages_exact(tdcm->buf, tdcm->buf_size);
+
+ kfree(tdcm);
+}
+
+DEFINE_FREE(tdx_tdcm_free, struct tdcm_ctx *,
+ if (!IS_ERR_OR_NULL(_T)) tdx_tdcm_free(_T))
+
struct tdx_devsec {
struct pci_tsm_devsec pci;
};
@@ -23,6 +137,28 @@ static struct tdx_devsec *to_tdx_devsec(struct pci_tsm *tsm)
return container_of(tsm, struct tdx_devsec, pci.base_tsm);
}
+static bool tdx_is_dev_teeio_support(struct tdx_devsec *tdevsec)
+{
+ struct pci_dev *pdev = tdevsec->pci.base_tsm.pdev;
+ struct tdcm_rsp_check_teeio_supp *rsp;
+ int ret;
+
+ struct tdcm_ctx *tdcm __free(tdx_tdcm_free) =
+ tdx_tdcm_alloc(pdev, TDCM_OP_CHECK_TEEIO_SUPP, 0, sizeof(*rsp));
+ if (IS_ERR(tdcm))
+ return false;
+
+ ret = tdx_tdcm_run(tdcm);
+ if (ret)
+ return false;
+
+ if (tdcm->rsp_data_sz != sizeof(*rsp))
+ return false;
+
+ rsp = (struct tdcm_rsp_check_teeio_supp *)to_tdcm_rsp_data(tdcm);
+ return !!rsp->is_supported;
+}
+
static struct pci_tsm *tdx_devsec_lock(struct tsm_dev *tsm_dev, struct pci_dev *pdev)
{
int ret;
@@ -35,6 +171,9 @@ static struct pci_tsm *tdx_devsec_lock(struct tsm_dev *tsm_dev, struct pci_dev *
if (ret)
return ERR_PTR(ret);
+ if (!tdx_is_dev_teeio_support(tdevsec))
+ return ERR_PTR(-EOPNOTSUPP);
+
return &no_free_ptr(tdevsec)->pci.base_tsm;
}
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [RFC PATCH 06/15] virt: tdx-guest: Support TDI bind and unbind operations
2026-09-24 4:10 [RFC PATCH 00/15] PCI/TSM: coco/tdx-guest: Implement TDX-Connect PCIe TDISP (phase2) Zhenzhong Duan
` (4 preceding siblings ...)
2026-09-24 4:10 ` [RFC PATCH 05/15] virt: tdx-guest: Add TDCM helpers and TEE-IO support check Zhenzhong Duan
@ 2026-09-24 4:10 ` Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 07/15] PCI/TSM: Track Device Interface Report MMIO range index Zhenzhong Duan
` (8 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Zhenzhong Duan @ 2026-09-24 4:10 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: dave.hansen, tglx, mingo, bp, hpa, dave.hansen, kas,
rick.p.edgecombe, jgg, nicolinc, aik, aneesh.kumar, seanjc,
pbonzini, yilun.xu, chao.gao, vishal.l.verma, xiaoyao.li,
kevin.tian, chao.p.peng
Introduce support for binding and unbinding the Trust Device Interface
(TDI) using the TDCM_OP_BIND and TDCM_OP_UNBIND commands.
Executing TDCM_OP_BIND transitions the TDI state to a locked state
(TDI_STATE_CONFIG_LOCKED). Add a sanity check following the bind operation
to verify this state transition succeeded before finalizing device locking.
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
drivers/virt/coco/tdx-guest/connect.c | 75 +++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
diff --git a/drivers/virt/coco/tdx-guest/connect.c b/drivers/virt/coco/tdx-guest/connect.c
index 1f52a187b30f..e36515fc43ef 100644
--- a/drivers/virt/coco/tdx-guest/connect.c
+++ b/drivers/virt/coco/tdx-guest/connect.c
@@ -159,8 +159,67 @@ static bool tdx_is_dev_teeio_support(struct tdx_devsec *tdevsec)
return !!rsp->is_supported;
}
+static struct tdx_devsec *tdx_tdi_bind_dev(struct tdx_devsec *tdevsec)
+{
+ struct pci_dev *pdev = tdevsec->pci.base_tsm.pdev;
+ int ret;
+
+ struct tdcm_ctx *tdcm __free(tdx_tdcm_free) = tdx_tdcm_alloc(pdev, TDCM_OP_BIND, 0, 0);
+ if (IS_ERR(tdcm))
+ return ERR_CAST(tdcm);
+
+ ret = tdx_tdcm_run(tdcm);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return tdevsec;
+}
+
+static int tdx_tdi_unbind_dev(struct tdx_devsec *tdevsec)
+{
+ struct pci_dev *pdev = tdevsec->pci.base_tsm.pdev;
+
+ struct tdcm_ctx *tdcm __free(tdx_tdcm_free) = tdx_tdcm_alloc(pdev, TDCM_OP_UNBIND, 0, 0);
+ if (IS_ERR(tdcm))
+ return PTR_ERR(tdcm);
+
+ return tdx_tdcm_run(tdcm);
+}
+
+DEFINE_FREE(tdx_tdi_unbind_dev, struct tdx_devsec *,
+ if (!IS_ERR_OR_NULL(_T)) tdx_tdi_unbind_dev(_T))
+
+/*
+ * TDI State value returned by TDG.TDI.RD.
+ * Refer to section "TDG.TDI.RD leaf" in the TDX Connect ABI Specification.
+ */
+enum tdi_state {
+ TDI_STATE_CONFIG_UNLOCKED = 0x0,
+ TDI_STATE_CONFIG_LOCKED = 0x1,
+ TDI_STATE_RUN = 0x2,
+ TDI_STATE_ERROR = 0x3,
+};
+
+enum tdi_field_code {
+ TDI_GET_TDISP_STATE = 2,
+};
+
+static int tdx_tdi_read_state(struct tdx_devsec *tdevsec, u8 *state)
+{
+ struct pci_dev *pdev = tdevsec->pci.base_tsm.pdev;
+ u64 value;
+ int ret;
+
+ ret = tdx_mcall_tdi_read(pci_dev_id(pdev), TDI_GET_TDISP_STATE, &value);
+ if (!ret)
+ *state = value;
+
+ return ret;
+}
+
static struct pci_tsm *tdx_devsec_lock(struct tsm_dev *tsm_dev, struct pci_dev *pdev)
{
+ u8 state;
int ret;
struct tdx_devsec *tdevsec __free(kfree) = kzalloc(sizeof(*tdevsec), GFP_KERNEL);
@@ -174,6 +233,19 @@ static struct pci_tsm *tdx_devsec_lock(struct tsm_dev *tsm_dev, struct pci_dev *
if (!tdx_is_dev_teeio_support(tdevsec))
return ERR_PTR(-EOPNOTSUPP);
+ struct tdx_devsec *tdevsec_bind __free(tdx_tdi_unbind_dev) = tdx_tdi_bind_dev(tdevsec);
+ if (IS_ERR(tdevsec_bind))
+ return ERR_CAST(tdevsec_bind);
+
+ ret = tdx_tdi_read_state(tdevsec, &state);
+ if (ret)
+ return ERR_PTR(ret);
+
+ if (state != TDI_STATE_CONFIG_LOCKED)
+ return ERR_PTR(-EIO);
+
+ retain_and_null_ptr(tdevsec_bind);
+
return &no_free_ptr(tdevsec)->pci.base_tsm;
}
@@ -181,6 +253,9 @@ static void tdx_devsec_unlock(struct pci_tsm *tsm)
{
struct tdx_devsec *tdevsec = to_tdx_devsec(tsm);
+ if (WARN_ON(tdx_tdi_unbind_dev(tdevsec)))
+ return;
+
kfree(tdevsec);
}
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [RFC PATCH 07/15] PCI/TSM: Track Device Interface Report MMIO range index
2026-09-24 4:10 [RFC PATCH 00/15] PCI/TSM: coco/tdx-guest: Implement TDX-Connect PCIe TDISP (phase2) Zhenzhong Duan
` (5 preceding siblings ...)
2026-09-24 4:10 ` [RFC PATCH 06/15] virt: tdx-guest: Support TDI bind and unbind operations Zhenzhong Duan
@ 2026-09-24 4:10 ` Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 08/15] x86/tdx: Add TDG.MMIO.ACCEPT module call wrapper for TDX Connect Zhenzhong Duan
` (7 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Zhenzhong Duan @ 2026-09-24 4:10 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: dave.hansen, tglx, mingo, bp, hpa, dave.hansen, kas,
rick.p.edgecombe, jgg, nicolinc, aik, aneesh.kumar, seanjc,
pbonzini, yilun.xu, chao.gao, vishal.l.verma, xiaoyao.li,
kevin.tian, chao.p.peng
TDX Connect's TDG.MMIO.ACCEPT interface identifies an MMIO range
by its index in the Device Interface Report. Preserve this report
index when building pci_tsm_mmio entries.
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
drivers/pci/tsm/core.c | 1 +
include/linux/pci-tsm.h | 1 +
2 files changed, 2 insertions(+)
diff --git a/drivers/pci/tsm/core.c b/drivers/pci/tsm/core.c
index 19ad35f2da4a..3efb9c10d44b 100644
--- a/drivers/pci/tsm/core.c
+++ b/drivers/pci/tsm/core.c
@@ -754,6 +754,7 @@ struct pci_tsm_mmio *pci_tsm_mmio_alloc(struct pci_dev *pdev,
entry->res.start = range.start;
entry->res.end = range.end;
entry->tsm_offset = tsm_offset;
+ entry->index = i;
mmio->nr++;
}
diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
index be9f78ca2c1a..e5d9d7af2701 100644
--- a/include/linux/pci-tsm.h
+++ b/include/linux/pci-tsm.h
@@ -165,6 +165,7 @@ struct pci_tsm_pf0 {
struct pci_tsm_mmio_entry {
struct resource res;
u64 tsm_offset;
+ int index;
};
struct pci_tsm_mmio {
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [RFC PATCH 08/15] x86/tdx: Add TDG.MMIO.ACCEPT module call wrapper for TDX Connect
2026-09-24 4:10 [RFC PATCH 00/15] PCI/TSM: coco/tdx-guest: Implement TDX-Connect PCIe TDISP (phase2) Zhenzhong Duan
` (6 preceding siblings ...)
2026-09-24 4:10 ` [RFC PATCH 07/15] PCI/TSM: Track Device Interface Report MMIO range index Zhenzhong Duan
@ 2026-09-24 4:10 ` Zhenzhong Duan
2026-09-24 23:41 ` Edgecombe, Rick P
2026-09-24 4:10 ` [RFC PATCH 09/15] virt: tdx-guest: Capture the TDI report during device lock Zhenzhong Duan
` (6 subsequent siblings)
14 siblings, 1 reply; 19+ messages in thread
From: Zhenzhong Duan @ 2026-09-24 4:10 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: dave.hansen, tglx, mingo, bp, hpa, dave.hansen, kas,
rick.p.edgecombe, jgg, nicolinc, aik, aneesh.kumar, seanjc,
pbonzini, yilun.xu, chao.gao, vishal.l.verma, xiaoyao.li,
kevin.tian, chao.p.peng
TDG.MMIO.ACCEPT verifies and accepts a pending private MMIO range for
a trusted device.
The MMIO range to be accepted can be a sub-range of the MMIO ranges
originally defined in the Device Interface Report. Because the TDX
module caches these ranges from the Device Interface Report, the guest
does not need to pass the explicit physical start and end addresses of
the MMIO range. Instead, it only needs to provide the target MMIO
range index, the offset within that range, and the size to be accepted.
Translate TDX module return codes into Linux errno values.
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
arch/x86/coco/tdx/tdx_connect.c | 38 +++++++++++++++++++++++++++++++
arch/x86/include/asm/shared/tdx.h | 1 +
arch/x86/include/asm/tdx.h | 1 +
3 files changed, 40 insertions(+)
diff --git a/arch/x86/coco/tdx/tdx_connect.c b/arch/x86/coco/tdx/tdx_connect.c
index 1409b1a30cc6..09a92fe23357 100644
--- a/arch/x86/coco/tdx/tdx_connect.c
+++ b/arch/x86/coco/tdx/tdx_connect.c
@@ -71,3 +71,41 @@ int tdx_mcall_tdi_read(u64 func_id, u64 field, u64 *value)
return tdx_mcall_tdi_to_errno(ret);
}
EXPORT_SYMBOL_FOR_MODULES(tdx_mcall_tdi_read, "tdx-guest");
+
+/**
+ * tdx_mcall_mmio_accept() - Accept a pending private MMIO mapping of a
+ * Trust Device Interface (TDI) instance
+ * @func_id: Function identifier specifying the TDI instance
+ * @index: MMIO range index from the Device Interface Report
+ * @pg_offset: Range offset to start accepting the subrange from, in pages
+ * @page_cnt: Count of pages to accept
+ * @gpa: GPA base address the subrange mapped to
+ *
+ * Verify and accept a pending private MMIO mapping. Upon success, the MMIO
+ * pages are set as mapped in the TDX module.
+ *
+ * Return 0 on success, -EINVAL for unaligned GPA, -ENXIO for invalid operands,
+ * -EBUSY for busy operation, -ENODEV for TDI not present or invalid metadata,
+ * or -EIO on other TDCALL failures.
+ *
+ */
+int tdx_mcall_mmio_accept(u64 func_id, u64 index, u32 pg_offset, u32 page_cnt, phys_addr_t gpa)
+{
+ struct tdx_module_args args = {
+ .rcx = gpa | TDX_PS_4K,
+ .rdx = index,
+ .r8 = func_id,
+ .r9 = (u64)pg_offset << 32 | page_cnt,
+ };
+ u64 ret;
+
+ if (!IS_ALIGNED(gpa, PAGE_SIZE))
+ return -EINVAL;
+
+ ret = __tdcall_ret(TDG_MMIO_ACCEPT, &args);
+ if (!ret)
+ return 0;
+
+ return tdx_mcall_tdi_to_errno(ret);
+}
+EXPORT_SYMBOL_FOR_MODULES(tdx_mcall_mmio_accept, "tdx-guest");
diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h
index 499103f7a01b..ce80fb2116fb 100644
--- a/arch/x86/include/asm/shared/tdx.h
+++ b/arch/x86/include/asm/shared/tdx.h
@@ -21,6 +21,7 @@
#define TDG_VM_RD 7
#define TDG_VM_WR 8
#define TDG_TDI_READ 67
+#define TDG_MMIO_ACCEPT 71
/* TDX TD attributes */
#define TDX_TD_ATTR_DEBUG_BIT 0
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 71cd701d346f..e6493f3bc0d1 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -155,6 +155,7 @@ struct tdcm_rsp_check_teeio_supp {
u64 tdx_hcall_tdcm(u16 devid, void *buf, size_t size, u8 vector);
int tdx_mcall_tdi_read(u64 func_id, u64 field, u64 *value);
+int tdx_mcall_mmio_accept(u64 func_id, u64 index, u32 pg_offset, u32 page_cnt, phys_addr_t gpa);
#endif
void __init tdx_dump_attributes(u64 td_attr);
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [RFC PATCH 08/15] x86/tdx: Add TDG.MMIO.ACCEPT module call wrapper for TDX Connect
2026-09-24 4:10 ` [RFC PATCH 08/15] x86/tdx: Add TDG.MMIO.ACCEPT module call wrapper for TDX Connect Zhenzhong Duan
@ 2026-09-24 23:41 ` Edgecombe, Rick P
2026-09-25 0:02 ` Edgecombe, Rick P
0 siblings, 1 reply; 19+ messages in thread
From: Edgecombe, Rick P @ 2026-09-24 23:41 UTC (permalink / raw)
To: linux-coco, linux-kernel, Duan, Zhenzhong, x86
Cc: Li, Xiaoyao, Hansen, Dave, dave.hansen, kas, mingo, seanjc,
pbonzini, Peng, Chao P, Verma, Vishal L, jgg, aneesh.kumar, Tian,
Kevin, hpa, tglx, nicolinc, bp, Gao, Chao, aik, yilun.xu
On Thu, 2026-09-24 at 12:10 +0800, Zhenzhong Duan wrote:
> TDG.MMIO.ACCEPT verifies and accepts a pending private MMIO range for
> a trusted device.
>
> The MMIO range to be accepted can be a sub-range of the MMIO ranges
> originally defined in the Device Interface Report. Because the TDX
> module caches these ranges from the Device Interface Report, the guest
> does not need to pass the explicit physical start and end addresses of
> the MMIO range. Instead, it only needs to provide the target MMIO
> range index, the offset within that range, and the size to be accepted.
>
> Translate TDX module return codes into Linux errno values.
>
> Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
We are going to need a lot more info about what this TDG call is doing. Look at
the host side seamcall wrapper commits for examples.
> ---
> arch/x86/coco/tdx/tdx_connect.c | 38 +++++++++++++++++++++++++++++++
> arch/x86/include/asm/shared/tdx.h | 1 +
> arch/x86/include/asm/tdx.h | 1 +
> 3 files changed, 40 insertions(+)
>
> diff --git a/arch/x86/coco/tdx/tdx_connect.c b/arch/x86/coco/tdx/tdx_connect.c
> index 1409b1a30cc6..09a92fe23357 100644
> --- a/arch/x86/coco/tdx/tdx_connect.c
> +++ b/arch/x86/coco/tdx/tdx_connect.c
> @@ -71,3 +71,41 @@ int tdx_mcall_tdi_read(u64 func_id, u64 field, u64 *value)
> return tdx_mcall_tdi_to_errno(ret);
> }
> EXPORT_SYMBOL_FOR_MODULES(tdx_mcall_tdi_read, "tdx-guest");
> +
> +/**
> + * tdx_mcall_mmio_accept() - Accept a pending private MMIO mapping of a
> + * Trust Device Interface (TDI) instance
> + * @func_id: Function identifier specifying the TDI instance
> + * @index: MMIO range index from the Device Interface Report
> + * @pg_offset: Range offset to start accepting the subrange from, in pages
> + * @page_cnt: Count of pages to accept
> + * @gpa: GPA base address the subrange mapped to
> + *
> + * Verify and accept a pending private MMIO mapping. Upon success, the MMIO
> + * pages are set as mapped in the TDX module.
> + *
> + * Return 0 on success, -EINVAL for unaligned GPA, -ENXIO for invalid operands,
> + * -EBUSY for busy operation, -ENODEV for TDI not present or invalid metadata,
> + * or -EIO on other TDCALL failures.
> + *
> + */
> +int tdx_mcall_mmio_accept(u64 func_id, u64 index, u32 pg_offset, u32 page_cnt, phys_addr_t gpa)
> +{
offset is 0 for all callers in this series. So I'd think to drop it unless there
is some other code coming very very soon.
Can we turn some of these others into proper types? Why not pass struct
pci_tsm_mmio_entry or struct pci_dev pointers instead of raw unsigned ints?
Actually, the only caller just accepts the whole pci_tsm_mmio_entry, so you just
need:
int tdx_mcall_mmio_accept(struct pci_dev *dev, struct pci_tsm_mmio_entry *entry)
What do you think?
> + struct tdx_module_args args = {
> + .rcx = gpa | TDX_PS_4K,
Always 4KB? Needs an explanation in the log at least.
> + .rdx = index,
> + .r8 = func_id,
> + .r9 = (u64)pg_offset << 32 | page_cnt,
> + };
> + u64 ret;
> +
> + if (!IS_ALIGNED(gpa, PAGE_SIZE))
> + return -EINVAL;
> +
> + ret = __tdcall_ret(TDG_MMIO_ACCEPT, &args);
> + if (!ret)
> + return 0;
> +
> + return tdx_mcall_tdi_to_errno(ret);
> +}
> +EXPORT_SYMBOL_FOR_MODULES(tdx_mcall_mmio_accept, "tdx-guest");
> diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h
> index 499103f7a01b..ce80fb2116fb 100644
> --- a/arch/x86/include/asm/shared/tdx.h
> +++ b/arch/x86/include/asm/shared/tdx.h
> @@ -21,6 +21,7 @@
> #define TDG_VM_RD 7
> #define TDG_VM_WR 8
> #define TDG_TDI_READ 67
> +#define TDG_MMIO_ACCEPT 71
>
> /* TDX TD attributes */
> #define TDX_TD_ATTR_DEBUG_BIT 0
> diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
> index 71cd701d346f..e6493f3bc0d1 100644
> --- a/arch/x86/include/asm/tdx.h
> +++ b/arch/x86/include/asm/tdx.h
> @@ -155,6 +155,7 @@ struct tdcm_rsp_check_teeio_supp {
>
> u64 tdx_hcall_tdcm(u16 devid, void *buf, size_t size, u8 vector);
> int tdx_mcall_tdi_read(u64 func_id, u64 field, u64 *value);
> +int tdx_mcall_mmio_accept(u64 func_id, u64 index, u32 pg_offset, u32 page_cnt, phys_addr_t gpa);
> #endif
>
> void __init tdx_dump_attributes(u64 td_attr);
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [RFC PATCH 08/15] x86/tdx: Add TDG.MMIO.ACCEPT module call wrapper for TDX Connect
2026-09-24 23:41 ` Edgecombe, Rick P
@ 2026-09-25 0:02 ` Edgecombe, Rick P
0 siblings, 0 replies; 19+ messages in thread
From: Edgecombe, Rick P @ 2026-09-25 0:02 UTC (permalink / raw)
To: linux-coco, linux-kernel, Duan, Zhenzhong, x86
Cc: Li, Xiaoyao, Hansen, Dave, dave.hansen, kas, mingo, seanjc,
pbonzini, Peng, Chao P, Verma, Vishal L, jgg, aneesh.kumar, Tian,
Kevin, hpa, tglx, nicolinc, bp, Gao, Chao, aik, yilun.xu
On Thu, 2026-09-24 at 16:41 -0700, Rick Edgecombe wrote:
> > Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
>
> We are going to need a lot more info about what this TDG call is doing. Look
> at the host side seamcall wrapper commits for examples.
BTW, what is the best source of info on these new TDX connect TDG calls. I have
an old "FAS" doc that has info on the specific args, but it says it's a draft.
^ permalink raw reply [flat|nested] 19+ messages in thread
* [RFC PATCH 09/15] virt: tdx-guest: Capture the TDI report during device lock
2026-09-24 4:10 [RFC PATCH 00/15] PCI/TSM: coco/tdx-guest: Implement TDX-Connect PCIe TDISP (phase2) Zhenzhong Duan
` (7 preceding siblings ...)
2026-09-24 4:10 ` [RFC PATCH 08/15] x86/tdx: Add TDG.MMIO.ACCEPT module call wrapper for TDX Connect Zhenzhong Duan
@ 2026-09-24 4:10 ` Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 10/15] virt: tdx-guest: Set up and accept private MMIO ranges Zhenzhong Duan
` (5 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Zhenzhong Duan @ 2026-09-24 4:10 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: dave.hansen, tglx, mingo, bp, hpa, dave.hansen, kas,
rick.p.edgecombe, jgg, nicolinc, aik, aneesh.kumar, seanjc,
pbonzini, yilun.xu, chao.gao, vishal.l.verma, xiaoyao.li,
kevin.tian, chao.p.peng
Retrieve the Trusted Device Interface report through
TDCM_OP_GET_TDI_REPORT after binding the device.
Store the report in the PCI TSM device evidence object and free it
during device unlock.
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
drivers/virt/coco/tdx-guest/connect.c | 45 +++++++++++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/drivers/virt/coco/tdx-guest/connect.c b/drivers/virt/coco/tdx-guest/connect.c
index e36515fc43ef..d5934e8c0089 100644
--- a/drivers/virt/coco/tdx-guest/connect.c
+++ b/drivers/virt/coco/tdx-guest/connect.c
@@ -217,8 +217,35 @@ static int tdx_tdi_read_state(struct tdx_devsec *tdevsec, u8 *state)
return ret;
}
+static u8 *tdx_tdi_get_report(struct tdx_devsec *tdevsec, u32 *report_sz)
+{
+ struct pci_dev *pdev = tdevsec->pci.base_tsm.pdev;
+ u8 *report;
+ int ret;
+
+ struct tdcm_ctx *tdcm __free(tdx_tdcm_free) =
+ tdx_tdcm_alloc(pdev, TDCM_OP_GET_TDI_REPORT, 0, MAX_TDI_REPORT_SIZE);
+ if (IS_ERR(tdcm))
+ return ERR_CAST(tdcm);
+
+ ret = tdx_tdcm_run(tdcm);
+ if (ret)
+ return ERR_PTR(ret);
+
+ report = kmemdup(to_tdcm_rsp_data(tdcm), tdcm->rsp_data_sz, GFP_KERNEL);
+ if (!report)
+ return ERR_PTR(-ENOMEM);
+
+ *report_sz = tdcm->rsp_data_sz;
+ print_hex_dump_debug("tdi_report: ", DUMP_PREFIX_OFFSET, 16, 1, report, *report_sz, false);
+ return report;
+}
+
static struct pci_tsm *tdx_devsec_lock(struct tsm_dev *tsm_dev, struct pci_dev *pdev)
{
+ struct device_evidence_object *tsm_report;
+ struct device_evidence *evidence;
+ u32 report_sz;
u8 state;
int ret;
@@ -237,6 +264,10 @@ static struct pci_tsm *tdx_devsec_lock(struct tsm_dev *tsm_dev, struct pci_dev *
if (IS_ERR(tdevsec_bind))
return ERR_CAST(tdevsec_bind);
+ u8 *report __free(kfree) = tdx_tdi_get_report(tdevsec, &report_sz);
+ if (IS_ERR(report))
+ return ERR_CAST(report);
+
ret = tdx_tdi_read_state(tdevsec, &state);
if (ret)
return ERR_PTR(ret);
@@ -244,6 +275,17 @@ static struct pci_tsm *tdx_devsec_lock(struct tsm_dev *tsm_dev, struct pci_dev *
if (state != TDI_STATE_CONFIG_LOCKED)
return ERR_PTR(-EIO);
+ evidence = device_evidence_create(0, HASH_ALGO_SHA384);
+
+ if (!evidence)
+ return ERR_PTR(-ENOMEM);
+
+ tsm_report = &evidence->obj[DEVICE_EVIDENCE_TYPE_REPORT];
+ tsm_report->data = no_free_ptr(report);
+ tsm_report->len = report_sz;
+
+ tdevsec->pci.base_tsm.evidence = evidence;
+
retain_and_null_ptr(tdevsec_bind);
return &no_free_ptr(tdevsec)->pci.base_tsm;
@@ -252,10 +294,13 @@ static struct pci_tsm *tdx_devsec_lock(struct tsm_dev *tsm_dev, struct pci_dev *
static void tdx_devsec_unlock(struct pci_tsm *tsm)
{
struct tdx_devsec *tdevsec = to_tdx_devsec(tsm);
+ struct device_evidence *evidence = tsm->evidence;
if (WARN_ON(tdx_tdi_unbind_dev(tdevsec)))
return;
+ kfree(evidence->obj[DEVICE_EVIDENCE_TYPE_REPORT].data);
+ kfree(evidence);
kfree(tdevsec);
}
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [RFC PATCH 10/15] virt: tdx-guest: Set up and accept private MMIO ranges
2026-09-24 4:10 [RFC PATCH 00/15] PCI/TSM: coco/tdx-guest: Implement TDX-Connect PCIe TDISP (phase2) Zhenzhong Duan
` (8 preceding siblings ...)
2026-09-24 4:10 ` [RFC PATCH 09/15] virt: tdx-guest: Capture the TDI report during device lock Zhenzhong Duan
@ 2026-09-24 4:10 ` Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 11/15] x86/tdx: Add TDG.TDI.START module call wrapper for TDX Connect Zhenzhong Duan
` (4 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Zhenzhong Duan @ 2026-09-24 4:10 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: dave.hansen, tglx, mingo, bp, hpa, dave.hansen, kas,
rick.p.edgecombe, jgg, nicolinc, aik, aneesh.kumar, seanjc,
pbonzini, yilun.xu, chao.gao, vishal.l.verma, xiaoyao.li,
kevin.tian, chao.p.peng
Build private MMIO descriptors from the TDI report using the PCI TSM
MMIO helpers and accept each private range through TDG.MMIO.ACCEPT.
Keep the descriptors while the device remains locked and tear them
down on acceptance failure and during unlock.
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
drivers/virt/coco/tdx-guest/connect.c | 75 +++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
diff --git a/drivers/virt/coco/tdx-guest/connect.c b/drivers/virt/coco/tdx-guest/connect.c
index d5934e8c0089..fe8d76ba6f81 100644
--- a/drivers/virt/coco/tdx-guest/connect.c
+++ b/drivers/virt/coco/tdx-guest/connect.c
@@ -241,6 +241,67 @@ static u8 *tdx_tdi_get_report(struct tdx_devsec *tdevsec, u32 *report_sz)
return report;
}
+static struct tdx_devsec *tdx_tdi_mmio_setup(struct tdx_devsec *tdevsec)
+{
+ struct pci_tsm_devsec *devsec_tsm = &tdevsec->pci;
+ struct pci_dev *pdev = devsec_tsm->base_tsm.pdev;
+ int rc;
+
+ struct pci_tsm_mmio *mmio __free(kfree) = pci_tsm_mmio_alloc(pdev, TDISP_OFFSET_RELATIVE);
+ if (!mmio)
+ return ERR_PTR(-EINVAL);
+
+ rc = pci_tsm_mmio_setup(pdev, mmio);
+ if (rc)
+ return ERR_PTR(rc);
+
+ devsec_tsm->mmio = no_free_ptr(mmio);
+ return tdevsec;
+}
+
+static void tdx_tdi_mmio_teardown(struct tdx_devsec *tdevsec)
+{
+ struct pci_tsm_devsec *devsec_tsm = &tdevsec->pci;
+
+ if (!devsec_tsm->mmio)
+ return;
+
+ pci_tsm_mmio_teardown(devsec_tsm->mmio);
+ kfree(devsec_tsm->mmio);
+ devsec_tsm->mmio = NULL;
+}
+DEFINE_FREE(tdx_tdi_mmio_teardown, struct tdx_devsec *,
+ if (!IS_ERR_OR_NULL(_T)) tdx_tdi_mmio_teardown(_T))
+
+static int tdx_tdi_mmio_accept(struct tdx_devsec *tdevsec)
+{
+ struct pci_dev *pdev = tdevsec->pci.base_tsm.pdev;
+ const struct pci_tsm_mmio *mmio;
+ u32 i;
+
+ mmio = tdevsec->pci.mmio;
+
+ for (i = 0; i < mmio->nr; i++) {
+ const struct pci_tsm_mmio_entry *entry = &mmio->mmio[i];
+ u32 pages = resource_size(&entry->res) >> PAGE_SHIFT;
+ phys_addr_t gpa = entry->res.start;
+ int ret;
+
+ pci_dbg(pdev, "accept MMIO entry %d %pR gpa=0x%llx\n",
+ entry->index, &entry->res, gpa);
+
+ /* Accept the whole range */
+ ret = tdx_mcall_mmio_accept(pci_dev_id(pdev), entry->index, 0, pages, gpa);
+ if (ret) {
+ pci_err(pdev, "Failed to accept MMIO entry %d (gpa=0x%llx), ret=%d\n",
+ entry->index, gpa, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
static struct pci_tsm *tdx_devsec_lock(struct tsm_dev *tsm_dev, struct pci_dev *pdev)
{
struct device_evidence_object *tsm_report;
@@ -299,6 +360,8 @@ static void tdx_devsec_unlock(struct pci_tsm *tsm)
if (WARN_ON(tdx_tdi_unbind_dev(tdevsec)))
return;
+ tdx_tdi_mmio_teardown(tdevsec);
+
kfree(evidence->obj[DEVICE_EVIDENCE_TYPE_REPORT].data);
kfree(evidence);
kfree(tdevsec);
@@ -306,6 +369,18 @@ static void tdx_devsec_unlock(struct pci_tsm *tsm)
static int tdx_devsec_run(struct pci_dev *pdev)
{
+ struct tdx_devsec *tdevsec = to_tdx_devsec(pdev->tsm);
+ int ret;
+
+ struct tdx_devsec *tdevsec_mmio __free(tdx_tdi_mmio_teardown) =
+ tdx_tdi_mmio_setup(tdevsec);
+ if (IS_ERR(tdevsec_mmio))
+ return PTR_ERR(tdevsec_mmio);
+
+ ret = tdx_tdi_mmio_accept(tdevsec);
+ if (ret)
+ return ret;
+
return -EOPNOTSUPP;
}
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [RFC PATCH 11/15] x86/tdx: Add TDG.TDI.START module call wrapper for TDX Connect
2026-09-24 4:10 [RFC PATCH 00/15] PCI/TSM: coco/tdx-guest: Implement TDX-Connect PCIe TDISP (phase2) Zhenzhong Duan
` (9 preceding siblings ...)
2026-09-24 4:10 ` [RFC PATCH 10/15] virt: tdx-guest: Set up and accept private MMIO ranges Zhenzhong Duan
@ 2026-09-24 4:10 ` Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 12/15] virt: tdx-guest: Support Trust Device Interface (TDI) activation Zhenzhong Duan
` (3 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Zhenzhong Duan @ 2026-09-24 4:10 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: dave.hansen, tglx, mingo, bp, hpa, dave.hansen, kas,
rick.p.edgecombe, jgg, nicolinc, aik, aneesh.kumar, seanjc,
pbonzini, yilun.xu, chao.gao, vishal.l.verma, xiaoyao.li,
kevin.tian, chao.p.peng
Add tdx_mcall_tdi_start() to signal that the TD is ready to start a TDI
instance. After a successful module call, the TDX module authorizes the
host to start the TDI with the TDH.TDI.START seamcall.
Translate TDX module return codes into Linux errno values.
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
arch/x86/coco/tdx/tdx_connect.c | 27 +++++++++++++++++++++++++++
arch/x86/include/asm/shared/tdx.h | 1 +
arch/x86/include/asm/tdx.h | 1 +
3 files changed, 29 insertions(+)
diff --git a/arch/x86/coco/tdx/tdx_connect.c b/arch/x86/coco/tdx/tdx_connect.c
index 09a92fe23357..3ef10b1df401 100644
--- a/arch/x86/coco/tdx/tdx_connect.c
+++ b/arch/x86/coco/tdx/tdx_connect.c
@@ -109,3 +109,30 @@ int tdx_mcall_mmio_accept(u64 func_id, u64 index, u32 pg_offset, u32 page_cnt, p
return tdx_mcall_tdi_to_errno(ret);
}
EXPORT_SYMBOL_FOR_MODULES(tdx_mcall_mmio_accept, "tdx-guest");
+
+/**
+ * tdx_mcall_tdi_start() - Authorize TDX module to start the TDI instance
+ * @func_id: Function identifier specifying the TDI instance
+ * @exp_bind_session: Expected bind session ID
+ *
+ * Signal TDX module that TD is ready to start TDI. Upon success, TDX module
+ * allows host to initiate TDI via TDH.TDI.START seamcall.
+ *
+ * Return 0 on success, -ENXIO for invalid operands, -EBUSY for busy operation,
+ * -ENODEV for TDI not present or invalid metadata, or -EIO on other TDCALL failures.
+ */
+int tdx_mcall_tdi_start(u64 func_id, u64 exp_bind_session)
+{
+ struct tdx_module_args args = {
+ .rcx = func_id,
+ .rdx = exp_bind_session,
+ };
+ u64 ret;
+
+ ret = __tdcall(TDG_TDI_START, &args);
+ if (!ret)
+ return 0;
+
+ return tdx_mcall_tdi_to_errno(ret);
+}
+EXPORT_SYMBOL_FOR_MODULES(tdx_mcall_tdi_start, "tdx-guest");
diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h
index ce80fb2116fb..72520f09ef3f 100644
--- a/arch/x86/include/asm/shared/tdx.h
+++ b/arch/x86/include/asm/shared/tdx.h
@@ -21,6 +21,7 @@
#define TDG_VM_RD 7
#define TDG_VM_WR 8
#define TDG_TDI_READ 67
+#define TDG_TDI_START 68
#define TDG_MMIO_ACCEPT 71
/* TDX TD attributes */
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index e6493f3bc0d1..787b3039ef5a 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -156,6 +156,7 @@ struct tdcm_rsp_check_teeio_supp {
u64 tdx_hcall_tdcm(u16 devid, void *buf, size_t size, u8 vector);
int tdx_mcall_tdi_read(u64 func_id, u64 field, u64 *value);
int tdx_mcall_mmio_accept(u64 func_id, u64 index, u32 pg_offset, u32 page_cnt, phys_addr_t gpa);
+int tdx_mcall_tdi_start(u64 func_id, u64 exp_bind_session);
#endif
void __init tdx_dump_attributes(u64 td_attr);
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [RFC PATCH 12/15] virt: tdx-guest: Support Trust Device Interface (TDI) activation
2026-09-24 4:10 [RFC PATCH 00/15] PCI/TSM: coco/tdx-guest: Implement TDX-Connect PCIe TDISP (phase2) Zhenzhong Duan
` (10 preceding siblings ...)
2026-09-24 4:10 ` [RFC PATCH 11/15] x86/tdx: Add TDG.TDI.START module call wrapper for TDX Connect Zhenzhong Duan
@ 2026-09-24 4:10 ` Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 13/15] x86/tdx: Add __tdcall_saved() helper Zhenzhong Duan
` (2 subsequent siblings)
14 siblings, 0 replies; 19+ messages in thread
From: Zhenzhong Duan @ 2026-09-24 4:10 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: dave.hansen, tglx, mingo, bp, hpa, dave.hansen, kas,
rick.p.edgecombe, jgg, nicolinc, aik, aneesh.kumar, seanjc,
pbonzini, yilun.xu, chao.gao, vishal.l.verma, xiaoyao.li,
kevin.tian, chao.p.peng
Store the bind session ID obtained after binding the TDI which is used
by TDG.TDI.START to verify same bind session established by host before
activation.
Add tdx_tdi_start_dev() to signal guest readiness to the TDX module and
issue the TDCM_OP_START_TDI hypercall for the host to complete activation.
The run callback accepts private MMIO ranges, starts the TDI, and verifies
that it reaches TDI_STATE_RUN, finally notifies DMA sub-system device's
ready to DMA to private memory. Note that private DMA is enabled separately
during driver attach by accepting the default DMAR entry.
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
drivers/virt/coco/tdx-guest/connect.c | 45 ++++++++++++++++++++++++++-
1 file changed, 44 insertions(+), 1 deletion(-)
diff --git a/drivers/virt/coco/tdx-guest/connect.c b/drivers/virt/coco/tdx-guest/connect.c
index fe8d76ba6f81..a9d41fd38b64 100644
--- a/drivers/virt/coco/tdx-guest/connect.c
+++ b/drivers/virt/coco/tdx-guest/connect.c
@@ -130,6 +130,8 @@ DEFINE_FREE(tdx_tdcm_free, struct tdcm_ctx *,
struct tdx_devsec {
struct pci_tsm_devsec pci;
+
+ u64 bind_session_id;
};
static struct tdx_devsec *to_tdx_devsec(struct pci_tsm *tsm)
@@ -202,6 +204,7 @@ enum tdi_state {
enum tdi_field_code {
TDI_GET_TDISP_STATE = 2,
+ TDI_GET_BIND_SESSION_ID = 5,
};
static int tdx_tdi_read_state(struct tdx_devsec *tdevsec, u8 *state)
@@ -302,10 +305,27 @@ static int tdx_tdi_mmio_accept(struct tdx_devsec *tdevsec)
return 0;
}
+static int tdx_tdi_start_dev(struct tdx_devsec *tdevsec)
+{
+ struct pci_dev *pdev = tdevsec->pci.base_tsm.pdev;
+ int ret;
+
+ ret = tdx_mcall_tdi_start(pci_dev_id(pdev), tdevsec->bind_session_id);
+ if (ret)
+ return ret;
+
+ struct tdcm_ctx *tdcm __free(tdx_tdcm_free) = tdx_tdcm_alloc(pdev, TDCM_OP_START_TDI, 0, 0);
+ if (IS_ERR(tdcm))
+ return PTR_ERR(tdcm);
+
+ return tdx_tdcm_run(tdcm);
+}
+
static struct pci_tsm *tdx_devsec_lock(struct tsm_dev *tsm_dev, struct pci_dev *pdev)
{
struct device_evidence_object *tsm_report;
struct device_evidence *evidence;
+ u64 session_id;
u32 report_sz;
u8 state;
int ret;
@@ -325,6 +345,12 @@ static struct pci_tsm *tdx_devsec_lock(struct tsm_dev *tsm_dev, struct pci_dev *
if (IS_ERR(tdevsec_bind))
return ERR_CAST(tdevsec_bind);
+ ret = tdx_mcall_tdi_read(pci_dev_id(pdev), TDI_GET_BIND_SESSION_ID, &session_id);
+ if (ret)
+ return ERR_PTR(ret);
+
+ tdevsec->bind_session_id = session_id;
+
u8 *report __free(kfree) = tdx_tdi_get_report(tdevsec, &report_sz);
if (IS_ERR(report))
return ERR_CAST(report);
@@ -362,6 +388,7 @@ static void tdx_devsec_unlock(struct pci_tsm *tsm)
tdx_tdi_mmio_teardown(tdevsec);
+ dma_set_cc_private(&tsm->pdev->dev, false);
kfree(evidence->obj[DEVICE_EVIDENCE_TYPE_REPORT].data);
kfree(evidence);
kfree(tdevsec);
@@ -370,6 +397,7 @@ static void tdx_devsec_unlock(struct pci_tsm *tsm)
static int tdx_devsec_run(struct pci_dev *pdev)
{
struct tdx_devsec *tdevsec = to_tdx_devsec(pdev->tsm);
+ u8 state;
int ret;
struct tdx_devsec *tdevsec_mmio __free(tdx_tdi_mmio_teardown) =
@@ -381,7 +409,22 @@ static int tdx_devsec_run(struct pci_dev *pdev)
if (ret)
return ret;
- return -EOPNOTSUPP;
+ ret = tdx_tdi_start_dev(tdevsec);
+ if (ret)
+ return ret;
+
+ ret = tdx_tdi_read_state(tdevsec, &state);
+ if (ret)
+ return ret;
+
+ if (state != TDI_STATE_RUN) {
+ pci_err(pdev, "TDI failed to reach RUN state, current state: 0x%x\n", state);
+ return -EIO;
+ }
+
+ dma_set_cc_private(&pdev->dev, true);
+ retain_and_null_ptr(tdevsec_mmio);
+ return 0;
}
static struct pci_tsm_ops tdx_devsec_ops = {
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [RFC PATCH 13/15] x86/tdx: Add __tdcall_saved() helper
2026-09-24 4:10 [RFC PATCH 00/15] PCI/TSM: coco/tdx-guest: Implement TDX-Connect PCIe TDISP (phase2) Zhenzhong Duan
` (11 preceding siblings ...)
2026-09-24 4:10 ` [RFC PATCH 12/15] virt: tdx-guest: Support Trust Device Interface (TDI) activation Zhenzhong Duan
@ 2026-09-24 4:10 ` Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 14/15] x86/tdx: Add TDG.DMAR.ACCEPT module call wrapper for TDX Connect Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 15/15] virt: tdx-guest: Accept default DMAR entry during PCI driver attach Zhenzhong Duan
14 siblings, 0 replies; 19+ messages in thread
From: Zhenzhong Duan @ 2026-09-24 4:10 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: dave.hansen, tglx, mingo, bp, hpa, dave.hansen, kas,
rick.p.edgecombe, jgg, nicolinc, aik, aneesh.kumar, seanjc,
pbonzini, yilun.xu, chao.gao, vishal.l.verma, xiaoyao.li,
kevin.tian, chao.p.peng
The TDX Connect ABI defines TDCALL leaves that use registers up to r15
in struct tdx_module_args as input, without returning output arguments.
Add __tdcall_saved() for these leaves.
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
arch/x86/coco/tdx/tdcall.S | 17 +++++++++++++++++
arch/x86/include/asm/shared/tdx.h | 1 +
2 files changed, 18 insertions(+)
diff --git a/arch/x86/coco/tdx/tdcall.S b/arch/x86/coco/tdx/tdcall.S
index 52d9786da308..1dca01909dee 100644
--- a/arch/x86/coco/tdx/tdcall.S
+++ b/arch/x86/coco/tdx/tdcall.S
@@ -44,6 +44,23 @@ SYM_FUNC_START(__tdcall_ret)
TDX_MODULE_CALL host=0 ret=1
SYM_FUNC_END(__tdcall_ret)
+/*
+ * __tdcall_saved() - Used by TDX guests to request services from the TDX
+ * module (does not include VMM services) using TDCALL instruction.
+ *
+ * __tdcall_saved() function ABI:
+ *
+ * @fn (RDI) - TDCALL Leaf ID, moved to RAX
+ * @args (RSI) - struct tdx_module_args for input
+ *
+ * All registers in @args are used as input registers.
+ *
+ * Return status of TDCALL via RAX.
+ */
+SYM_FUNC_START(__tdcall_saved)
+ TDX_MODULE_CALL host=0 saved=1
+SYM_FUNC_END(__tdcall_saved)
+
/*
* __tdcall_saved_ret() - Used by TDX guests to request services from the
* TDX module (including VMM services) using TDCALL instruction, with
diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h
index 72520f09ef3f..90f2842f4253 100644
--- a/arch/x86/include/asm/shared/tdx.h
+++ b/arch/x86/include/asm/shared/tdx.h
@@ -153,6 +153,7 @@ struct tdx_module_args {
/* Used to communicate with the TDX module */
u64 __tdcall(u64 fn, struct tdx_module_args *args);
u64 __tdcall_ret(u64 fn, struct tdx_module_args *args);
+u64 __tdcall_saved(u64 fn, struct tdx_module_args *args);
u64 __tdcall_saved_ret(u64 fn, struct tdx_module_args *args);
/* Used to request services from the VMM */
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [RFC PATCH 14/15] x86/tdx: Add TDG.DMAR.ACCEPT module call wrapper for TDX Connect
2026-09-24 4:10 [RFC PATCH 00/15] PCI/TSM: coco/tdx-guest: Implement TDX-Connect PCIe TDISP (phase2) Zhenzhong Duan
` (12 preceding siblings ...)
2026-09-24 4:10 ` [RFC PATCH 13/15] x86/tdx: Add __tdcall_saved() helper Zhenzhong Duan
@ 2026-09-24 4:10 ` Zhenzhong Duan
2026-09-24 4:10 ` [RFC PATCH 15/15] virt: tdx-guest: Accept default DMAR entry during PCI driver attach Zhenzhong Duan
14 siblings, 0 replies; 19+ messages in thread
From: Zhenzhong Duan @ 2026-09-24 4:10 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: dave.hansen, tglx, mingo, bp, hpa, dave.hansen, kas,
rick.p.edgecombe, jgg, nicolinc, aik, aneesh.kumar, seanjc,
pbonzini, yilun.xu, chao.gao, vishal.l.verma, xiaoyao.li,
kevin.tian, chao.p.peng
TDG.DMAR.ACCEPT updates the PASID table entry of the TDI as present to
allow DMA access to the TD private memory.
For TDG.DMAR.ACCEPT, the R8-R15 input registers are reserved and must
be zero. Use a zero-initialized argument structure with __tdcall_saved()
so the complete TDCALL input register set is loaded with the required
values.
Translate TDX module return codes into Linux errno values.
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
arch/x86/coco/tdx/tdx_connect.c | 28 ++++++++++++++++++++++++++++
arch/x86/include/asm/shared/tdx.h | 1 +
arch/x86/include/asm/tdx.h | 1 +
3 files changed, 30 insertions(+)
diff --git a/arch/x86/coco/tdx/tdx_connect.c b/arch/x86/coco/tdx/tdx_connect.c
index 3ef10b1df401..a03b1da9a52a 100644
--- a/arch/x86/coco/tdx/tdx_connect.c
+++ b/arch/x86/coco/tdx/tdx_connect.c
@@ -136,3 +136,31 @@ int tdx_mcall_tdi_start(u64 func_id, u64 exp_bind_session)
return tdx_mcall_tdi_to_errno(ret);
}
EXPORT_SYMBOL_FOR_MODULES(tdx_mcall_tdi_start, "tdx-guest");
+
+/**
+ * tdx_mcall_dmar_accept() - Accept PASID table entry of a TDI instance
+ * @func_id: Function identifier specifying the TDI instance
+ * @target: DMAR target, 0 for non-partitioned TD or L1, 1-3 for L2 VM1-VM3,
+ * other value reserved
+ *
+ * Update the PASID table entry of the TDI to mark the DMA Remapping (DMAR)
+ * state as present, allowing DMA access to the TD private memory.
+ *
+ * Return 0 on success, -ENXIO for invalid operands, -EBUSY for busy operation,
+ * -ENODEV for TDI not present or invalid metadata, or -EIO on other TDCALL failures.
+ */
+int tdx_mcall_dmar_accept(u64 func_id, u64 target)
+{
+ struct tdx_module_args args = {
+ .rcx = func_id,
+ .rdx = target,
+ };
+ u64 ret;
+
+ ret = __tdcall_saved(TDG_DMAR_ACCEPT, &args);
+ if (!ret)
+ return 0;
+
+ return tdx_mcall_tdi_to_errno(ret);
+}
+EXPORT_SYMBOL_FOR_MODULES(tdx_mcall_dmar_accept, "tdx-guest");
diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h
index 90f2842f4253..01387f6b24b3 100644
--- a/arch/x86/include/asm/shared/tdx.h
+++ b/arch/x86/include/asm/shared/tdx.h
@@ -22,6 +22,7 @@
#define TDG_VM_WR 8
#define TDG_TDI_READ 67
#define TDG_TDI_START 68
+#define TDG_DMAR_ACCEPT 70
#define TDG_MMIO_ACCEPT 71
/* TDX TD attributes */
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 787b3039ef5a..118cc58cc909 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -157,6 +157,7 @@ u64 tdx_hcall_tdcm(u16 devid, void *buf, size_t size, u8 vector);
int tdx_mcall_tdi_read(u64 func_id, u64 field, u64 *value);
int tdx_mcall_mmio_accept(u64 func_id, u64 index, u32 pg_offset, u32 page_cnt, phys_addr_t gpa);
int tdx_mcall_tdi_start(u64 func_id, u64 exp_bind_session);
+int tdx_mcall_dmar_accept(u64 func_id, u64 target);
#endif
void __init tdx_dump_attributes(u64 td_attr);
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread* [RFC PATCH 15/15] virt: tdx-guest: Accept default DMAR entry during PCI driver attach
2026-09-24 4:10 [RFC PATCH 00/15] PCI/TSM: coco/tdx-guest: Implement TDX-Connect PCIe TDISP (phase2) Zhenzhong Duan
` (13 preceding siblings ...)
2026-09-24 4:10 ` [RFC PATCH 14/15] x86/tdx: Add TDG.DMAR.ACCEPT module call wrapper for TDX Connect Zhenzhong Duan
@ 2026-09-24 4:10 ` Zhenzhong Duan
14 siblings, 0 replies; 19+ messages in thread
From: Zhenzhong Duan @ 2026-09-24 4:10 UTC (permalink / raw)
To: x86, linux-coco, linux-kernel
Cc: dave.hansen, tglx, mingo, bp, hpa, dave.hansen, kas,
rick.p.edgecombe, jgg, nicolinc, aik, aneesh.kumar, seanjc,
pbonzini, yilun.xu, chao.gao, vishal.l.verma, xiaoyao.li,
kevin.tian, chao.p.peng
Before a TDI can perform DMA to guest private memory, its secure DMA
mapping must be accepted. This happens during driver load stage after
MMIO acceptance and TDI activation.
The enable_dma callback issues TDG.DMAR.ACCEPT to set secure PASID
table entry to present state and target non-partitioned TD.
The secure PASID table entry is cleared in unlock stage implicitly
so disable_dma is a no-op currently.
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
drivers/virt/coco/tdx-guest/connect.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/drivers/virt/coco/tdx-guest/connect.c b/drivers/virt/coco/tdx-guest/connect.c
index a9d41fd38b64..b328caac0a49 100644
--- a/drivers/virt/coco/tdx-guest/connect.c
+++ b/drivers/virt/coco/tdx-guest/connect.c
@@ -427,10 +427,26 @@ static int tdx_devsec_run(struct pci_dev *pdev)
return 0;
}
+static int tdx_devsec_enable_dma(struct pci_dev *pdev)
+{
+ int ret = tdx_mcall_dmar_accept(pci_dev_id(pdev), 0);
+
+ if (ret)
+ pci_err(pdev, "Failed to accept DMAR, ret=%d\n", ret);
+
+ return ret;
+}
+
+static void tdx_devsec_disable_dma(struct pci_dev *pdev)
+{
+}
+
static struct pci_tsm_ops tdx_devsec_ops = {
.lock = tdx_devsec_lock,
.unlock = tdx_devsec_unlock,
.run = tdx_devsec_run,
+ .enable_dma = tdx_devsec_enable_dma,
+ .disable_dma = tdx_devsec_disable_dma,
};
static void devsec_tsm_remove(void *tsm_dev)
--
2.52.0
^ permalink raw reply [flat|nested] 19+ messages in thread