* [PATCH v2 01/31] x86/tdx: Move all TDX error defines into <asm/shared/tdx_errno.h>
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 23:37 ` Edgecombe, Rick P
2026-03-27 16:01 ` [PATCH v2 02/31] x86/virt/tdx: Move bit definitions of TDX_FEATURES0 to public header Xu Yilun
` (29 subsequent siblings)
30 siblings, 1 reply; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
From: "Kirill A. Shutemov" <kirill.shutemov@linux.intel.com>
Today there are two separate locations where TDX error codes are defined:
arch/x86/include/asm/tdx.h
arch/x86/kvm/vmx/tdx_errno.h
They have some overlap that is already defined similarly. Reduce the
duplication and prepare to introduce some helpers for these error codes in
the central place by unifying them. Join them at:
asm/shared/tdx_errno.h
...and update the headers that contained the duplicated definitions to
include the new unified header.
"asm/shared" is used for sharing TDX code between the early compressed
code and the normal kernel code. While the compressed code for the guest
doesn't use these error code header definitions today, it does make the
types of calls that return the values they define. So place the defines in
"shared" location so that it can, but leave such cleanups for future
changes.
Also, adjust BITUL() -> _BITULL() to address 32 bit build errors after the
move.
Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
[enhance log]
Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
Reviewed-by: Chao Gao <chao.gao@intel.com>
---
arch/x86/include/asm/shared/tdx.h | 1 +
.../vmx => include/asm/shared}/tdx_errno.h | 28 +++++++++++++++----
arch/x86/include/asm/tdx.h | 21 --------------
arch/x86/kvm/vmx/tdx.h | 1 -
4 files changed, 23 insertions(+), 28 deletions(-)
rename arch/x86/{kvm/vmx => include/asm/shared}/tdx_errno.h (64%)
diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h
index 8bc074c8d7c6..6a1646fc2b2f 100644
--- a/arch/x86/include/asm/shared/tdx.h
+++ b/arch/x86/include/asm/shared/tdx.h
@@ -4,6 +4,7 @@
#include <linux/bits.h>
#include <linux/types.h>
+#include <asm/shared/tdx_errno.h>
#define TDX_HYPERCALL_STANDARD 0
diff --git a/arch/x86/kvm/vmx/tdx_errno.h b/arch/x86/include/asm/shared/tdx_errno.h
similarity index 64%
rename from arch/x86/kvm/vmx/tdx_errno.h
rename to arch/x86/include/asm/shared/tdx_errno.h
index 6ff4672c4181..8bf6765cf082 100644
--- a/arch/x86/kvm/vmx/tdx_errno.h
+++ b/arch/x86/include/asm/shared/tdx_errno.h
@@ -1,14 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
-/* architectural status code for SEAMCALL */
-
-#ifndef __KVM_X86_TDX_ERRNO_H
-#define __KVM_X86_TDX_ERRNO_H
+#ifndef _ASM_X86_SHARED_TDX_ERRNO_H
+#define _ASM_X86_SHARED_TDX_ERRNO_H
+#include <asm/trapnr.h>
+/* Upper 32 bit of the TDX error code encodes the status */
#define TDX_SEAMCALL_STATUS_MASK 0xFFFFFFFF00000000ULL
/*
- * TDX SEAMCALL Status Codes (returned in RAX)
+ * TDX Status Codes (returned in RAX)
*/
+#define TDX_SUCCESS 0ULL
#define TDX_NON_RECOVERABLE_VCPU 0x4000000100000000ULL
#define TDX_NON_RECOVERABLE_TD 0x4000000200000000ULL
#define TDX_NON_RECOVERABLE_TD_NON_ACCESSIBLE 0x6000000500000000ULL
@@ -17,6 +18,7 @@
#define TDX_OPERAND_INVALID 0xC000010000000000ULL
#define TDX_OPERAND_BUSY 0x8000020000000000ULL
#define TDX_PREVIOUS_TLB_EPOCH_BUSY 0x8000020100000000ULL
+#define TDX_RND_NO_ENTROPY 0x8000020300000000ULL
#define TDX_PAGE_METADATA_INCORRECT 0xC000030000000000ULL
#define TDX_VCPU_NOT_ASSOCIATED 0x8000070200000000ULL
#define TDX_KEY_GENERATION_FAILED 0x8000080000000000ULL
@@ -28,6 +30,20 @@
#define TDX_EPT_ENTRY_STATE_INCORRECT 0xC0000B0D00000000ULL
#define TDX_METADATA_FIELD_NOT_READABLE 0xC0000C0200000000ULL
+/*
+ * SW-defined error codes.
+ *
+ * Bits 47:40 == 0xFF indicate Reserved status code class that never used by
+ * TDX module.
+ */
+#define TDX_ERROR _BITULL(63)
+#define TDX_NON_RECOVERABLE _BITULL(62)
+#define TDX_SW_ERROR (TDX_ERROR | GENMASK_ULL(47, 40))
+#define TDX_SEAMCALL_VMFAILINVALID (TDX_SW_ERROR | _ULL(0xFFFF0000))
+
+#define TDX_SEAMCALL_GP (TDX_SW_ERROR | X86_TRAP_GP)
+#define TDX_SEAMCALL_UD (TDX_SW_ERROR | X86_TRAP_UD)
+
/*
* TDX module operand ID, appears in 31:0 part of error code as
* detail information
@@ -37,4 +53,4 @@
#define TDX_OPERAND_ID_SEPT 0x92
#define TDX_OPERAND_ID_TD_EPOCH 0xa9
-#endif /* __KVM_X86_TDX_ERRNO_H */
+#endif /* _ASM_X86_SHARED_TDX_ERRNO_H */
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 6b338d7f01b7..e040e0467ae4 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -9,29 +9,8 @@
#include <asm/errno.h>
#include <asm/ptrace.h>
-#include <asm/trapnr.h>
#include <asm/shared/tdx.h>
-/*
- * SW-defined error codes.
- *
- * Bits 47:40 == 0xFF indicate Reserved status code class that never used by
- * TDX module.
- */
-#define TDX_ERROR _BITUL(63)
-#define TDX_NON_RECOVERABLE _BITUL(62)
-#define TDX_SW_ERROR (TDX_ERROR | GENMASK_ULL(47, 40))
-#define TDX_SEAMCALL_VMFAILINVALID (TDX_SW_ERROR | _UL(0xFFFF0000))
-
-#define TDX_SEAMCALL_GP (TDX_SW_ERROR | X86_TRAP_GP)
-#define TDX_SEAMCALL_UD (TDX_SW_ERROR | X86_TRAP_UD)
-
-/*
- * TDX module SEAMCALL leaf function error codes
- */
-#define TDX_SUCCESS 0ULL
-#define TDX_RND_NO_ENTROPY 0x8000020300000000ULL
-
#ifndef __ASSEMBLER__
#include <uapi/asm/mce.h>
diff --git a/arch/x86/kvm/vmx/tdx.h b/arch/x86/kvm/vmx/tdx.h
index 45b5183ccb36..ce2720a028ad 100644
--- a/arch/x86/kvm/vmx/tdx.h
+++ b/arch/x86/kvm/vmx/tdx.h
@@ -3,7 +3,6 @@
#define __KVM_X86_VMX_TDX_H
#include "tdx_arch.h"
-#include "tdx_errno.h"
#ifdef CONFIG_KVM_INTEL_TDX
#include "common.h"
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* Re: [PATCH v2 01/31] x86/tdx: Move all TDX error defines into <asm/shared/tdx_errno.h>
2026-03-27 16:01 ` [PATCH v2 01/31] x86/tdx: Move all TDX error defines into <asm/shared/tdx_errno.h> Xu Yilun
@ 2026-03-27 23:37 ` Edgecombe, Rick P
2026-03-28 1:16 ` Dan Williams
0 siblings, 1 reply; 38+ messages in thread
From: Edgecombe, Rick P @ 2026-03-27 23:37 UTC (permalink / raw)
To: Williams, Dan J, linux-pci@vger.kernel.org,
linux-coco@lists.linux.dev, yilun.xu@linux.intel.com,
x86@kernel.org
Cc: Gao, Chao, Xu, Yilun, dave.hansen@linux.intel.com, kas@kernel.org,
baolu.lu@linux.intel.com, Jiang, Dave, Li, Xiaoyao,
Verma, Vishal L, Duan, Zhenzhong, kvm@vger.kernel.org,
linux-kernel@vger.kernel.org
On Sat, 2026-03-28 at 00:01 +0800, Xu Yilun wrote:
> From: "Kirill A. Shutemov" <kirill.shutemov@linux.intel.com>
>
> Today there are two separate locations where TDX error codes are defined:
> arch/x86/include/asm/tdx.h
> arch/x86/kvm/vmx/tdx_errno.h
>
> They have some overlap that is already defined similarly. Reduce the
> duplication and prepare to introduce some helpers for these error codes in
> the central place by unifying them. Join them at:
> asm/shared/tdx_errno.h
> ...and update the headers that contained the duplicated definitions to
> include the new unified header.
>
> "asm/shared" is used for sharing TDX code between the early compressed
> code and the normal kernel code. While the compressed code for the guest
> doesn't use these error code header definitions today, it does make the
> types of calls that return the values they define. So place the defines in
> "shared" location so that it can, but leave such cleanups for future
> changes.
>
> Also, adjust BITUL() -> _BITULL() to address 32 bit build errors after the
> move.
>
> Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
> [enhance log]
> Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
> Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
> Reviewed-by: Chao Gao <chao.gao@intel.com>
I think it is missing Kai's RB from sys disable v1, and your sign off.
This patch is in three series now, hence the long SOB chain. And I think it is
also the only KVM touch point in the series. If sys disable gets merged ahead of
time it can be dropped. But if there is any lag there we should see if Dave will
just take it instead of trying to keep it in sync.
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 01/31] x86/tdx: Move all TDX error defines into <asm/shared/tdx_errno.h>
2026-03-27 23:37 ` Edgecombe, Rick P
@ 2026-03-28 1:16 ` Dan Williams
0 siblings, 0 replies; 38+ messages in thread
From: Dan Williams @ 2026-03-28 1:16 UTC (permalink / raw)
To: Edgecombe, Rick P, Williams, Dan J, linux-pci@vger.kernel.org,
linux-coco@lists.linux.dev, yilun.xu@linux.intel.com,
x86@kernel.org
Cc: Gao, Chao, Xu, Yilun, dave.hansen@linux.intel.com, kas@kernel.org,
baolu.lu@linux.intel.com, Jiang, Dave, Li, Xiaoyao,
Verma, Vishal L, Duan, Zhenzhong, kvm@vger.kernel.org,
linux-kernel@vger.kernel.org
Edgecombe, Rick P wrote:
> On Sat, 2026-03-28 at 00:01 +0800, Xu Yilun wrote:
> > From: "Kirill A. Shutemov" <kirill.shutemov@linux.intel.com>
> >
> > Today there are two separate locations where TDX error codes are defined:
> > arch/x86/include/asm/tdx.h
> > arch/x86/kvm/vmx/tdx_errno.h
> >
> > They have some overlap that is already defined similarly. Reduce the
> > duplication and prepare to introduce some helpers for these error codes in
> > the central place by unifying them. Join them at:
> > asm/shared/tdx_errno.h
> > ...and update the headers that contained the duplicated definitions to
> > include the new unified header.
> >
> > "asm/shared" is used for sharing TDX code between the early compressed
> > code and the normal kernel code. While the compressed code for the guest
> > doesn't use these error code header definitions today, it does make the
> > types of calls that return the values they define. So place the defines in
> > "shared" location so that it can, but leave such cleanups for future
> > changes.
> >
> > Also, adjust BITUL() -> _BITULL() to address 32 bit build errors after the
> > move.
> >
> > Signed-off-by: Kirill A. Shutemov <kirill.shutemov@linux.intel.com>
> > [enhance log]
> > Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
> > Signed-off-by: Vishal Verma <vishal.l.verma@intel.com>
> > Reviewed-by: Chao Gao <chao.gao@intel.com>
>
> I think it is missing Kai's RB from sys disable v1, and your sign off.
>
> This patch is in three series now, hence the long SOB chain. And I think it is
> also the only KVM touch point in the series. If sys disable gets merged ahead of
> time it can be dropped. But if there is any lag there we should see if Dave will
> just take it instead of trying to keep it in sync.
Thanks for the heads up.
I think if we are committed to the idea that some TDX related sets may
go through different upstreams and some of those sets have a small
handful of common infrastructure patches then there are a few options.
Either have someone keep an eye for these and publish stable-commits for
folks to share, accept that duplication collisions will happen and
rebase when they do, or accept that duplication collisions be ok with
that small bit of mess showing up in the history.
In this case, for the tsm.git#staging branch, I will replace this with a
fresh application of this:
https://lore.kernel.org/all/20260323-fuller_tdx_kexec_support-v2-1-87a36409e051@intel.com/
Yilun, going forward, if you borrow a patch from another set, be sure to
both add your own signed-off-by, but also a:
Link: https://patch.msgid.link/20260323-fuller_tdx_kexec_support-v2-1-87a36409e051@intel.com
...to make it extra clear you are including a patch that is already on
the list in another set.
I suspect that by the time this set is ready to move from
tsm.git#staging to tsm.git#next a stable commit-id may be available for
a rebase.
^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH v2 02/31] x86/virt/tdx: Move bit definitions of TDX_FEATURES0 to public header
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
2026-03-27 16:01 ` [PATCH v2 01/31] x86/tdx: Move all TDX error defines into <asm/shared/tdx_errno.h> Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 23:45 ` Edgecombe, Rick P
2026-03-27 16:01 ` [PATCH v2 03/31] x86/virt/tdx: Add tdx_page_array helpers for new TDX Module objects Xu Yilun
` (28 subsequent siblings)
30 siblings, 1 reply; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Move bit definitions of TDX_FEATURES0 to TDX core public header.
Kernel users get TDX_FEATURES0 bitmap via tdx_get_sysinfo(). It is
reasonable to also public the definitions of each bit. TDX Connect (a
new TDX feature to enable Trusted I/O virtualization) will add new bits
and check them in separate kernel modules.
Take the opportunity to change its type to BIT_ULL since TDX_FEATURES0
is explicitly defined as 64-bit in both TDX Module Specification and
TDX core code.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/include/asm/tdx.h | 4 ++++
arch/x86/virt/vmx/tdx/tdx.h | 3 ---
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index e040e0467ae4..65c4da396450 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -127,6 +127,10 @@ static __always_inline u64 sc_retry(sc_func_t func, u64 fn,
int tdx_cpu_enable(void);
int tdx_enable(void);
const char *tdx_dump_mce_info(struct mce *m);
+
+/* Bit definitions of TDX_FEATURES0 metadata field */
+#define TDX_FEATURES0_NO_RBP_MOD BIT_ULL(18)
+
const struct tdx_sys_info *tdx_get_sysinfo(void);
int tdx_guest_keyid_alloc(void);
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index 82bb82be8567..c641b4632826 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -84,9 +84,6 @@ struct tdmr_info {
DECLARE_FLEX_ARRAY(struct tdmr_reserved_area, reserved_areas);
} __packed __aligned(TDMR_INFO_ALIGNMENT);
-/* Bit definitions of TDX_FEATURES0 metadata field */
-#define TDX_FEATURES0_NO_RBP_MOD BIT(18)
-
/*
* Do not put any hardware-defined TDX structure representations below
* this comment!
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* Re: [PATCH v2 02/31] x86/virt/tdx: Move bit definitions of TDX_FEATURES0 to public header
2026-03-27 16:01 ` [PATCH v2 02/31] x86/virt/tdx: Move bit definitions of TDX_FEATURES0 to public header Xu Yilun
@ 2026-03-27 23:45 ` Edgecombe, Rick P
0 siblings, 0 replies; 38+ messages in thread
From: Edgecombe, Rick P @ 2026-03-27 23:45 UTC (permalink / raw)
To: Williams, Dan J, linux-pci@vger.kernel.org,
linux-coco@lists.linux.dev, yilun.xu@linux.intel.com,
x86@kernel.org
Cc: Gao, Chao, Xu, Yilun, dave.hansen@linux.intel.com, kas@kernel.org,
baolu.lu@linux.intel.com, Jiang, Dave, Li, Xiaoyao,
Verma, Vishal L, Duan, Zhenzhong, kvm@vger.kernel.org,
linux-kernel@vger.kernel.org
On Sat, 2026-03-28 at 00:01 +0800, Xu Yilun wrote:
> Move bit definitions of TDX_FEATURES0 to TDX core public header.
Patch seems reasonable, but not sure about the "public header" language. Maybe
more widely accessibly header? Or a better name?
>
> Kernel users get TDX_FEATURES0 bitmap via tdx_get_sysinfo(). It is
> reasonable to also public the definitions of each bit. TDX Connect (a
> new TDX feature to enable Trusted I/O virtualization) will add new bits
> and check them in separate kernel modules.
>
> Take the opportunity to change its type to BIT_ULL since TDX_FEATURES0
> is explicitly defined as 64-bit in both TDX Module Specification and
> TDX core code.
>
> Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
> ---
^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH v2 03/31] x86/virt/tdx: Add tdx_page_array helpers for new TDX Module objects
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
2026-03-27 16:01 ` [PATCH v2 01/31] x86/tdx: Move all TDX error defines into <asm/shared/tdx_errno.h> Xu Yilun
2026-03-27 16:01 ` [PATCH v2 02/31] x86/virt/tdx: Move bit definitions of TDX_FEATURES0 to public header Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-28 1:35 ` Edgecombe, Rick P
2026-03-27 16:01 ` [PATCH v2 04/31] x86/virt/tdx: Support allocating contiguous pages for tdx_page_array Xu Yilun
` (27 subsequent siblings)
30 siblings, 1 reply; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Add struct tdx_page_array definition for new TDX Module object
types - HPA_ARRAY_T and HPA_LIST_INFO. They are used as input/output
parameters in newly defined SEAMCALLs. Also define some helpers to
allocate, setup and free tdx_page_array.
HPA_ARRAY_T and HPA_LIST_INFO are similar in most aspects. They both
represent a list of pages for TDX Module accessing. There are several
use cases for these 2 structures:
- As SEAMCALL inputs. They are claimed by TDX Module as control pages.
Control pages are private pages for TDX Module to hold its internal
control structures or private data. TDR, TDCS, TDVPR... are existing
control pages, just not added via tdx_page_array.
- As SEAMCALL outputs. They were TDX Module control pages and now are
released.
- As SEAMCALL inputs. They are just temporary buffers for exchanging
data blobs in one SEAMCALL. TDX Module will not hold them for long
time.
The 2 structures both need a 'root page' which contains a list of HPAs.
They collapse the HPA of the root page and the number of valid HPAs
into a 64 bit raw value for SEAMCALL parameters. The root page is
always a medium for passing data pages, TDX Module never keeps the
root page.
A main difference is HPA_ARRAY_T requires singleton mode when
containing just 1 functional page (page0). In this mode the root page is
not needed and the HPA field of the raw value directly points to the
page0. But in this patch, root page is always allocated for user
friendly kAPIs.
Another small difference is HPA_LIST_INFO contains a 'first entry' field
which could be filled by TDX Module. This simplifies host by providing
the same structure when re-invoke the interrupted SEAMCALL. No need for
host to touch this field.
Typical usages of the tdx_page_array:
1. Add control pages:
- struct tdx_page_array *array = tdx_page_array_create(nr_pages);
- seamcall(TDH_XXX_CREATE, array, ...);
2. Release control pages:
- seamcall(TDX_XXX_DELETE, array, &nr_released, &released_hpa);
- tdx_page_array_ctrl_release(array, nr_released, released_hpa);
3. Exchange data blobs:
- struct tdx_page_array *array = tdx_page_array_create(nr_pages);
- seamcall(TDX_XXX, array, ...);
- Read data from array.
- tdx_page_array_free(array);
4. Note the root page contains 512 HPAs at most, if more pages are
required, re-populate the tdx_page_array is needed.
- struct tdx_page_array *array = tdx_page_array_alloc(nr_pages);
- for each 512-page bulk
- tdx_page_array_populate(array, offset);
- seamcall(TDH_XXX_ADD, array, ...);
In case 2, SEAMCALLs output the released page array in the form of
HPA_ARRAY_T or PAGE_LIST_INFO. Use tdx_page_array_ctrl_release() to
check if the output pages match the original input pages. If failed,
TDX Module is buggy. In this case the safer way is to leak the
control pages, call tdx_page_array_ctrl_leak().
The usage of tdx_page_array will be in following patches.
Co-developed-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/include/asm/tdx.h | 37 +++++
arch/x86/virt/vmx/tdx/tdx.c | 299 ++++++++++++++++++++++++++++++++++++
2 files changed, 336 insertions(+)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 65c4da396450..9173a432b312 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -139,6 +139,43 @@ void tdx_guest_keyid_free(unsigned int keyid);
void tdx_quirk_reset_page(struct page *page);
+/**
+ * struct tdx_page_array - Represents a list of pages for TDX Module access
+ * @nr_pages: Total number of data pages in the collection
+ * @pages: Array of data page pointers containing all the data
+ *
+ * @offset: Internal: The starting index in @pages, positions the currently
+ * populated page window in @root.
+ * @nents: Internal: Number of valid HPAs for the page window in @root
+ * @root: Internal: A single 4KB page holding the 8-byte HPAs of the page
+ * window. The page window max size is constrained by the root page,
+ * which is 512 HPAs.
+ *
+ * This structure abstracts several TDX Module defined object types, e.g.,
+ * HPA_ARRAY_T and HPA_LIST_INFO. Typically they all use a "root page" as the
+ * medium to exchange a list of data pages between host and TDX Module. This
+ * structure serves as a unified parameter type for SEAMCALL wrappers, where
+ * these hardware object types are needed.
+ */
+struct tdx_page_array {
+ /* public: */
+ unsigned int nr_pages;
+ struct page **pages;
+
+ /* private: */
+ unsigned int offset;
+ unsigned int nents;
+ u64 *root;
+};
+
+void tdx_page_array_free(struct tdx_page_array *array);
+DEFINE_FREE(tdx_page_array_free, struct tdx_page_array *, if (_T) tdx_page_array_free(_T))
+struct tdx_page_array *tdx_page_array_create(unsigned int nr_pages);
+void tdx_page_array_ctrl_leak(struct tdx_page_array *array);
+int tdx_page_array_ctrl_release(struct tdx_page_array *array,
+ unsigned int nr_released,
+ u64 released_hpa);
+
struct tdx_td {
/* TD root structure: */
struct page *tdr_page;
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 8b8e165a2001..a3021e7e2490 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -30,6 +30,7 @@
#include <linux/suspend.h>
#include <linux/idr.h>
#include <linux/kvm_types.h>
+#include <linux/bitfield.h>
#include <asm/page.h>
#include <asm/special_insns.h>
#include <asm/msr-index.h>
@@ -258,6 +259,304 @@ static int build_tdx_memlist(struct list_head *tmb_list)
return ret;
}
+#define TDX_PAGE_ARRAY_MAX_NENTS (PAGE_SIZE / sizeof(u64))
+
+static int tdx_page_array_populate(struct tdx_page_array *array,
+ unsigned int offset)
+{
+ u64 *entries;
+ int i;
+
+ if (offset >= array->nr_pages)
+ return 0;
+
+ array->offset = offset;
+ array->nents = umin(array->nr_pages - offset,
+ TDX_PAGE_ARRAY_MAX_NENTS);
+
+ entries = array->root;
+ for (i = 0; i < array->nents; i++)
+ entries[i] = page_to_phys(array->pages[offset + i]);
+
+ return array->nents;
+}
+
+static void tdx_free_pages_bulk(unsigned int nr_pages, struct page **pages)
+{
+ int i;
+
+ for (i = 0; i < nr_pages; i++)
+ __free_page(pages[i]);
+}
+
+static int tdx_alloc_pages_bulk(unsigned int nr_pages, struct page **pages)
+{
+ unsigned int filled, done = 0;
+
+ do {
+ filled = alloc_pages_bulk(GFP_KERNEL, nr_pages - done,
+ pages + done);
+ if (!filled) {
+ tdx_free_pages_bulk(done, pages);
+ return -ENOMEM;
+ }
+
+ done += filled;
+ } while (done != nr_pages);
+
+ return 0;
+}
+
+/**
+ * tdx_page_array_free() - Free all memory for a tdx_page_array
+ * @array: The tdx_page_array to be freed.
+ *
+ * Free all associated pages and the container itself.
+ */
+void tdx_page_array_free(struct tdx_page_array *array)
+{
+ if (!array)
+ return;
+
+ tdx_free_pages_bulk(array->nr_pages, array->pages);
+ kfree(array->pages);
+ kfree(array->root);
+ kfree(array);
+}
+EXPORT_SYMBOL_GPL(tdx_page_array_free);
+
+static struct tdx_page_array *
+tdx_page_array_alloc(unsigned int nr_pages)
+{
+ struct tdx_page_array *array = NULL;
+ struct page **pages = NULL;
+ u64 *root = NULL;
+ int ret;
+
+ if (!nr_pages)
+ return NULL;
+
+ array = kzalloc_obj(*array);
+ if (!array)
+ goto out_free;
+
+ root = kzalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!root)
+ goto out_free;
+
+ pages = kcalloc(nr_pages, sizeof(*pages), GFP_KERNEL);
+ if (!pages)
+ goto out_free;
+
+ ret = tdx_alloc_pages_bulk(nr_pages, pages);
+ if (ret)
+ goto out_free;
+
+ array->nr_pages = nr_pages;
+ array->pages = pages;
+ array->root = root;
+
+ return array;
+
+out_free:
+ kfree(pages);
+ kfree(root);
+ kfree(array);
+
+ return NULL;
+}
+
+/**
+ * tdx_page_array_create() - Create a small tdx_page_array (up to 512 pages)
+ * @nr_pages: Number of pages to allocate (must be <= 512).
+ *
+ * Allocate and populate a tdx_page_array in a single step. This is intended
+ * for small collections that fit within a single root page. The allocated
+ * pages are all order-0 pages. This is the most common use case for a list of
+ * TDX control pages.
+ *
+ * If more pages are required, use tdx_page_array_alloc() and
+ * tdx_page_array_populate() to build tdx_page_array chunk by chunk.
+ *
+ * Return: Fully populated tdx_page_array or NULL on failure.
+ */
+struct tdx_page_array *tdx_page_array_create(unsigned int nr_pages)
+{
+ struct tdx_page_array *array;
+ int populated;
+
+ if (nr_pages > TDX_PAGE_ARRAY_MAX_NENTS)
+ return NULL;
+
+ array = tdx_page_array_alloc(nr_pages);
+ if (!array)
+ return NULL;
+
+ populated = tdx_page_array_populate(array, 0);
+ if (populated != nr_pages)
+ goto out_free;
+
+ return array;
+
+out_free:
+ tdx_page_array_free(array);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(tdx_page_array_create);
+
+/**
+ * tdx_page_array_ctrl_leak() - Leak data pages and free the container
+ * @array: The tdx_page_array to be leaked.
+ *
+ * Call this function when failed to reclaim the control pages. Free the root
+ * page and the holding structures, but orphan the data pages, to prevent the
+ * host from re-allocating and accessing memory that the hardware may still
+ * consider private.
+ */
+void tdx_page_array_ctrl_leak(struct tdx_page_array *array)
+{
+ if (!array)
+ return;
+
+ kfree(array->pages);
+ kfree(array->root);
+ kfree(array);
+}
+EXPORT_SYMBOL_GPL(tdx_page_array_ctrl_leak);
+
+static bool tdx_page_array_validate_release(struct tdx_page_array *array,
+ unsigned int offset,
+ unsigned int nr_released,
+ u64 released_hpa)
+{
+ unsigned int nents;
+
+ if (offset >= array->nr_pages)
+ return false;
+
+ nents = umin(array->nr_pages - offset, TDX_PAGE_ARRAY_MAX_NENTS);
+
+ if (nents != nr_released) {
+ pr_err("%s nr_released [%d] doesn't match page array nents [%d]\n",
+ __func__, nr_released, nents);
+ return false;
+ }
+
+ /*
+ * Unfortunately TDX has multiple page allocation protocols, check the
+ * "singleton" case required for HPA_ARRAY_T.
+ */
+ if (page_to_phys(array->pages[0]) == released_hpa &&
+ array->nr_pages == 1)
+ return true;
+
+ /* Then check the "non-singleton" case */
+ if (virt_to_phys(array->root) == released_hpa) {
+ u64 *entries = array->root;
+ int i;
+
+ for (i = 0; i < nents; i++) {
+ struct page *page = array->pages[offset + i];
+ u64 val = page_to_phys(page);
+
+ if (val != entries[i]) {
+ pr_err("%s entry[%d] [0x%llx] doesn't match page hpa [0x%llx]\n",
+ __func__, i, entries[i], val);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ pr_err("%s failed to validate, released_hpa [0x%llx], root page hpa [0x%llx], page0 hpa [%#llx], number pages %u\n",
+ __func__, released_hpa, virt_to_phys(array->root),
+ page_to_phys(array->pages[0]), array->nr_pages);
+
+ return false;
+}
+
+/**
+ * tdx_page_array_ctrl_release() - Verify and release TDX control pages
+ * @array: The tdx_page_array used to originally create control pages.
+ * @nr_released: Number of HPAs the TDX Module reported as released.
+ * @released_hpa: The HPA list the TDX Module reported as released.
+ *
+ * TDX Module can at most release 512 control pages, so this function only
+ * accepts small tdx_page_array (up to 512 pages), usually created by
+ * tdx_page_array_create().
+ *
+ * Return: 0 on success, -errno on page release protocol error.
+ */
+int tdx_page_array_ctrl_release(struct tdx_page_array *array,
+ unsigned int nr_released,
+ u64 released_hpa)
+{
+ int i;
+
+ /*
+ * The only case where ->nr_pages is allowed to be >
+ * TDX_PAGE_ARRAY_MAX_NENTS is a case where those pages are never
+ * expected to be released by this function.
+ */
+ if (WARN_ON(array->nr_pages > TDX_PAGE_ARRAY_MAX_NENTS))
+ return -EINVAL;
+
+ if (WARN_ONCE(!tdx_page_array_validate_release(array, 0, nr_released,
+ released_hpa),
+ "page release protocol error, consider reboot and replace TDX Module.\n"))
+ return -EFAULT;
+
+ for (i = 0; i < array->nr_pages; i++) {
+ u64 r;
+
+ r = tdh_phymem_page_wbinvd_hkid(tdx_global_keyid,
+ array->pages[i]);
+ if (WARN_ON(r))
+ return -EFAULT;
+ }
+
+ tdx_page_array_free(array);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tdx_page_array_ctrl_release);
+
+#define HPA_LIST_INFO_FIRST_ENTRY GENMASK_U64(11, 3)
+#define HPA_LIST_INFO_PFN GENMASK_U64(51, 12)
+#define HPA_LIST_INFO_LAST_ENTRY GENMASK_U64(63, 55)
+
+static u64 __maybe_unused hpa_list_info_assign_raw(struct tdx_page_array *array)
+{
+ return FIELD_PREP(HPA_LIST_INFO_FIRST_ENTRY, 0) |
+ FIELD_PREP(HPA_LIST_INFO_PFN,
+ PFN_DOWN(virt_to_phys(array->root))) |
+ FIELD_PREP(HPA_LIST_INFO_LAST_ENTRY, array->nents - 1);
+}
+
+#define HPA_ARRAY_T_PFN GENMASK_U64(51, 12)
+#define HPA_ARRAY_T_SIZE GENMASK_U64(63, 55)
+
+static u64 __maybe_unused hpa_array_t_assign_raw(struct tdx_page_array *array)
+{
+ unsigned long pfn;
+
+ if (array->nents == 1)
+ pfn = page_to_pfn(array->pages[array->offset]);
+ else
+ pfn = PFN_DOWN(virt_to_phys(array->root));
+
+ return FIELD_PREP(HPA_ARRAY_T_PFN, pfn) |
+ FIELD_PREP(HPA_ARRAY_T_SIZE, array->nents - 1);
+}
+
+static u64 __maybe_unused hpa_array_t_release_raw(struct tdx_page_array *array)
+{
+ if (array->nents == 1)
+ return 0;
+
+ return virt_to_phys(array->root);
+}
+
static int read_sys_metadata_field(u64 field_id, u64 *data)
{
struct tdx_module_args args = {};
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* Re: [PATCH v2 03/31] x86/virt/tdx: Add tdx_page_array helpers for new TDX Module objects
2026-03-27 16:01 ` [PATCH v2 03/31] x86/virt/tdx: Add tdx_page_array helpers for new TDX Module objects Xu Yilun
@ 2026-03-28 1:35 ` Edgecombe, Rick P
0 siblings, 0 replies; 38+ messages in thread
From: Edgecombe, Rick P @ 2026-03-28 1:35 UTC (permalink / raw)
To: Williams, Dan J, linux-pci@vger.kernel.org,
linux-coco@lists.linux.dev, yilun.xu@linux.intel.com,
x86@kernel.org
Cc: Gao, Chao, Xu, Yilun, dave.hansen@linux.intel.com, kas@kernel.org,
baolu.lu@linux.intel.com, Jiang, Dave, Li, Xiaoyao,
Verma, Vishal L, Duan, Zhenzhong, kvm@vger.kernel.org,
linux-kernel@vger.kernel.org
Hi,
In general I'm struggling to understand the design decisions. It seems a very
specific design and quite a bit of code to manage an array of pages. Questions
below.
On Sat, 2026-03-28 at 00:01 +0800, Xu Yilun wrote:
> Add struct tdx_page_array definition for new TDX Module object
> types - HPA_ARRAY_T and HPA_LIST_INFO.
This is unfortunate. I see you agree in the comments.
>
> They are used as input/output
> parameters in newly defined SEAMCALLs. Also define some helpers to
> allocate, setup and free tdx_page_array.
>
> HPA_ARRAY_T and HPA_LIST_INFO are similar in most aspects. They both
> represent a list of pages for TDX Module accessing. There are several
> use cases for these 2 structures:
>
> - As SEAMCALL inputs. They are claimed by TDX Module as control pages.
> Control pages are private pages for TDX Module to hold its internal
> control structures or private data. TDR, TDCS, TDVPR... are existing
> control pages, just not added via tdx_page_array.
> - As SEAMCALL outputs. They were TDX Module control pages and now are
> released.
> - As SEAMCALL inputs. They are just temporary buffers for exchanging
> data blobs in one SEAMCALL. TDX Module will not hold them for long
> time.
This is kind of verbose for what it seems to be trying to say. It's just that
these types can be input or output params. The TDX module could hold on to the
pages for a long time, or just transiently. For that latter part I think you are
trying to say sometimes they need flushing and sometimes they don't?
>
> The 2 structures both need a 'root page' which contains a list of HPAs.
> They collapse the HPA of the root page and the number of valid HPAs
> into a 64 bit raw value for SEAMCALL parameters. The root page is
> always a medium for passing data pages, TDX Module never keeps the
> root page.
>
> A main difference is HPA_ARRAY_T requires singleton mode when
> containing just 1 functional page (page0). In this mode the root page is
> not needed and the HPA field of the raw value directly points to the
> page0. But in this patch, root page is always allocated for user
> friendly kAPIs.
"singleton mode"? What is it? If it's the case of not needing populate loop, it
probably deserves more explanation. I'm not sure, but the populate loop seems to
drive a lot of the struct design?
>
> Another small difference is HPA_LIST_INFO contains a 'first entry' field
> which could be filled by TDX Module. This simplifies host by providing
> the same structure when re-invoke the interrupted SEAMCALL. No need for
> host to touch this field.
Not clear what this is talking about. But I'm starting to wonder if we should be
so bold to claim that the differences between the types really simplify the
host.
>
> Typical usages of the tdx_page_array:
>
> 1. Add control pages:
> - struct tdx_page_array *array = tdx_page_array_create(nr_pages);
> - seamcall(TDH_XXX_CREATE, array, ...);
>
> 2. Release control pages:
> - seamcall(TDX_XXX_DELETE, array, &nr_released, &released_hpa);
> - tdx_page_array_ctrl_release(array, nr_released, released_hpa);
So release is mostly needed because of the need to do the wbvind seamcall? And
unlike tdx_page_array_free() it returns an error in case that fails. Or other
sanity checking. But all the callers do the same thing on error, call
tdx_page_array_ctrl_leak().
Just wondering if we could simplify it somehow. There are two helpers and the
caller has to know which one to call based on SEAMCALL specifics. What if the
seamcall wrapper set a bit in the page array while passing it out. The bit would
specify to the helper if it needs to do wbinvd or not. Then the wrappers could
encapsulate the type of free needed and not rely on the caller to know. And we
only need to have one function for it instead of two.
BTW, do we expect errors from the tdh_phymem_page_wbinvd_hkid() calls here? How
could the BUSY happen? If we don't think it can happen in normal runtime, we
could just warn and skip the special leak logic. In KVM side there is a place
where we can't really handle it for the wbinvd calls. And one where we can. If
we need a ton of code to handle a bug somewhere (on kernel side or TDX module),
it seems too defensive to me. At least it's not in sync with the rest of TDX.
Especially the quite large tdx_page_array_validate_release() logic should need a
justification that there is something very tricky that needs all this checking.
But maybe you can explain what the special risk is.
>
> 3. Exchange data blobs:
> - struct tdx_page_array *array = tdx_page_array_create(nr_pages);
> - seamcall(TDX_XXX, array, ...);
> - Read data from array.
>
>
> 4. Note the root page contains 512 HPAs at most, if more pages are
> required, re-populate the tdx_page_array is needed.
>
> - struct tdx_page_array *array = tdx_page_array_alloc(nr_pages);
> - for each 512-page bulk
> - tdx_page_array_populate(array, offset);
> - seamcall(TDH_XXX_ADD, array, ...);
>
> In case 2, SEAMCALLs output the released page array in the form of
> HPA_ARRAY_T or PAGE_LIST_INFO. Use tdx_page_array_ctrl_release() to
> check if the output pages match the original input pages. If failed,
> TDX Module is buggy. In this case the safer way is to leak the
> control pages, call tdx_page_array_ctrl_leak().
>
> The usage of tdx_page_array will be in following patches.
>
> Co-developed-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
> Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
> Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
> ---
> arch/x86/include/asm/tdx.h | 37 +++++
> arch/x86/virt/vmx/tdx/tdx.c | 299 ++++++++++++++++++++++++++++++++++++
> 2 files changed, 336 insertions(+)
>
> diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
> index 65c4da396450..9173a432b312 100644
> --- a/arch/x86/include/asm/tdx.h
> +++ b/arch/x86/include/asm/tdx.h
> @@ -139,6 +139,43 @@ void tdx_guest_keyid_free(unsigned int keyid);
>
> void tdx_quirk_reset_page(struct page *page);
>
> +/**
> + * struct tdx_page_array - Represents a list of pages for TDX Module access
> + * @nr_pages: Total number of data pages in the collection
> + * @pages: Array of data page pointers containing all the data
> + *
> + * @offset: Internal: The starting index in @pages, positions the currently
> + * populated page window in @root.
> + * @nents: Internal: Number of valid HPAs for the page window in @root
> + * @root: Internal: A single 4KB page holding the 8-byte HPAs of the page
> + * window. The page window max size is constrained by the root page,
> + * which is 512 HPAs.
> + *
> + * This structure abstracts several TDX Module defined object types, e.g.,
> + * HPA_ARRAY_T and HPA_LIST_INFO. Typically they all use a "root page" as the
> + * medium to exchange a list of data pages between host and TDX Module. This
> + * structure serves as a unified parameter type for SEAMCALL wrappers, where
> + * these hardware object types are needed.
> + */
> +struct tdx_page_array {
> + /* public: */
> + unsigned int nr_pages;
> + struct page **pages;
> +
> + /* private: */
> + unsigned int offset;
> + unsigned int nents;
> + u64 *root;
pages is going to be an array of struct pointers, and root is a single page of
PA's that gets re-used to copy and pass the PA's to the TDX module. Why do we
need both? Like just keep an array of PA's that would be the same size as the
struct page array. And not need the populate loop?
Pausing for now. Still looking through the callers and it's the end of the day.
^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH v2 04/31] x86/virt/tdx: Support allocating contiguous pages for tdx_page_array
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (2 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 03/31] x86/virt/tdx: Add tdx_page_array helpers for new TDX Module objects Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 05/31] x86/virt/tdx: Extend tdx_page_array to support IOMMU_MT Xu Yilun
` (26 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
The current tdx_page_array implementation allocates scattered order-0
pages. However, some TDX Module operations benefit from contiguous
physical memory. E.g. Enabling TDX Module Extensions (an optional TDX
feature) requires ~50MB memory and never returns. Such allocation
would at worst cause ~25GB permanently fragmented memory if each
allocated page is from a different 2M region.
Support allocating contiguous pages for tdx_page_array by making the
allocation method configurable. Change the tdx_page_array_alloc() to
accept a custom allocation function pointer and a context parameter.
Wrap the specific allocation into a tdx_page_array_alloc_contig()
helper.
The foreseeable caller will allocate ~50MB memory with this helper,
exceeding the maximum HPAs (512) a root page can hold, the typical usage
will be:
- struct tdx_page_array *array = tdx_page_array_alloc_contig(nr_pages);
- for each 512-page bulk
- tdx_page_array_populate(array, offset);
- seamcall(TDH_XXX_ADD, array, ...);
The configurable allocation method would also benefit more
tdx_page_array usages. TDX Module may require more specific memory
layouts encoded in the root page. Will introduce them in following
patches.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.c | 42 +++++++++++++++++++++++++++++++++----
1 file changed, 38 insertions(+), 4 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index a3021e7e2490..6c4ed80e8e5a 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -289,7 +289,8 @@ static void tdx_free_pages_bulk(unsigned int nr_pages, struct page **pages)
__free_page(pages[i]);
}
-static int tdx_alloc_pages_bulk(unsigned int nr_pages, struct page **pages)
+static int tdx_alloc_pages_bulk(unsigned int nr_pages, struct page **pages,
+ void *data)
{
unsigned int filled, done = 0;
@@ -326,7 +327,10 @@ void tdx_page_array_free(struct tdx_page_array *array)
EXPORT_SYMBOL_GPL(tdx_page_array_free);
static struct tdx_page_array *
-tdx_page_array_alloc(unsigned int nr_pages)
+tdx_page_array_alloc(unsigned int nr_pages,
+ int (*alloc_fn)(unsigned int nr_pages,
+ struct page **pages, void *data),
+ void *data)
{
struct tdx_page_array *array = NULL;
struct page **pages = NULL;
@@ -348,7 +352,7 @@ tdx_page_array_alloc(unsigned int nr_pages)
if (!pages)
goto out_free;
- ret = tdx_alloc_pages_bulk(nr_pages, pages);
+ ret = alloc_fn(nr_pages, pages, data);
if (ret)
goto out_free;
@@ -388,7 +392,7 @@ struct tdx_page_array *tdx_page_array_create(unsigned int nr_pages)
if (nr_pages > TDX_PAGE_ARRAY_MAX_NENTS)
return NULL;
- array = tdx_page_array_alloc(nr_pages);
+ array = tdx_page_array_alloc(nr_pages, tdx_alloc_pages_bulk, NULL);
if (!array)
return NULL;
@@ -521,6 +525,36 @@ int tdx_page_array_ctrl_release(struct tdx_page_array *array,
}
EXPORT_SYMBOL_GPL(tdx_page_array_ctrl_release);
+static int tdx_alloc_pages_contig(unsigned int nr_pages, struct page **pages,
+ void *data)
+{
+ struct page *page;
+ int i;
+
+ page = alloc_contig_pages(nr_pages, GFP_KERNEL, numa_mem_id(),
+ &node_online_map);
+ if (!page)
+ return -ENOMEM;
+
+ for (i = 0; i < nr_pages; i++)
+ pages[i] = page + i;
+
+ return 0;
+}
+
+/*
+ * For holding large number of contiguous pages, usually larger than
+ * TDX_PAGE_ARRAY_MAX_NENTS (512).
+ *
+ * Similar to tdx_page_array_alloc(), after allocating with this
+ * function, call tdx_page_array_populate() to populate the tdx_page_array.
+ */
+static __maybe_unused struct tdx_page_array *
+tdx_page_array_alloc_contig(unsigned int nr_pages)
+{
+ return tdx_page_array_alloc(nr_pages, tdx_alloc_pages_contig, NULL);
+}
+
#define HPA_LIST_INFO_FIRST_ENTRY GENMASK_U64(11, 3)
#define HPA_LIST_INFO_PFN GENMASK_U64(51, 12)
#define HPA_LIST_INFO_LAST_ENTRY GENMASK_U64(63, 55)
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 05/31] x86/virt/tdx: Extend tdx_page_array to support IOMMU_MT
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (3 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 04/31] x86/virt/tdx: Support allocating contiguous pages for tdx_page_array Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 06/31] x86/virt/tdx: Read global metadata for TDX Module Extensions/Connect Xu Yilun
` (25 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
IOMMU_MT is another TDX Module defined structure similar to HPA_ARRAY_T
and HPA_LIST_INFO. The difference is it requires multi-order contiguous
pages for some entries. It adds an additional NUM_PAGES field for every
multi-order page entry.
Add a dedicated allocation helper for IOMMU_MT. Fortunately put_page()
works well for both single pages and multi-order folios, simplifying the
cleanup logic for all allocation methods.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/include/asm/tdx.h | 2 +
arch/x86/virt/vmx/tdx/tdx.c | 90 +++++++++++++++++++++++++++++++++++--
2 files changed, 89 insertions(+), 3 deletions(-)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 9173a432b312..d5f1d7b7d1e7 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -175,6 +175,8 @@ void tdx_page_array_ctrl_leak(struct tdx_page_array *array);
int tdx_page_array_ctrl_release(struct tdx_page_array *array,
unsigned int nr_released,
u64 released_hpa);
+struct tdx_page_array *
+tdx_page_array_create_iommu_mt(unsigned int iq_order, unsigned int nr_mt_pages);
struct tdx_td {
/* TD root structure: */
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 6c4ed80e8e5a..2b17e0f73dac 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -275,8 +275,15 @@ static int tdx_page_array_populate(struct tdx_page_array *array,
TDX_PAGE_ARRAY_MAX_NENTS);
entries = array->root;
- for (i = 0; i < array->nents; i++)
- entries[i] = page_to_phys(array->pages[offset + i]);
+ for (i = 0; i < array->nents; i++) {
+ struct page *page = array->pages[offset + i];
+
+ entries[i] = page_to_phys(page);
+
+ /* Now only for iommu_mt */
+ if (compound_nr(page) > 1)
+ entries[i] |= compound_nr(page);
+ }
return array->nents;
}
@@ -286,7 +293,7 @@ static void tdx_free_pages_bulk(unsigned int nr_pages, struct page **pages)
int i;
for (i = 0; i < nr_pages; i++)
- __free_page(pages[i]);
+ put_page(pages[i]);
}
static int tdx_alloc_pages_bulk(unsigned int nr_pages, struct page **pages,
@@ -463,6 +470,10 @@ static bool tdx_page_array_validate_release(struct tdx_page_array *array,
struct page *page = array->pages[offset + i];
u64 val = page_to_phys(page);
+ /* Now only for iommu_mt */
+ if (compound_nr(page) > 1)
+ val |= compound_nr(page);
+
if (val != entries[i]) {
pr_err("%s entry[%d] [0x%llx] doesn't match page hpa [0x%llx]\n",
__func__, i, entries[i], val);
@@ -555,6 +566,79 @@ tdx_page_array_alloc_contig(unsigned int nr_pages)
return tdx_page_array_alloc(nr_pages, tdx_alloc_pages_contig, NULL);
}
+static int tdx_alloc_pages_iommu_mt(unsigned int nr_pages, struct page **pages,
+ void *data)
+{
+ unsigned int iq_order = (unsigned int)(long)data;
+ struct folio *t_iq, *t_ctxiq;
+ int ret;
+
+ /* TODO: folio_alloc_node() is preferred, but need numa info */
+ t_iq = folio_alloc(GFP_KERNEL | __GFP_ZERO, iq_order);
+ if (!t_iq)
+ return -ENOMEM;
+
+ t_ctxiq = folio_alloc(GFP_KERNEL | __GFP_ZERO, iq_order);
+ if (!t_ctxiq) {
+ ret = -ENOMEM;
+ goto out_t_iq;
+ }
+
+ ret = tdx_alloc_pages_bulk(nr_pages - 2, pages + 2, NULL);
+ if (ret)
+ goto out_t_ctxiq;
+
+ pages[0] = folio_page(t_iq, 0);
+ pages[1] = folio_page(t_ctxiq, 0);
+
+ return 0;
+
+out_t_ctxiq:
+ folio_put(t_ctxiq);
+out_t_iq:
+ folio_put(t_iq);
+
+ return ret;
+}
+
+/**
+ * tdx_page_array_create_iommu_mt() - Create a page array for IOMMU Memory Tables
+ * @iq_order: The allocation order for the IOMMU Invalidation Queue.
+ * @nr_mt_pages: Number of additional order-0 pages for the MT.
+ *
+ * Allocate and populate a specialized tdx_page_array for IOMMU_MT structures.
+ * The resulting array consists of two multi-order folios (at index 0 and 1)
+ * followed by the requested number of order-0 pages.
+ *
+ * Return: Fully populated tdx_page_array or NULL on failure.
+ */
+struct tdx_page_array *
+tdx_page_array_create_iommu_mt(unsigned int iq_order, unsigned int nr_mt_pages)
+{
+ unsigned int nr_pages = nr_mt_pages + 2;
+ struct tdx_page_array *array;
+ int populated;
+
+ if (nr_pages > TDX_PAGE_ARRAY_MAX_NENTS)
+ return NULL;
+
+ array = tdx_page_array_alloc(nr_pages, tdx_alloc_pages_iommu_mt,
+ (void *)(long)iq_order);
+ if (!array)
+ return NULL;
+
+ populated = tdx_page_array_populate(array, 0);
+ if (populated != nr_pages)
+ goto out_free;
+
+ return array;
+
+out_free:
+ tdx_page_array_free(array);
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(tdx_page_array_create_iommu_mt);
+
#define HPA_LIST_INFO_FIRST_ENTRY GENMASK_U64(11, 3)
#define HPA_LIST_INFO_PFN GENMASK_U64(51, 12)
#define HPA_LIST_INFO_LAST_ENTRY GENMASK_U64(63, 55)
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 06/31] x86/virt/tdx: Read global metadata for TDX Module Extensions/Connect
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (4 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 05/31] x86/virt/tdx: Extend tdx_page_array to support IOMMU_MT Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 07/31] x86/virt/tdx: Embed version info in SEAMCALL leaf function definitions Xu Yilun
` (24 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Add reading of the global metadata for TDX Module Extensions & TDX
Connect. Add them in a batch as TDX Connect is currently the only user
of TDX Module Extensions and no way to initialize TDX Module Extensions
without firstly enabling TDX Connect.
TDX Module Extensions & TDX Connect are optional features enumerated by
TDX_FEATURES0. Check the TDX_FEATURES0 before reading these metadata to
avoid failing the whole TDX initialization.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/include/asm/tdx.h | 2 ++
arch/x86/include/asm/tdx_global_metadata.h | 14 ++++++++
arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 36 +++++++++++++++++++++
3 files changed, 52 insertions(+)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index d5f1d7b7d1e7..d7605235aa9b 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -129,7 +129,9 @@ int tdx_enable(void);
const char *tdx_dump_mce_info(struct mce *m);
/* Bit definitions of TDX_FEATURES0 metadata field */
+#define TDX_FEATURES0_TDXCONNECT BIT_ULL(6)
#define TDX_FEATURES0_NO_RBP_MOD BIT_ULL(18)
+#define TDX_FEATURES0_EXT BIT_ULL(39)
const struct tdx_sys_info *tdx_get_sysinfo(void);
diff --git a/arch/x86/include/asm/tdx_global_metadata.h b/arch/x86/include/asm/tdx_global_metadata.h
index 060a2ad744bf..e7948bca671a 100644
--- a/arch/x86/include/asm/tdx_global_metadata.h
+++ b/arch/x86/include/asm/tdx_global_metadata.h
@@ -34,11 +34,25 @@ struct tdx_sys_info_td_conf {
u64 cpuid_config_values[128][2];
};
+struct tdx_sys_info_ext {
+ u16 memory_pool_required_pages;
+ u8 ext_required;
+};
+
+struct tdx_sys_info_connect {
+ u16 ide_mt_page_count;
+ u16 spdm_mt_page_count;
+ u16 iommu_mt_page_count;
+ u16 spdm_max_dev_info_pages;
+};
+
struct tdx_sys_info {
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_ext ext;
+ struct tdx_sys_info_connect 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 13ad2663488b..a07f1e7b18e8 100644
--- a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
+++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
@@ -85,6 +85,36 @@ static int get_tdx_sys_info_td_conf(struct tdx_sys_info_td_conf *sysinfo_td_conf
return ret;
}
+static int get_tdx_sys_info_ext(struct tdx_sys_info_ext *sysinfo_ext)
+{
+ int ret = 0;
+ u64 val;
+
+ if (!ret && !(ret = read_sys_metadata_field(0x3100000100000000, &val)))
+ sysinfo_ext->memory_pool_required_pages = val;
+ if (!ret && !(ret = read_sys_metadata_field(0x3100000100000001, &val)))
+ sysinfo_ext->ext_required = val;
+
+ return ret;
+}
+
+static int get_tdx_sys_info_connect(struct tdx_sys_info_connect *sysinfo_connect)
+{
+ int ret = 0;
+ u64 val;
+
+ if (!ret && !(ret = read_sys_metadata_field(0x3000000100000001, &val)))
+ sysinfo_connect->ide_mt_page_count = val;
+ if (!ret && !(ret = read_sys_metadata_field(0x3000000100000002, &val)))
+ sysinfo_connect->spdm_mt_page_count = val;
+ if (!ret && !(ret = read_sys_metadata_field(0x3000000100000003, &val)))
+ sysinfo_connect->iommu_mt_page_count = val;
+ if (!ret && !(ret = read_sys_metadata_field(0x3000000100000007, &val)))
+ sysinfo_connect->spdm_max_dev_info_pages = val;
+
+ return ret;
+}
+
static int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
{
int ret = 0;
@@ -94,5 +124,11 @@ static 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 (sysinfo->features.tdx_features0 & TDX_FEATURES0_EXT)
+ ret = ret ?: get_tdx_sys_info_ext(&sysinfo->ext);
+
+ if (sysinfo->features.tdx_features0 & TDX_FEATURES0_TDXCONNECT)
+ ret = ret ?: get_tdx_sys_info_connect(&sysinfo->connect);
+
return ret;
}
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 07/31] x86/virt/tdx: Embed version info in SEAMCALL leaf function definitions
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (5 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 06/31] x86/virt/tdx: Read global metadata for TDX Module Extensions/Connect Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 08/31] x86/virt/tdx: Configure TDX Module with optional TDX Connect feature Xu Yilun
` (23 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Embed version information in SEAMCALL leaf function definitions rather
than let the caller open code them. For now, only TDH.VP.INIT is
involved.
Don't bother the caller to choose the SEAMCALL version if unnecessary.
New version SEAMCALLs are guaranteed to be backward compatible, so
ideally kernel doesn't need to keep version history and only uses the
latest version SEAMCALLs.
The concern is some old TDX Modules don't recognize new version
SEAMCALLs. Multiple SEAMCALL versions co-exist when kernel should
support these old Modules. As time goes by, the old Modules deprecate
and old version SEAMCALL definitions should disappear.
The old TDX Modules that only support TDH.VP.INIT v0 are all deprecated,
so only provide the latest (v1) definition.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.h | 23 ++++++++++++++---------
arch/x86/virt/vmx/tdx/tdx.c | 4 ++--
2 files changed, 16 insertions(+), 11 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index c641b4632826..e5a9331df451 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -2,6 +2,7 @@
#ifndef _X86_VIRT_TDX_H
#define _X86_VIRT_TDX_H
+#include <linux/bitfield.h>
#include <linux/bits.h>
/*
@@ -11,6 +12,18 @@
* architectural definitions come first.
*/
+/*
+ * SEAMCALL leaf:
+ *
+ * Bit 15:0 Leaf number
+ * Bit 23:16 Version number
+ */
+#define SEAMCALL_LEAF GENMASK(15, 0)
+#define SEAMCALL_VER GENMASK(23, 16)
+
+#define SEAMCALL_LEAF_VER(l, v) (FIELD_PREP(SEAMCALL_LEAF, l) | \
+ FIELD_PREP(SEAMCALL_VER, v))
+
/*
* TDX module SEAMCALL leaf functions
*/
@@ -31,7 +44,7 @@
#define TDH_VP_CREATE 10
#define TDH_MNG_KEY_FREEID 20
#define TDH_MNG_INIT 21
-#define TDH_VP_INIT 22
+#define TDH_VP_INIT SEAMCALL_LEAF_VER(22, 1)
#define TDH_PHYMEM_PAGE_RDMD 24
#define TDH_VP_RD 26
#define TDH_PHYMEM_PAGE_RECLAIM 28
@@ -47,14 +60,6 @@
#define TDH_VP_WR 43
#define TDH_SYS_CONFIG 45
-/*
- * SEAMCALL leaf:
- *
- * Bit 15:0 Leaf number
- * Bit 23:16 Version number
- */
-#define TDX_VERSION_SHIFT 16
-
/* TDX page types */
#define PT_NDA 0x0
#define PT_RSVD 0x1
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 2b17e0f73dac..130214933c2f 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -2202,8 +2202,8 @@ u64 tdh_vp_init(struct tdx_vp *vp, u64 initial_rcx, u32 x2apicid)
.r8 = x2apicid,
};
- /* apicid requires version == 1. */
- return seamcall(TDH_VP_INIT | (1ULL << TDX_VERSION_SHIFT), &args);
+ /* apicid requires version == 1. See TDH_VP_INIT definition.*/
+ return seamcall(TDH_VP_INIT, &args);
}
EXPORT_SYMBOL_FOR_KVM(tdh_vp_init);
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 08/31] x86/virt/tdx: Configure TDX Module with optional TDX Connect feature
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (6 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 07/31] x86/virt/tdx: Embed version info in SEAMCALL leaf function definitions Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 09/31] x86/virt/tdx: Move tdx_clflush_page() up in the file Xu Yilun
` (22 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
TDX Module supports optional TDX features (e.g. TDX Connect & TDX Module
Extensions) that won't be enabled by default. It extends TDH.SYS.CONFIG
for host to choose to enable them on bootup.
Call TDH.SYS.CONFIG with a new bitmap input parameter to specify which
features to enable. The bitmap uses the same definitions as
TDX_FEATURES0. But note not all bits in TDX_FEATURES0 are valid for
configuration, e.g. TDX Module Extensions is a service that supports TDX
Connect, it is implicitly enabled when TDX Connect is enabled. Setting
TDX_FEATURES0_EXT in the bitmap has no effect.
TDX Module advances the version of TDH.SYS.CONFIG for the change, so
use the latest version (v1) for optional feature enabling. But
supporting existing Modules which only support v0 is still necessary
until they are deprecated, enumerate via TDX_FEATURES0 to decide which
version to use.
TDX Module updates global metadata when optional features are enabled.
Host should update the cached tdx_sysinfo to reflect these changes.
Co-developed-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.h | 3 ++-
arch/x86/virt/vmx/tdx/tdx.c | 16 +++++++++++++++-
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index e5a9331df451..870bb75da3ba 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -58,7 +58,8 @@
#define TDH_PHYMEM_CACHE_WB 40
#define TDH_PHYMEM_PAGE_WBINVD 41
#define TDH_VP_WR 43
-#define TDH_SYS_CONFIG 45
+#define TDH_SYS_CONFIG_V0 45
+#define TDH_SYS_CONFIG SEAMCALL_LEAF_VER(TDH_SYS_CONFIG_V0, 1)
/* TDX page types */
#define PT_NDA 0x0
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 130214933c2f..0c5d6bdd810f 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1353,6 +1353,7 @@ static int construct_tdmrs(struct list_head *tmb_list,
static int config_tdx_module(struct tdmr_info_list *tdmr_list, u64 global_keyid)
{
struct tdx_module_args args = {};
+ u64 seamcall_fn = TDH_SYS_CONFIG_V0;
u64 *tdmr_pa_array;
size_t array_sz;
int i, ret;
@@ -1377,7 +1378,15 @@ static int config_tdx_module(struct tdmr_info_list *tdmr_list, u64 global_keyid)
args.rcx = __pa(tdmr_pa_array);
args.rdx = tdmr_list->nr_consumed_tdmrs;
args.r8 = global_keyid;
- ret = seamcall_prerr(TDH_SYS_CONFIG, &args);
+
+ if (tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_TDXCONNECT) {
+ args.r9 |= TDX_FEATURES0_TDXCONNECT;
+ args.r11 = ktime_get_real_seconds();
+ /* These parameters requires version >= 1 */
+ seamcall_fn = TDH_SYS_CONFIG;
+ }
+
+ ret = seamcall_prerr(seamcall_fn, &args);
/* Free the array as it is not required anymore. */
kfree(tdmr_pa_array);
@@ -1537,6 +1546,11 @@ static int init_tdx_module(void)
if (ret)
goto err_free_pamts;
+ /* configuration to tdx module may change tdx_sysinfo, update it */
+ ret = get_tdx_sys_info(&tdx_sysinfo);
+ if (ret)
+ goto err_reset_pamts;
+
/* Config the key of global KeyID on all packages */
ret = config_global_keyid();
if (ret)
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 09/31] x86/virt/tdx: Move tdx_clflush_page() up in the file
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (7 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 08/31] x86/virt/tdx: Configure TDX Module with optional TDX Connect feature Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 10/31] x86/virt/tdx: Add extra memory to TDX Module for Extensions Xu Yilun
` (21 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Prepare to add more callers earlier in this file, so move this
function up in advance.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.c | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 0c5d6bdd810f..4fb56bb442f0 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1502,6 +1502,17 @@ static int init_tdmrs(struct tdmr_info_list *tdmr_list)
return 0;
}
+/*
+ * The TDX module exposes a CLFLUSH_BEFORE_ALLOC bit to specify whether
+ * a CLFLUSH of pages is required before handing them to the TDX module.
+ * Be conservative and make the code simpler by doing the CLFLUSH
+ * unconditionally.
+ */
+static void tdx_clflush_page(struct page *page)
+{
+ clflush_cache_range(page_to_virt(page), PAGE_SIZE);
+}
+
static int init_tdx_module(void)
{
int ret;
@@ -1936,17 +1947,6 @@ static inline u64 tdx_tdr_pa(struct tdx_td *td)
return page_to_phys(td->tdr_page);
}
-/*
- * The TDX module exposes a CLFLUSH_BEFORE_ALLOC bit to specify whether
- * a CLFLUSH of pages is required before handing them to the TDX module.
- * Be conservative and make the code simpler by doing the CLFLUSH
- * unconditionally.
- */
-static void tdx_clflush_page(struct page *page)
-{
- clflush_cache_range(page_to_virt(page), PAGE_SIZE);
-}
-
noinstr u64 tdh_vp_enter(struct tdx_vp *td, struct tdx_module_args *args)
{
args->rcx = td->tdvpr_pa;
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 10/31] x86/virt/tdx: Add extra memory to TDX Module for Extensions
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (8 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 09/31] x86/virt/tdx: Move tdx_clflush_page() up in the file Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 11/31] x86/virt/tdx: Make TDX Module initialize Extensions Xu Yilun
` (20 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Adding more memory to TDX Module is the first step to enable Extensions.
Currently, TDX Module memory use is relatively static. But, some new
features (called "TDX Module Extensions") need to use memory more
dynamically. While 'static' here means the kernel provides necessary
amount of memory to TDX Module for its basic functionalities, 'dynamic'
means extra memory is needed only if new optional features are to be
enabled. So add a new memory feeding process backed by a new SEAMCALL
TDH.EXT.MEM.ADD.
The process is mostly the same as adding PAMT. The kernel queries TDX
Module how much memory needed, allocates it, hands it over, and never
gets it back.
TDH.EXT.MEM.ADD uses tdx_page_array to provide control (private) pages
to TDX Module. Introduce a tdx_clflush_page_array() helper to flush
shared cache before SEAMCALL, to avoid shared cache write back damages
these private pages.
For now, TDX Module Extensions consume relatively large amount of
memory (~50MB). Use contiguous page allocation to avoid permanently
fragment too much memory. Print this readout value on TDX Module
Extensions initialization for visibility.
Co-developed-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.h | 1 +
arch/x86/virt/vmx/tdx/tdx.c | 92 ++++++++++++++++++++++++++++++++++++-
2 files changed, 91 insertions(+), 2 deletions(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index 870bb75da3ba..31ccdfcf518c 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -60,6 +60,7 @@
#define TDH_VP_WR 43
#define TDH_SYS_CONFIG_V0 45
#define TDH_SYS_CONFIG SEAMCALL_LEAF_VER(TDH_SYS_CONFIG_V0, 1)
+#define TDH_EXT_MEM_ADD 61
/* TDX page types */
#define PT_NDA 0x0
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 4fb56bb442f0..5fae17c13191 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -560,7 +560,7 @@ static int tdx_alloc_pages_contig(unsigned int nr_pages, struct page **pages,
* Similar to tdx_page_array_alloc(), after allocating with this
* function, call tdx_page_array_populate() to populate the tdx_page_array.
*/
-static __maybe_unused struct tdx_page_array *
+static struct tdx_page_array *
tdx_page_array_alloc_contig(unsigned int nr_pages)
{
return tdx_page_array_alloc(nr_pages, tdx_alloc_pages_contig, NULL);
@@ -643,7 +643,7 @@ EXPORT_SYMBOL_GPL(tdx_page_array_create_iommu_mt);
#define HPA_LIST_INFO_PFN GENMASK_U64(51, 12)
#define HPA_LIST_INFO_LAST_ENTRY GENMASK_U64(63, 55)
-static u64 __maybe_unused hpa_list_info_assign_raw(struct tdx_page_array *array)
+static u64 hpa_list_info_assign_raw(struct tdx_page_array *array)
{
return FIELD_PREP(HPA_LIST_INFO_FIRST_ENTRY, 0) |
FIELD_PREP(HPA_LIST_INFO_PFN,
@@ -1513,6 +1513,94 @@ static void tdx_clflush_page(struct page *page)
clflush_cache_range(page_to_virt(page), PAGE_SIZE);
}
+static void tdx_clflush_page_array(struct tdx_page_array *array)
+{
+ for (int i = 0; i < array->nents; i++)
+ tdx_clflush_page(array->pages[array->offset + i]);
+}
+
+static int tdx_ext_mem_add(struct tdx_page_array *ext_mem)
+{
+ struct tdx_module_args args = {
+ .rcx = hpa_list_info_assign_raw(ext_mem),
+ };
+ u64 r;
+
+ tdx_clflush_page_array(ext_mem);
+
+ do {
+ r = seamcall_ret(TDH_EXT_MEM_ADD, &args);
+ cond_resched();
+ } while (r == TDX_INTERRUPTED_RESUMABLE);
+
+ if (r != TDX_SUCCESS)
+ return -EFAULT;
+
+ return 0;
+}
+
+static int tdx_ext_mem_setup(struct tdx_page_array *ext_mem)
+{
+ unsigned int populated, offset = 0;
+ int ret;
+
+ /*
+ * tdx_page_array's root page can hold 512 HPAs at most. We have ~50MB
+ * memory to add, re-populate the array and add pages bulk by bulk.
+ */
+ while (1) {
+ populated = tdx_page_array_populate(ext_mem, offset);
+ if (!populated)
+ break;
+
+ ret = tdx_ext_mem_add(ext_mem);
+ if (ret)
+ return ret;
+
+ offset += populated;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused init_tdx_ext(void)
+{
+ struct tdx_page_array *ext_mem = NULL;
+ unsigned int nr_pages;
+ int ret;
+
+ if (!(tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_EXT))
+ return 0;
+
+ nr_pages = tdx_sysinfo.ext.memory_pool_required_pages;
+ /*
+ * memory_pool_required_pages == 0 means no need to add more pages,
+ * skip the memory setup.
+ */
+ if (nr_pages) {
+ ext_mem = tdx_page_array_alloc_contig(nr_pages);
+ if (!ext_mem)
+ return -ENOMEM;
+
+ ret = tdx_ext_mem_setup(ext_mem);
+ if (ret)
+ goto out_ext_mem;
+ }
+
+ /* Extension memory is never reclaimed once assigned */
+ tdx_page_array_ctrl_leak(ext_mem);
+
+ pr_info("%lu KB allocated for TDX Module Extensions\n",
+ nr_pages * PAGE_SIZE / 1024);
+
+ return 0;
+
+out_ext_mem:
+ tdx_page_array_free(ext_mem);
+
+ return ret;
+}
+
static int init_tdx_module(void)
{
int ret;
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 11/31] x86/virt/tdx: Make TDX Module initialize Extensions
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (9 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 10/31] x86/virt/tdx: Add extra memory to TDX Module for Extensions Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 12/31] x86/virt/tdx: Enable the Extensions after basic TDX Module init Xu Yilun
` (19 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
After providing all required memory to TDX Module, initialize the
Extensions via TDH.EXT.INIT, and then Extension-SEAMCALLs can be used.
The initialization of Extensions touches the required memory (previously
provided by TDH.EXT.MEM.ADD) in private manner. If failed, flush cache
before freeing these memory, to avoid private cache write back damages
the shared pages.
TDX should use movdir64b to clear private pages when reclaiming them on
older platforms with the X86_BUG_TDX_PW_MCE erratum. For simplicity,
don't expect this errata on any TDX Extensions supported platform. So
TDX Extensions & all features that require TDX Extensions (e.g. TDX
Connect) will not call the clearing helpers.
Note the "ext_required" global metadata specifies if TDH.EXT.INIT call
is needed. If 0, the Extensions are already working, so skip the SEAMCALL.
Co-developed-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.h | 1 +
arch/x86/virt/vmx/tdx/tdx.c | 45 +++++++++++++++++++++++++++++++++++++
2 files changed, 46 insertions(+)
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index 31ccdfcf518c..a26fe94c07ff 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -60,6 +60,7 @@
#define TDH_VP_WR 43
#define TDH_SYS_CONFIG_V0 45
#define TDH_SYS_CONFIG SEAMCALL_LEAF_VER(TDH_SYS_CONFIG_V0, 1)
+#define TDH_EXT_INIT 60
#define TDH_EXT_MEM_ADD 61
/* TDX page types */
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 5fae17c13191..4134f92425da 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1519,6 +1519,23 @@ static void tdx_clflush_page_array(struct tdx_page_array *array)
tdx_clflush_page(array->pages[array->offset + i]);
}
+/* Initialize the TDX Module Extensions then Extension-SEAMCALLs can be used */
+static int tdx_ext_init(void)
+{
+ struct tdx_module_args args = {};
+ u64 r;
+
+ do {
+ r = seamcall(TDH_EXT_INIT, &args);
+ cond_resched();
+ } while (r == TDX_INTERRUPTED_RESUMABLE);
+
+ if (r != TDX_SUCCESS)
+ return -EFAULT;
+
+ return 0;
+}
+
static int tdx_ext_mem_add(struct tdx_page_array *ext_mem)
{
struct tdx_module_args args = {
@@ -1572,6 +1589,17 @@ static int __maybe_unused init_tdx_ext(void)
if (!(tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_EXT))
return 0;
+ /*
+ * With this errata, TDX should use movdir64b to clear private pages
+ * when reclaiming them. See tdx_quirk_reset_paddr().
+ *
+ * Don't expect this errata on any TDX Extensions supported platform.
+ * All features require TDX Extensions (including TDX Extensions
+ * itself) will never call tdx_quirk_reset_paddr().
+ */
+ if (boot_cpu_has_bug(X86_BUG_TDX_PW_MCE))
+ return -ENXIO;
+
nr_pages = tdx_sysinfo.ext.memory_pool_required_pages;
/*
* memory_pool_required_pages == 0 means no need to add more pages,
@@ -1587,6 +1615,20 @@ static int __maybe_unused init_tdx_ext(void)
goto out_ext_mem;
}
+ /*
+ * ext_required == 0 means no need to call TDH.EXT.INIT, the Extensions
+ * are already working.
+ */
+ if (tdx_sysinfo.ext.ext_required) {
+ ret = tdx_ext_init();
+ /*
+ * Some pages may have been touched by the TDX module.
+ * Flush cache before returning these pages to kernel.
+ */
+ if (ret)
+ goto out_flush;
+ }
+
/* Extension memory is never reclaimed once assigned */
tdx_page_array_ctrl_leak(ext_mem);
@@ -1595,6 +1637,9 @@ static int __maybe_unused init_tdx_ext(void)
return 0;
+out_flush:
+ if (ext_mem)
+ wbinvd_on_all_cpus();
out_ext_mem:
tdx_page_array_free(ext_mem);
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 12/31] x86/virt/tdx: Enable the Extensions after basic TDX Module init
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (10 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 11/31] x86/virt/tdx: Make TDX Module initialize Extensions Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 13/31] x86/virt/tdx: Extend tdx_clflush_page() to handle compound pages Xu Yilun
` (18 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
The detailed initialization flow for TDX Module Extensions has been
fully implemented. Enable the flow after basic TDX Module
initialization.
Theoretically, the Extensions can be initialized later when the first
usage of the Extension-SEAMCALL comes. That would save or postpone the
usage of ~50M memory. But it isn't worth the complexity, the needs for
Extensions are vast but the savings are little for a typical TDX capable
system (about 0.001% of memory). So just enable it along with the basic
TDX.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 4134f92425da..0e1ad793e648 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1580,7 +1580,7 @@ static int tdx_ext_mem_setup(struct tdx_page_array *ext_mem)
return 0;
}
-static int __maybe_unused init_tdx_ext(void)
+static int init_tdx_ext(void)
{
struct tdx_page_array *ext_mem = NULL;
unsigned int nr_pages;
@@ -1705,6 +1705,10 @@ static int init_tdx_module(void)
if (ret)
goto err_reset_pamts;
+ ret = init_tdx_ext();
+ if (ret)
+ goto err_reset_pamts;
+
pr_info("%lu KB allocated for PAMT\n", tdmrs_count_pamt_kb(&tdx_tdmr_list));
out_put_tdxmem:
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 13/31] x86/virt/tdx: Extend tdx_clflush_page() to handle compound pages
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (11 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 12/31] x86/virt/tdx: Enable the Extensions after basic TDX Module init Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 14/31] PCI/TSM: Report active IDE streams per host bridge Xu Yilun
` (17 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Use page_size() to correctly flush the range that a compound page
covers.
Recall that TDX Module requires VMM to provide IOMMU metadata known as
IOMMU_MT, which contains some multi-order pages. Like all other
metadata, TDX Module will convert these multi-order pages to private so
VMM should flush the shared cache beforehand. Extend tdx_clflush_page()
to handle this case.
The usage of tdx_clflush_page() for IOMMU_MT will be introduced later,
but the change stands as a valid improvement on its own.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 0e1ad793e648..e7d47fbe7057 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1510,7 +1510,7 @@ static int init_tdmrs(struct tdmr_info_list *tdmr_list)
*/
static void tdx_clflush_page(struct page *page)
{
- clflush_cache_range(page_to_virt(page), PAGE_SIZE);
+ clflush_cache_range(page_to_virt(page), page_size(page));
}
static void tdx_clflush_page_array(struct tdx_page_array *array)
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 14/31] PCI/TSM: Report active IDE streams per host bridge
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (12 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 13/31] x86/virt/tdx: Extend tdx_clflush_page() to handle compound pages Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 15/31] coco/tdx-host: Introduce a "tdx_host" device Xu Yilun
` (16 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
From: Dan Williams <dan.j.williams@intel.com>
The first attempt at an ABI for this failed to account for naming
collisions across host bridges:
Commit a4438f06b1db ("PCI/TSM: Report active IDE streams")
Revive this ABI with a per host bridge link that appears at first stream
creation for a given host bridge and disappears after the last stream is
removed.
For systems with many host bridge objects it allows:
ls /sys/class/tsm/tsmN/pci*/stream*
...to find all the host bridges with active streams without first iterating
over all host bridges. Yilun notes that is handy to have this short cut [1]
and from an administrator perspective it helps with inventory for
constrained stream resources.
Link: http://lore.kernel.org/aXLtILY85oMU5qlb@yilunxu-OptiPlex-7050 [1]
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
Documentation/ABI/testing/sysfs-class-tsm | 13 +++
include/linux/pci-ide.h | 2 +
include/linux/tsm.h | 3 +
drivers/pci/ide.c | 4 +
drivers/virt/coco/tsm-core.c | 97 +++++++++++++++++++++++
5 files changed, 119 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-class-tsm b/Documentation/ABI/testing/sysfs-class-tsm
index 2949468deaf7..1ddb8f357961 100644
--- a/Documentation/ABI/testing/sysfs-class-tsm
+++ b/Documentation/ABI/testing/sysfs-class-tsm
@@ -7,3 +7,16 @@ Description:
signals when the PCI layer is able to support establishment of
link encryption and other device-security features coordinated
through a platform tsm.
+
+What: /sys/class/tsm/tsmN/pciDDDD:BB
+Contact: linux-pci@vger.kernel.org
+Description:
+ (RO) When a PCIe host bridge has established a secure connection
+ via a TSM to an endpoint, this symlink appears. It facilitates a
+ TSM instance scoped view of PCIe Link Encryption and Secure
+ Session resource consumption across host bridges. The symlink
+ appears when a host bridge has 1 or more IDE streams established
+ with this TSM, and disappears when that number returns to 0. See
+ Documentation/ABI/testing/sysfs-devices-pci-host-bridge for the
+ description of the pciDDDD:BB/streamH.R.E symlink and the
+ pciDDDD:BB/available_secure_streams attribute.
diff --git a/include/linux/pci-ide.h b/include/linux/pci-ide.h
index ae07d9f699c0..381a1bf22a95 100644
--- a/include/linux/pci-ide.h
+++ b/include/linux/pci-ide.h
@@ -82,6 +82,7 @@ struct pci_ide_regs {
* @host_bridge_stream: allocated from host bridge @ide_stream_ida pool
* @stream_id: unique Stream ID (within Partner Port pairing)
* @name: name of the established Selective IDE Stream in sysfs
+ * @tsm_dev: For TSM established IDE, the TSM device context
*
* Negative @stream_id values indicate "uninitialized" on the
* expectation that with TSM established IDE the TSM owns the stream_id
@@ -93,6 +94,7 @@ struct pci_ide {
u8 host_bridge_stream;
int stream_id;
const char *name;
+ struct tsm_dev *tsm_dev;
};
/*
diff --git a/include/linux/tsm.h b/include/linux/tsm.h
index 381c53244c83..7f72a154b6b2 100644
--- a/include/linux/tsm.h
+++ b/include/linux/tsm.h
@@ -123,4 +123,7 @@ int tsm_report_unregister(const struct tsm_report_ops *ops);
struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *ops);
void tsm_unregister(struct tsm_dev *tsm_dev);
struct tsm_dev *find_tsm_dev(int id);
+struct pci_ide;
+int tsm_ide_stream_register(struct pci_ide *ide);
+void tsm_ide_stream_unregister(struct pci_ide *ide);
#endif /* __TSM_H */
diff --git a/drivers/pci/ide.c b/drivers/pci/ide.c
index be74e8f0ae21..b35e8aba7ecb 100644
--- a/drivers/pci/ide.c
+++ b/drivers/pci/ide.c
@@ -11,6 +11,7 @@
#include <linux/pci_regs.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
+#include <linux/tsm.h>
#include "pci.h"
@@ -372,6 +373,9 @@ void pci_ide_stream_release(struct pci_ide *ide)
if (ide->partner[PCI_IDE_EP].enable)
pci_ide_stream_disable(pdev, ide);
+ if (ide->tsm_dev)
+ tsm_ide_stream_unregister(ide);
+
if (ide->partner[PCI_IDE_RP].setup)
pci_ide_stream_teardown(rp, ide);
diff --git a/drivers/virt/coco/tsm-core.c b/drivers/virt/coco/tsm-core.c
index 98dcf7d836df..ece7cd7ea9d8 100644
--- a/drivers/virt/coco/tsm-core.c
+++ b/drivers/virt/coco/tsm-core.c
@@ -4,10 +4,12 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/tsm.h>
+#include <linux/pci.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/cleanup.h>
#include <linux/pci-tsm.h>
+#include <linux/pci-ide.h>
static struct class *tsm_class;
static DEFINE_IDA(tsm_ida);
@@ -104,6 +106,100 @@ void tsm_unregister(struct tsm_dev *tsm_dev)
}
EXPORT_SYMBOL_GPL(tsm_unregister);
+static DEFINE_XARRAY(tsm_ide_streams);
+static DEFINE_MUTEX(tsm_ide_streams_lock);
+
+/* tracker for the bridge symlink when the bridge has any streams */
+struct tsm_ide_stream {
+ struct tsm_dev *tsm_dev;
+ struct pci_host_bridge *bridge;
+ struct kref kref;
+};
+
+static struct tsm_ide_stream *create_streams(struct tsm_dev *tsm_dev,
+ struct pci_host_bridge *bridge)
+{
+ int rc;
+
+ struct tsm_ide_stream *streams __free(kfree) =
+ kzalloc(sizeof(*streams), GFP_KERNEL);
+ if (!streams)
+ return NULL;
+
+ streams->tsm_dev = tsm_dev;
+ streams->bridge = bridge;
+ kref_init(&streams->kref);
+ rc = xa_insert(&tsm_ide_streams, (unsigned long)bridge, streams,
+ GFP_KERNEL);
+ if (rc)
+ return NULL;
+
+ rc = sysfs_create_link(&tsm_dev->dev.kobj, &bridge->dev.kobj,
+ dev_name(&bridge->dev));
+ if (rc) {
+ xa_erase(&tsm_ide_streams, (unsigned long)bridge);
+ return NULL;
+ }
+
+ return no_free_ptr(streams);
+}
+
+int tsm_ide_stream_register(struct pci_ide *ide)
+{
+ struct tsm_ide_stream *streams;
+ struct pci_dev *pdev = ide->pdev;
+ struct pci_tsm *tsm = pdev->tsm;
+ struct tsm_dev *tsm_dev = tsm->tsm_dev;
+ struct pci_host_bridge *bridge = pci_find_host_bridge(pdev->bus);
+
+ guard(mutex)(&tsm_ide_streams_lock);
+ streams = xa_load(&tsm_ide_streams, (unsigned long)bridge);
+ if (streams)
+ kref_get(&streams->kref);
+ else
+ streams = create_streams(tsm_dev, bridge);
+
+ if (!streams)
+ return -ENOMEM;
+ ide->tsm_dev = tsm_dev;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tsm_ide_stream_register);
+
+static void destroy_streams(struct kref *kref)
+{
+ struct tsm_ide_stream *streams =
+ container_of(kref, struct tsm_ide_stream, kref);
+ struct tsm_dev *tsm_dev = streams->tsm_dev;
+ struct pci_host_bridge *bridge = streams->bridge;
+
+ lockdep_assert_held(&tsm_ide_streams_lock);
+ sysfs_remove_link(&tsm_dev->dev.kobj, dev_name(&bridge->dev));
+ xa_erase(&tsm_ide_streams, (unsigned long)bridge);
+ kfree(streams);
+}
+
+void tsm_ide_stream_unregister(struct pci_ide *ide)
+{
+ struct tsm_ide_stream *streams;
+ struct tsm_dev *tsm_dev = ide->tsm_dev;
+ struct pci_dev *pdev = ide->pdev;
+ struct pci_host_bridge *bridge = pci_find_host_bridge(pdev->bus);
+
+ guard(mutex)(&tsm_ide_streams_lock);
+ streams = xa_load(&tsm_ide_streams, (unsigned long)bridge);
+ /* catch API abuse */
+ if (dev_WARN_ONCE(&tsm_dev->dev,
+ !streams || streams->tsm_dev != tsm_dev,
+ "no IDE streams associated with %s\n",
+ dev_name(&bridge->dev)))
+ return;
+ kref_put(&streams->kref, destroy_streams);
+ ide->tsm_dev = NULL;
+}
+EXPORT_SYMBOL_GPL(tsm_ide_stream_unregister);
+
static void tsm_release(struct device *dev)
{
struct tsm_dev *tsm_dev = container_of(dev, typeof(*tsm_dev), dev);
@@ -126,6 +222,7 @@ module_init(tsm_init)
static void __exit tsm_exit(void)
{
class_destroy(tsm_class);
+ xa_destroy(&tsm_ide_streams);
}
module_exit(tsm_exit)
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 15/31] coco/tdx-host: Introduce a "tdx_host" device
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (13 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 14/31] PCI/TSM: Report active IDE streams per host bridge Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 16/31] coco/tdx-host: Support Link TSM for TDX host Xu Yilun
` (15 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
From: Chao Gao <chao.gao@intel.com>
TDX depends on a platform firmware module that is invoked via instructions
similar to vmenter (i.e. enter into a new privileged "root-mode" context to
manage private memory and private device mechanisms). It is a software
construct that depends on the CPU vmxon state to enable invocation of
TDX module ABIs. Unlike other Trusted Execution Environment (TEE) platform
implementations that employ a firmware module running on a PCI device with
an MMIO mailbox for communication, TDX has no hardware device to point to
as the TEE Secure Manager (TSM).
Create a virtual device not only to align with other implementations but
also to make it easier to
- expose metadata (e.g., TDX module version, seamldr version etc) to
the userspace as device attributes
- implement firmware uploader APIs which are tied to a device. This is
needed to support TDX module runtime updates
- enable TDX Connect which will share a common infrastructure with other
platform implementations. In the TDX Connect context, every
architecture has a TSM, represented by a PCIe or virtual device. The
new "tdx_host" device will serve the TSM role.
A faux device is used for TDX because the TDX module is singular within
the system and lacks associated platform resources. Using a faux device
eliminates the need to create a stub bus.
The call to tdx_get_sysinfo() ensures that the TDX module is ready to
provide services.
Note that AMD has a PCI device for the PSP for SEV and ARM CCA will
likely have a faux device [1].
Co-developed-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Signed-off-by: Chao Gao <chao.gao@intel.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
Reviewed-by: Xu Yilun <yilun.xu@linux.intel.com>
Reviewed-by: Kai Huang <kai.huang@intel.com>
Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Link: https://lore.kernel.org/all/2025073035-bulginess-rematch-b92e@gregkh/ # [1]
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
drivers/virt/coco/Kconfig | 2 ++
drivers/virt/coco/tdx-host/Kconfig | 10 +++++++
drivers/virt/coco/Makefile | 1 +
drivers/virt/coco/tdx-host/Makefile | 1 +
arch/x86/virt/vmx/tdx/tdx.c | 2 +-
drivers/virt/coco/tdx-host/tdx-host.c | 43 +++++++++++++++++++++++++++
6 files changed, 58 insertions(+), 1 deletion(-)
create mode 100644 drivers/virt/coco/tdx-host/Kconfig
create mode 100644 drivers/virt/coco/tdx-host/Makefile
create mode 100644 drivers/virt/coco/tdx-host/tdx-host.c
diff --git a/drivers/virt/coco/Kconfig b/drivers/virt/coco/Kconfig
index df1cfaf26c65..f7691f64fbe3 100644
--- a/drivers/virt/coco/Kconfig
+++ b/drivers/virt/coco/Kconfig
@@ -17,5 +17,7 @@ source "drivers/virt/coco/arm-cca-guest/Kconfig"
source "drivers/virt/coco/guest/Kconfig"
endif
+source "drivers/virt/coco/tdx-host/Kconfig"
+
config TSM
bool
diff --git a/drivers/virt/coco/tdx-host/Kconfig b/drivers/virt/coco/tdx-host/Kconfig
new file mode 100644
index 000000000000..d35d85ef91c0
--- /dev/null
+++ b/drivers/virt/coco/tdx-host/Kconfig
@@ -0,0 +1,10 @@
+config TDX_HOST_SERVICES
+ tristate "TDX Host Services Driver"
+ depends on INTEL_TDX_HOST
+ default m
+ help
+ Enable access to TDX host services like module update and
+ extensions (e.g. TDX Connect).
+
+ Say y or m if enabling support for confidential virtual machine
+ support (CONFIG_INTEL_TDX_HOST). The module is called tdx_host.ko.
diff --git a/drivers/virt/coco/Makefile b/drivers/virt/coco/Makefile
index cb52021912b3..b323b0ae4f82 100644
--- a/drivers/virt/coco/Makefile
+++ b/drivers/virt/coco/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_EFI_SECRET) += efi_secret/
obj-$(CONFIG_ARM_PKVM_GUEST) += pkvm-guest/
obj-$(CONFIG_SEV_GUEST) += sev-guest/
obj-$(CONFIG_INTEL_TDX_GUEST) += tdx-guest/
+obj-$(CONFIG_INTEL_TDX_HOST) += tdx-host/
obj-$(CONFIG_ARM_CCA_GUEST) += arm-cca-guest/
obj-$(CONFIG_TSM) += tsm-core.o
obj-$(CONFIG_TSM_GUEST) += guest/
diff --git a/drivers/virt/coco/tdx-host/Makefile b/drivers/virt/coco/tdx-host/Makefile
new file mode 100644
index 000000000000..e61e749a8dff
--- /dev/null
+++ b/drivers/virt/coco/tdx-host/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_TDX_HOST_SERVICES) += tdx-host.o
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index e7d47fbe7057..cd0948794b6c 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -2057,7 +2057,7 @@ const struct tdx_sys_info *tdx_get_sysinfo(void)
return p;
}
-EXPORT_SYMBOL_FOR_KVM(tdx_get_sysinfo);
+EXPORT_SYMBOL_FOR_MODULES(tdx_get_sysinfo, "kvm-intel,tdx-host");
u32 tdx_get_nr_guest_keyids(void)
{
diff --git a/drivers/virt/coco/tdx-host/tdx-host.c b/drivers/virt/coco/tdx-host/tdx-host.c
new file mode 100644
index 000000000000..c77885392b09
--- /dev/null
+++ b/drivers/virt/coco/tdx-host/tdx-host.c
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TDX host user interface driver
+ *
+ * Copyright (C) 2025 Intel Corporation
+ */
+
+#include <linux/device/faux.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+
+#include <asm/cpu_device_id.h>
+#include <asm/tdx.h>
+
+static const struct x86_cpu_id tdx_host_ids[] = {
+ X86_MATCH_FEATURE(X86_FEATURE_TDX_HOST_PLATFORM, NULL),
+ {}
+};
+MODULE_DEVICE_TABLE(x86cpu, tdx_host_ids);
+
+static struct faux_device *fdev;
+
+static int __init tdx_host_init(void)
+{
+ if (!x86_match_cpu(tdx_host_ids) || !tdx_get_sysinfo())
+ return -ENODEV;
+
+ fdev = faux_device_create(KBUILD_MODNAME, NULL, NULL);
+ if (!fdev)
+ return -ENODEV;
+
+ return 0;
+}
+module_init(tdx_host_init);
+
+static void __exit tdx_host_exit(void)
+{
+ faux_device_destroy(fdev);
+}
+module_exit(tdx_host_exit);
+
+MODULE_DESCRIPTION("TDX Host Services");
+MODULE_LICENSE("GPL");
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 16/31] coco/tdx-host: Support Link TSM for TDX host
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (14 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 15/31] coco/tdx-host: Introduce a "tdx_host" device Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 17/31] acpi: Add KEYP support to fw_table parsing Xu Yilun
` (14 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Register a Link TSM instance to support host side TSM operations for
TDISP, when the TDX Connect support bit is set by TDX Module in
tdx_feature0.
This is the main purpose of an independent tdx-host module out of TDX
core. Recall that a TEE Security Manager (TSM) is a platform agent that
speaks the TEE Device Interface Security Protocol (TDISP) to PCIe
devices and manages private memory resources for the platform. An
independent tdx-host module allows for device-security enumeration and
initialization flows to be deferred from other TDX Module initialization
requirements. Crucially, when / if TDX Module init moves earlier in x86
initialization flow this driver is still guaranteed to run after IOMMU
and PCI init (i.e. subsys_initcall() vs device_initcall()).
The ability to unload the module, or unbind the driver is also useful
for debug and coarse grained transitioning between PCI TSM operation and
PCI CMA operation (native kernel PCI device authentication).
For now only verify TDX Connect support in TDX Module and enable TDX
Module Extentions. The TSM support are basic boilerplate with operation
flows to be added later.
Co-developed-by: Dan Williams <dan.j.williams@intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
drivers/virt/coco/tdx-host/Kconfig | 5 +
drivers/virt/coco/tdx-host/tdx-host.c | 138 +++++++++++++++++++++++++-
2 files changed, 141 insertions(+), 2 deletions(-)
diff --git a/drivers/virt/coco/tdx-host/Kconfig b/drivers/virt/coco/tdx-host/Kconfig
index d35d85ef91c0..32add81b7d56 100644
--- a/drivers/virt/coco/tdx-host/Kconfig
+++ b/drivers/virt/coco/tdx-host/Kconfig
@@ -8,3 +8,8 @@ config TDX_HOST_SERVICES
Say y or m if enabling support for confidential virtual machine
support (CONFIG_INTEL_TDX_HOST). The module is called tdx_host.ko.
+
+config TDX_CONNECT
+ def_bool y
+ depends on TDX_HOST_SERVICES
+ depends on PCI_TSM
diff --git a/drivers/virt/coco/tdx-host/tdx-host.c b/drivers/virt/coco/tdx-host/tdx-host.c
index c77885392b09..5ea35a514865 100644
--- a/drivers/virt/coco/tdx-host/tdx-host.c
+++ b/drivers/virt/coco/tdx-host/tdx-host.c
@@ -8,9 +8,13 @@
#include <linux/device/faux.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
+#include <linux/pci.h>
+#include <linux/pci-tsm.h>
+#include <linux/tsm.h>
#include <asm/cpu_device_id.h>
#include <asm/tdx.h>
+#include <asm/tdx_global_metadata.h>
static const struct x86_cpu_id tdx_host_ids[] = {
X86_MATCH_FEATURE(X86_FEATURE_TDX_HOST_PLATFORM, NULL),
@@ -18,14 +22,144 @@ static const struct x86_cpu_id tdx_host_ids[] = {
};
MODULE_DEVICE_TABLE(x86cpu, tdx_host_ids);
+/*
+ * The global pointer is for features which won't be affected by tdx_sysinfo
+ * change after TDX Module update, e.g. TDX Connect, so could cache it. A
+ * counterexample is the TDX Module version.
+ */
+static const struct tdx_sys_info *tdx_sysinfo;
+
+struct tdx_tsm_link {
+ struct pci_tsm_pf0 pci;
+};
+
+static struct tdx_tsm_link *to_tdx_tsm_link(struct pci_tsm *tsm)
+{
+ return container_of(tsm, struct tdx_tsm_link, pci.base_tsm);
+}
+
+static int tdx_tsm_link_connect(struct pci_dev *pdev)
+{
+ return -ENXIO;
+}
+
+static void tdx_tsm_link_disconnect(struct pci_dev *pdev)
+{
+}
+
+static struct pci_tsm *tdx_tsm_link_pf0_probe(struct tsm_dev *tsm_dev,
+ struct pci_dev *pdev)
+{
+ int rc;
+
+ struct tdx_tsm_link *tlink __free(kfree) = kzalloc_obj(*tlink);
+ if (!tlink)
+ return NULL;
+
+ rc = pci_tsm_pf0_constructor(pdev, &tlink->pci, tsm_dev);
+ if (rc)
+ return NULL;
+
+ return &no_free_ptr(tlink)->pci.base_tsm;
+}
+
+static void tdx_tsm_link_pf0_remove(struct pci_tsm *tsm)
+{
+ struct tdx_tsm_link *tlink = to_tdx_tsm_link(tsm);
+
+ pci_tsm_pf0_destructor(&tlink->pci);
+ kfree(tlink);
+}
+
+static struct pci_tsm *tdx_tsm_link_fn_probe(struct tsm_dev *tsm_dev,
+ struct pci_dev *pdev)
+{
+ int rc;
+
+ struct pci_tsm *pci_tsm __free(kfree) = kzalloc_obj(*pci_tsm);
+ if (!pci_tsm)
+ return NULL;
+
+ rc = pci_tsm_link_constructor(pdev, pci_tsm, tsm_dev);
+ if (rc)
+ return NULL;
+
+ return no_free_ptr(pci_tsm);
+}
+
+static struct pci_tsm *tdx_tsm_link_probe(struct tsm_dev *tsm_dev,
+ struct pci_dev *pdev)
+{
+ if (is_pci_tsm_pf0(pdev))
+ return tdx_tsm_link_pf0_probe(tsm_dev, pdev);
+
+ return tdx_tsm_link_fn_probe(tsm_dev, pdev);
+}
+
+static void tdx_tsm_link_remove(struct pci_tsm *tsm)
+{
+ if (is_pci_tsm_pf0(tsm->pdev)) {
+ tdx_tsm_link_pf0_remove(tsm);
+ return;
+ }
+
+ /* for sub-functions */
+ kfree(tsm);
+}
+
+static struct pci_tsm_ops tdx_tsm_link_ops = {
+ .probe = tdx_tsm_link_probe,
+ .remove = tdx_tsm_link_remove,
+ .connect = tdx_tsm_link_connect,
+ .disconnect = tdx_tsm_link_disconnect,
+};
+
+static void unregister_link_tsm(void *link)
+{
+ tsm_unregister(link);
+}
+
+static int __maybe_unused tdx_connect_init(struct device *dev)
+{
+ struct tsm_dev *link;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_TDX_CONNECT))
+ return 0;
+
+ if (!(tdx_sysinfo->features.tdx_features0 & TDX_FEATURES0_TDXCONNECT))
+ return 0;
+
+ link = tsm_register(dev, &tdx_tsm_link_ops);
+ if (IS_ERR(link))
+ return dev_err_probe(dev, PTR_ERR(link),
+ "failed to register TSM\n");
+
+ return devm_add_action_or_reset(dev, unregister_link_tsm, link);
+}
+
+static int tdx_host_probe(struct faux_device *fdev)
+{
+ /* TODO: do tdx_connect_init() when it is fully implemented. */
+ return 0;
+}
+
+static struct faux_device_ops tdx_host_ops = {
+ .probe = tdx_host_probe,
+};
+
static struct faux_device *fdev;
static int __init tdx_host_init(void)
{
- if (!x86_match_cpu(tdx_host_ids) || !tdx_get_sysinfo())
+ if (!x86_match_cpu(tdx_host_ids))
+ return -ENODEV;
+
+ tdx_sysinfo = tdx_get_sysinfo();
+ if (!tdx_sysinfo)
return -ENODEV;
- fdev = faux_device_create(KBUILD_MODNAME, NULL, NULL);
+ fdev = faux_device_create(KBUILD_MODNAME, NULL, &tdx_host_ops);
if (!fdev)
return -ENODEV;
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 17/31] acpi: Add KEYP support to fw_table parsing
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (15 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 16/31] coco/tdx-host: Support Link TSM for TDX host Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 18/31] iommu/vt-d: Cache max domain ID to avoid redundant calculation Xu Yilun
` (13 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
From: Dave Jiang <dave.jiang@intel.com>
KEYP ACPI table can be parsed using the common fw_table handlers. Add
additional support to detect and parse the table.
Co-developed-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
---
include/linux/acpi.h | 3 +++
include/linux/fw_table.h | 1 +
drivers/acpi/tables.c | 12 +++++++++++-
lib/fw_table.c | 9 +++++++++
4 files changed, 24 insertions(+), 1 deletion(-)
diff --git a/include/linux/acpi.h b/include/linux/acpi.h
index 4d2f0bed7a06..e5b51bd46600 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -247,6 +247,9 @@ int acpi_table_parse_madt(enum acpi_madt_type id,
int __init_or_acpilib
acpi_table_parse_cedt(enum acpi_cedt_type id,
acpi_tbl_entry_handler_arg handler_arg, void *arg);
+int __init_or_acpilib
+acpi_table_parse_keyp(enum acpi_keyp_type id,
+ acpi_tbl_entry_handler_arg handler_arg, void *arg);
int acpi_parse_mcfg (struct acpi_table_header *header);
void acpi_table_print_madt_entry (struct acpi_subtable_header *madt);
diff --git a/include/linux/fw_table.h b/include/linux/fw_table.h
index 9bd605b87c4c..293252cb0b7e 100644
--- a/include/linux/fw_table.h
+++ b/include/linux/fw_table.h
@@ -36,6 +36,7 @@ union acpi_subtable_headers {
struct acpi_prmt_module_header prmt;
struct acpi_cedt_header cedt;
struct acpi_cdat_header cdat;
+ struct acpi_keyp_common_header keyp;
};
int acpi_parse_entries_array(char *id, unsigned long table_size,
diff --git a/drivers/acpi/tables.c b/drivers/acpi/tables.c
index 4286e4af1092..8dc60632faf3 100644
--- a/drivers/acpi/tables.c
+++ b/drivers/acpi/tables.c
@@ -299,6 +299,16 @@ acpi_table_parse_cedt(enum acpi_cedt_type id,
}
EXPORT_SYMBOL_ACPI_LIB(acpi_table_parse_cedt);
+int __init_or_acpilib
+acpi_table_parse_keyp(enum acpi_keyp_type id,
+ acpi_tbl_entry_handler_arg handler_arg, void *arg)
+{
+ return __acpi_table_parse_entries(ACPI_SIG_KEYP,
+ sizeof(struct acpi_table_keyp), id,
+ NULL, handler_arg, arg, 0);
+}
+EXPORT_SYMBOL_ACPI_LIB(acpi_table_parse_keyp);
+
int __init acpi_table_parse_entries(char *id, unsigned long table_size,
int entry_id,
acpi_tbl_entry_handler handler,
@@ -408,7 +418,7 @@ static const char table_sigs[][ACPI_NAMESEG_SIZE] __nonstring_array __initconst
ACPI_SIG_PSDT, ACPI_SIG_RSDT, ACPI_SIG_XSDT, ACPI_SIG_SSDT,
ACPI_SIG_IORT, ACPI_SIG_NFIT, ACPI_SIG_HMAT, ACPI_SIG_PPTT,
ACPI_SIG_NHLT, ACPI_SIG_AEST, ACPI_SIG_CEDT, ACPI_SIG_AGDI,
- ACPI_SIG_NBFT, ACPI_SIG_SWFT, ACPI_SIG_MPAM};
+ ACPI_SIG_NBFT, ACPI_SIG_SWFT, ACPI_SIG_MPAM, ACPI_SIG_KEYP};
#define ACPI_HEADER_SIZE sizeof(struct acpi_table_header)
diff --git a/lib/fw_table.c b/lib/fw_table.c
index 16291814450e..147e3895e94c 100644
--- a/lib/fw_table.c
+++ b/lib/fw_table.c
@@ -20,6 +20,7 @@ enum acpi_subtable_type {
ACPI_SUBTABLE_PRMT,
ACPI_SUBTABLE_CEDT,
CDAT_SUBTABLE,
+ ACPI_SUBTABLE_KEYP,
};
struct acpi_subtable_entry {
@@ -41,6 +42,8 @@ acpi_get_entry_type(struct acpi_subtable_entry *entry)
return entry->hdr->cedt.type;
case CDAT_SUBTABLE:
return entry->hdr->cdat.type;
+ case ACPI_SUBTABLE_KEYP:
+ return entry->hdr->keyp.type;
}
return 0;
}
@@ -61,6 +64,8 @@ acpi_get_entry_length(struct acpi_subtable_entry *entry)
__le16 length = (__force __le16)entry->hdr->cdat.length;
return le16_to_cpu(length);
+ case ACPI_SUBTABLE_KEYP:
+ return entry->hdr->keyp.length;
}
}
return 0;
@@ -80,6 +85,8 @@ acpi_get_subtable_header_length(struct acpi_subtable_entry *entry)
return sizeof(entry->hdr->cedt);
case CDAT_SUBTABLE:
return sizeof(entry->hdr->cdat);
+ case ACPI_SUBTABLE_KEYP:
+ return sizeof(entry->hdr->keyp);
}
return 0;
}
@@ -95,6 +102,8 @@ acpi_get_subtable_type(char *id)
return ACPI_SUBTABLE_CEDT;
if (strncmp(id, ACPI_SIG_CDAT, 4) == 0)
return CDAT_SUBTABLE;
+ if (strncmp(id, ACPI_SIG_KEYP, 4) == 0)
+ return ACPI_SUBTABLE_KEYP;
return ACPI_SUBTABLE_COMMON;
}
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 18/31] iommu/vt-d: Cache max domain ID to avoid redundant calculation
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (16 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 17/31] acpi: Add KEYP support to fw_table parsing Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 19/31] iommu/vt-d: Reserve the MSB domain ID bit for the TDX module Xu Yilun
` (12 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
From: Lu Baolu <baolu.lu@linux.intel.com>
The cap_ndoms() helper calculates the maximum available domain ID from
the value of capability register, which can be inefficient if called
repeatedly. Cache the maximum supported domain ID in max_domain_id field
during initialization to avoid redundant calls to cap_ndoms() throughout
the IOMMU driver.
No functionality change.
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
drivers/iommu/intel/iommu.h | 1 +
drivers/iommu/intel/dmar.c | 1 +
drivers/iommu/intel/iommu.c | 10 +++++-----
3 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h
index 599913fb65d5..4a21ab6a311d 100644
--- a/drivers/iommu/intel/iommu.h
+++ b/drivers/iommu/intel/iommu.h
@@ -705,6 +705,7 @@ struct intel_iommu {
/* mutex to protect domain_ida */
struct mutex did_lock;
struct ida domain_ida; /* domain id allocator */
+ unsigned long max_domain_id;
unsigned long *copied_tables; /* bitmap of copied tables */
spinlock_t lock; /* protect context, domain ids */
struct root_entry *root_entry; /* virtual address */
diff --git a/drivers/iommu/intel/dmar.c b/drivers/iommu/intel/dmar.c
index d68c06025cac..93efd1a5dc5b 100644
--- a/drivers/iommu/intel/dmar.c
+++ b/drivers/iommu/intel/dmar.c
@@ -1099,6 +1099,7 @@ static int alloc_iommu(struct dmar_drhd_unit *drhd)
spin_lock_init(&iommu->lock);
ida_init(&iommu->domain_ida);
mutex_init(&iommu->did_lock);
+ iommu->max_domain_id = cap_ndoms(iommu->cap);
ver = readl(iommu->reg + DMAR_VER_REG);
pr_info("%s: reg_base_addr %llx ver %d:%d cap %llx ecap %llx\n",
diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index ef7613b177b9..9a57f78647ed 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -1043,7 +1043,7 @@ int domain_attach_iommu(struct dmar_domain *domain, struct intel_iommu *iommu)
}
num = ida_alloc_range(&iommu->domain_ida, IDA_START_DID,
- cap_ndoms(iommu->cap) - 1, GFP_KERNEL);
+ iommu->max_domain_id - 1, GFP_KERNEL);
if (num < 0) {
pr_err("%s: No free domain ids\n", iommu->name);
goto err_unlock;
@@ -1107,7 +1107,7 @@ static void copied_context_tear_down(struct intel_iommu *iommu,
did_old = context_domain_id(context);
context_clear_entry(context);
- if (did_old < cap_ndoms(iommu->cap)) {
+ if (did_old < iommu->max_domain_id) {
iommu->flush.flush_context(iommu, did_old,
PCI_DEVID(bus, devfn),
DMA_CCMD_MASK_NOBIT,
@@ -1505,7 +1505,7 @@ static int copy_context_table(struct intel_iommu *iommu,
continue;
did = context_domain_id(&ce);
- if (did >= 0 && did < cap_ndoms(iommu->cap))
+ if (did >= 0 && did < iommu->max_domain_id)
ida_alloc_range(&iommu->domain_ida, did, did, GFP_KERNEL);
set_context_copied(iommu, bus, devfn);
@@ -2425,7 +2425,7 @@ static ssize_t domains_supported_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct intel_iommu *iommu = dev_to_intel_iommu(dev);
- return sysfs_emit(buf, "%ld\n", cap_ndoms(iommu->cap));
+ return sysfs_emit(buf, "%ld\n", iommu->max_domain_id);
}
static DEVICE_ATTR_RO(domains_supported);
@@ -2436,7 +2436,7 @@ static ssize_t domains_used_show(struct device *dev,
unsigned int count = 0;
int id;
- for (id = 0; id < cap_ndoms(iommu->cap); id++)
+ for (id = 0; id < iommu->max_domain_id; id++)
if (ida_exists(&iommu->domain_ida, id))
count++;
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 19/31] iommu/vt-d: Reserve the MSB domain ID bit for the TDX module
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (17 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 18/31] iommu/vt-d: Cache max domain ID to avoid redundant calculation Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-28 16:57 ` kernel test robot
2026-03-28 19:58 ` kernel test robot
2026-03-27 16:01 ` [PATCH v2 20/31] x86/virt/tdx: Add a helper to loop on TDX_INTERRUPTED_RESUMABLE Xu Yilun
` (11 subsequent siblings)
30 siblings, 2 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
From: Lu Baolu <baolu.lu@linux.intel.com>
The Intel TDX Connect Architecture Specification defines some enhancements
for the VT-d architecture to introduce IOMMU support for TEE-IO requests.
Section 2.2, 'Trusted DMA' states that:
"I/O TLB and DID Isolation – When IOMMU is enabled to support TDX
Connect, the IOMMU restricts the VMM’s DID setting, reserving the MSB bit
for the TDX module. The TDX module always sets this reserved bit on the
trusted DMA table. IOMMU tags IOTLB, PASID cache, and context entries to
indicate whether they were created from TEE-IO transactions, ensuring
isolation between TEE and non-TEE requests in translation caches."
Reserve the MSB in the domain ID for the TDX module's use if the
enhancement is required, which is detected if the ECAP.TDXCS bit in the
VT-d extended capability register is set and the TVM Usable field of the
ACPI KEYP table is set.
Co-developed-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
---
drivers/iommu/intel/iommu.h | 1 +
drivers/iommu/intel/dmar.c | 52 ++++++++++++++++++++++++++++++++++++-
2 files changed, 52 insertions(+), 1 deletion(-)
diff --git a/drivers/iommu/intel/iommu.h b/drivers/iommu/intel/iommu.h
index 4a21ab6a311d..0c2b4e38dee7 100644
--- a/drivers/iommu/intel/iommu.h
+++ b/drivers/iommu/intel/iommu.h
@@ -192,6 +192,7 @@
*/
#define ecap_pms(e) (((e) >> 51) & 0x1)
+#define ecap_tdxc(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)
diff --git a/drivers/iommu/intel/dmar.c b/drivers/iommu/intel/dmar.c
index 93efd1a5dc5b..4f9571eee1d4 100644
--- a/drivers/iommu/intel/dmar.c
+++ b/drivers/iommu/intel/dmar.c
@@ -1033,6 +1033,56 @@ static int map_iommu(struct intel_iommu *iommu, struct dmar_drhd_unit *drhd)
return err;
}
+static int keyp_config_unit_tvm_usable(union acpi_subtable_headers *header,
+ void *arg, const unsigned long end)
+{
+ struct acpi_keyp_config_unit *acpi_cu =
+ (struct acpi_keyp_config_unit *)&header->keyp;
+ int *tvm_usable = arg;
+
+ if (acpi_cu->flags & ACPI_KEYP_F_TVM_USABLE)
+ *tvm_usable = 1;
+
+ return 0;
+}
+
+static bool platform_is_tdxc_enhanced(void)
+{
+ static int tvm_usable = -1;
+ int ret;
+
+ /* only need to parse once */
+ if (tvm_usable != -1)
+ return !!tvm_usable;
+
+ tvm_usable = 0;
+ ret = acpi_table_parse_keyp(ACPI_KEYP_TYPE_CONFIG_UNIT,
+ keyp_config_unit_tvm_usable, &tvm_usable);
+ if (ret < 0)
+ tvm_usable = 0;
+
+ return !!tvm_usable;
+}
+
+static unsigned long iommu_max_domain_id(struct intel_iommu *iommu)
+{
+ unsigned long ndoms = cap_ndoms(iommu->cap);
+
+ /*
+ * Intel TDX Connect Architecture Specification, Section 2.2 Trusted DMA
+ *
+ * When IOMMU is enabled to support TDX Connect, the IOMMU restricts
+ * the VMM’s DID setting, reserving the MSB bit for the TDX module. The
+ * TDX module always sets this reserved bit on the trusted DMA table.
+ */
+ if (ecap_tdxc(iommu->ecap) && platform_is_tdxc_enhanced()) {
+ pr_info_once("Most Significant Bit of domain ID reserved.\n");
+ return ndoms >> 1;
+ }
+
+ return ndoms;
+}
+
static int alloc_iommu(struct dmar_drhd_unit *drhd)
{
struct intel_iommu *iommu;
@@ -1099,7 +1149,7 @@ static int alloc_iommu(struct dmar_drhd_unit *drhd)
spin_lock_init(&iommu->lock);
ida_init(&iommu->domain_ida);
mutex_init(&iommu->did_lock);
- iommu->max_domain_id = cap_ndoms(iommu->cap);
+ iommu->max_domain_id = iommu_max_domain_id(iommu);
ver = readl(iommu->reg + DMAR_VER_REG);
pr_info("%s: reg_base_addr %llx ver %d:%d cap %llx ecap %llx\n",
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* Re: [PATCH v2 19/31] iommu/vt-d: Reserve the MSB domain ID bit for the TDX module
2026-03-27 16:01 ` [PATCH v2 19/31] iommu/vt-d: Reserve the MSB domain ID bit for the TDX module Xu Yilun
@ 2026-03-28 16:57 ` kernel test robot
2026-03-28 19:58 ` kernel test robot
1 sibling, 0 replies; 38+ messages in thread
From: kernel test robot @ 2026-03-28 16:57 UTC (permalink / raw)
To: Xu Yilun, linux-coco, linux-pci, dan.j.williams, x86
Cc: oe-kbuild-all, chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Hi Xu,
kernel test robot noticed the following build warnings:
[auto build test WARNING on 11439c4635edd669ae435eec308f4ab8a0804808]
url: https://github.com/intel-lab-lkp/linux/commits/Xu-Yilun/x86-tdx-Move-all-TDX-error-defines-into-asm-shared-tdx_errno-h/20260328-151524
base: 11439c4635edd669ae435eec308f4ab8a0804808
patch link: https://lore.kernel.org/r/20260327160132.2946114-20-yilun.xu%40linux.intel.com
patch subject: [PATCH v2 19/31] iommu/vt-d: Reserve the MSB domain ID bit for the TDX module
config: i386-randconfig-141-20260328 (https://download.01.org/0day-ci/archive/20260329/202603290006.za7iiDgF-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
smatch: v0.5.0-9004-gb810ac53
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260329/202603290006.za7iiDgF-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603290006.za7iiDgF-lkp@intel.com/
All warnings (new ones prefixed by >>, old ones prefixed by <<):
>> WARNING: modpost: vmlinux: section mismatch in reference: iommu_max_domain_id+0x55 (section: .text.iommu_max_domain_id) -> acpi_table_parse_keyp (section: .init.text)
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 38+ messages in thread
* Re: [PATCH v2 19/31] iommu/vt-d: Reserve the MSB domain ID bit for the TDX module
2026-03-27 16:01 ` [PATCH v2 19/31] iommu/vt-d: Reserve the MSB domain ID bit for the TDX module Xu Yilun
2026-03-28 16:57 ` kernel test robot
@ 2026-03-28 19:58 ` kernel test robot
1 sibling, 0 replies; 38+ messages in thread
From: kernel test robot @ 2026-03-28 19:58 UTC (permalink / raw)
To: Xu Yilun, linux-coco, linux-pci, dan.j.williams, x86
Cc: oe-kbuild-all, chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Hi Xu,
kernel test robot noticed the following build warnings:
[auto build test WARNING on 11439c4635edd669ae435eec308f4ab8a0804808]
url: https://github.com/intel-lab-lkp/linux/commits/Xu-Yilun/x86-tdx-Move-all-TDX-error-defines-into-asm-shared-tdx_errno-h/20260328-151524
base: 11439c4635edd669ae435eec308f4ab8a0804808
patch link: https://lore.kernel.org/r/20260327160132.2946114-20-yilun.xu%40linux.intel.com
patch subject: [PATCH v2 19/31] iommu/vt-d: Reserve the MSB domain ID bit for the TDX module
config: x86_64-defconfig (https://download.01.org/0day-ci/archive/20260329/202603290317.BVIn0aoy-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260329/202603290317.BVIn0aoy-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603290317.BVIn0aoy-lkp@intel.com/
All warnings (new ones prefixed by >>, old ones prefixed by <<):
>> WARNING: modpost: vmlinux: section mismatch in reference: alloc_iommu.cold+0x49 (section: .text.unlikely) -> acpi_table_parse_keyp (section: .init.text)
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 38+ messages in thread
* [PATCH v2 20/31] x86/virt/tdx: Add a helper to loop on TDX_INTERRUPTED_RESUMABLE
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (18 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 19/31] iommu/vt-d: Reserve the MSB domain ID bit for the TDX module Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 21/31] x86/virt/tdx: Add SEAMCALL wrappers for trusted IOMMU setup and clear Xu Yilun
` (10 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Add a helper to handle SEAMCALL return code TDX_INTERRUPTED_RESUMABLE.
SEAMCALL returns TDX_INTERRUPTED_RESUMABLE to avoid stalling host for
long time. After host has handled the interrupt, it calls the
interrupted SEAMCALL again and TDX Module continues to execute. TDX
Module made progress in this case and would eventually finish. An
infinite loop in host should be safe.
The helper is for SEAMCALL wrappers which output information by using
seamcall_ret() or seamcall_saved_ret(). The 2 functions overwrite input
arguments by outputs but much SEAMCALLs expect the same inputs to
resume.
The helper is not for special cases where the SEAMCALL expects modified
inputs to resume. The helper is also not for SEAMCALLs with no output,
do {...} while (r == TDX_INTERRUPTED_RESUMABLE) just works.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/virt/vmx/tdx/tdx.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index cd0948794b6c..294f36048c03 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -2084,6 +2084,29 @@ static inline u64 tdx_tdr_pa(struct tdx_td *td)
return page_to_phys(td->tdr_page);
}
+static u64 __maybe_unused __seamcall_ir_resched(sc_func_t sc_func, u64 fn,
+ struct tdx_module_args *args)
+{
+ struct tdx_module_args _args;
+ u64 r;
+
+ while (1) {
+ _args = *(args);
+ r = sc_retry(sc_func, fn, &_args);
+ if (r != TDX_INTERRUPTED_RESUMABLE)
+ break;
+
+ cond_resched();
+ }
+
+ *args = _args;
+
+ return r;
+}
+
+#define seamcall_ret_ir_resched(fn, args) \
+ __seamcall_ir_resched(__seamcall_ret, fn, args)
+
noinstr u64 tdh_vp_enter(struct tdx_vp *td, struct tdx_module_args *args)
{
args->rcx = td->tdvpr_pa;
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 21/31] x86/virt/tdx: Add SEAMCALL wrappers for trusted IOMMU setup and clear
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (19 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 20/31] x86/virt/tdx: Add a helper to loop on TDX_INTERRUPTED_RESUMABLE Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 22/31] iommu/vt-d: Export a helper to do function for each dmar_drhd_unit Xu Yilun
` (9 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
From: Zhenzhong Duan <zhenzhong.duan@intel.com>
Add SEAMCALLs to setup/clear trusted IOMMU for TDX Connect.
Enable TEE I/O support for a target device requires to setup trusted IOMMU
for the related IOMMU device first, even only for enabling physical secure
links like SPDM/IDE.
TDH.IOMMU.SETUP takes the register base address (VTBAR) to position an
IOMMU device, and outputs an IOMMU_ID as the trusted IOMMU identifier.
TDH.IOMMU.CLEAR takes the IOMMU_ID to reverse the setup.
More information see Intel TDX Connect ABI Specification [1]
Section 3.2 TDX Connect Host-Side (SEAMCALL) Interface Functions.
[1]: https://cdrdv2.intel.com/v1/dl/getContent/858625
Co-developed-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
arch/x86/include/asm/tdx.h | 2 ++
arch/x86/virt/vmx/tdx/tdx.h | 2 ++
arch/x86/virt/vmx/tdx/tdx.c | 32 ++++++++++++++++++++++++++++++--
3 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index d7605235aa9b..a59e0e43e465 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -245,6 +245,8 @@ u64 tdh_mem_page_remove(struct tdx_td *td, u64 gpa, u64 level, u64 *ext_err1, u6
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, struct page *page);
+u64 tdh_iommu_setup(u64 vtbar, struct tdx_page_array *iommu_mt, u64 *iommu_id);
+u64 tdh_iommu_clear(u64 iommu_id, struct tdx_page_array *iommu_mt);
#else
static inline void tdx_init(void) { }
static inline int tdx_cpu_enable(void) { return -ENODEV; }
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index a26fe94c07ff..b25c418f6e61 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -62,6 +62,8 @@
#define TDH_SYS_CONFIG SEAMCALL_LEAF_VER(TDH_SYS_CONFIG_V0, 1)
#define TDH_EXT_INIT 60
#define TDH_EXT_MEM_ADD 61
+#define TDH_IOMMU_SETUP 128
+#define TDH_IOMMU_CLEAR 129
/* TDX page types */
#define PT_NDA 0x0
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 294f36048c03..790713881f1f 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -2084,8 +2084,8 @@ static inline u64 tdx_tdr_pa(struct tdx_td *td)
return page_to_phys(td->tdr_page);
}
-static u64 __maybe_unused __seamcall_ir_resched(sc_func_t sc_func, u64 fn,
- struct tdx_module_args *args)
+static u64 __seamcall_ir_resched(sc_func_t sc_func, u64 fn,
+ struct tdx_module_args *args)
{
struct tdx_module_args _args;
u64 r;
@@ -2478,3 +2478,31 @@ void tdx_cpu_flush_cache_for_kexec(void)
}
EXPORT_SYMBOL_FOR_KVM(tdx_cpu_flush_cache_for_kexec);
#endif
+
+u64 tdh_iommu_setup(u64 vtbar, struct tdx_page_array *iommu_mt, u64 *iommu_id)
+{
+ struct tdx_module_args args = {
+ .rcx = vtbar,
+ .rdx = virt_to_phys(iommu_mt->root),
+ };
+ u64 r;
+
+ tdx_clflush_page_array(iommu_mt);
+
+ r = seamcall_ret_ir_resched(TDH_IOMMU_SETUP, &args);
+
+ *iommu_id = args.rcx;
+ return r;
+}
+EXPORT_SYMBOL_FOR_MODULES(tdh_iommu_setup, "tdx-host");
+
+u64 tdh_iommu_clear(u64 iommu_id, struct tdx_page_array *iommu_mt)
+{
+ struct tdx_module_args args = {
+ .rcx = iommu_id,
+ .rdx = virt_to_phys(iommu_mt->root),
+ };
+
+ return seamcall_ret_ir_resched(TDH_IOMMU_CLEAR, &args);
+}
+EXPORT_SYMBOL_FOR_MODULES(tdh_iommu_clear, "tdx-host");
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 22/31] iommu/vt-d: Export a helper to do function for each dmar_drhd_unit
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (20 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 21/31] x86/virt/tdx: Add SEAMCALL wrappers for trusted IOMMU setup and clear Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 23/31] coco/tdx-host: Setup all trusted IOMMUs on TDX Connect init Xu Yilun
` (8 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Enable the tdx-host module to get VTBAR address for every IOMMU device.
The VTBAR address is for TDX Module to identify the IOMMU device and
setup its trusted configuraion.
Suggested-by: Lu Baolu <baolu.lu@linux.intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
include/linux/dmar.h | 2 ++
drivers/iommu/intel/dmar.c | 16 ++++++++++++++++
2 files changed, 18 insertions(+)
diff --git a/include/linux/dmar.h b/include/linux/dmar.h
index 692b2b445761..cd8d9f440975 100644
--- a/include/linux/dmar.h
+++ b/include/linux/dmar.h
@@ -86,6 +86,8 @@ extern struct list_head dmar_drhd_units;
dmar_rcu_check()) \
if (i=drhd->iommu, 0) {} else
+int do_for_each_drhd_unit(int (*fn)(struct dmar_drhd_unit *));
+
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 4f9571eee1d4..eea9ba691f99 100644
--- a/drivers/iommu/intel/dmar.c
+++ b/drivers/iommu/intel/dmar.c
@@ -2452,3 +2452,19 @@ bool dmar_platform_optin(void)
return ret;
}
EXPORT_SYMBOL_GPL(dmar_platform_optin);
+
+int do_for_each_drhd_unit(int (*fn)(struct dmar_drhd_unit *))
+{
+ struct dmar_drhd_unit *drhd;
+ int ret;
+
+ guard(rwsem_read)(&dmar_global_lock);
+
+ for_each_drhd_unit(drhd) {
+ ret = fn(drhd);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(do_for_each_drhd_unit);
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 23/31] coco/tdx-host: Setup all trusted IOMMUs on TDX Connect init
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (21 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 22/31] iommu/vt-d: Export a helper to do function for each dmar_drhd_unit Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 24/31] coco/tdx-host: Add a helper to exchange SPDM messages through DOE Xu Yilun
` (7 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Setup all trusted IOMMUs on TDX Connect initialization and clear all on
TDX Connect removal.
Trusted IOMMU setup is the pre-condition for all following TDX Connect
operations such as SPDM/IDE setup. It is more of a platform
configuration than a standalone IOMMU configuration, so put the
implementation in tdx-host driver.
There is no dedicated way to enumerate which IOMMU devices support
trusted operations. The host has to call TDH.IOMMU.SETUP on all IOMMU
devices and tell their trusted capability by the return value.
Suggested-by: Lu Baolu <baolu.lu@linux.intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
drivers/virt/coco/tdx-host/Kconfig | 1 +
drivers/virt/coco/tdx-host/tdx-host.c | 85 +++++++++++++++++++++++++++
2 files changed, 86 insertions(+)
diff --git a/drivers/virt/coco/tdx-host/Kconfig b/drivers/virt/coco/tdx-host/Kconfig
index 32add81b7d56..24e872f8953e 100644
--- a/drivers/virt/coco/tdx-host/Kconfig
+++ b/drivers/virt/coco/tdx-host/Kconfig
@@ -13,3 +13,4 @@ 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/tdx-host.c b/drivers/virt/coco/tdx-host/tdx-host.c
index 5ea35a514865..98ed93ac0153 100644
--- a/drivers/virt/coco/tdx-host/tdx-host.c
+++ b/drivers/virt/coco/tdx-host/tdx-host.c
@@ -6,6 +6,7 @@
*/
#include <linux/device/faux.h>
+#include <linux/dmar.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/pci.h>
@@ -119,6 +120,82 @@ static void unregister_link_tsm(void *link)
tsm_unregister(link);
}
+static DEFINE_XARRAY(tlink_iommu_xa);
+
+static void tdx_iommu_clear(u64 iommu_id, struct tdx_page_array *iommu_mt)
+{
+ u64 r;
+
+ r = tdh_iommu_clear(iommu_id, iommu_mt);
+ if (r) {
+ pr_err("fail to clear tdx iommu 0x%llx\n", r);
+ goto leak;
+ }
+
+ if (tdx_page_array_ctrl_release(iommu_mt, iommu_mt->nr_pages,
+ virt_to_phys(iommu_mt->root))) {
+ pr_err("fail to release iommu_mt pages\n");
+ goto leak;
+ }
+
+ return;
+
+leak:
+ tdx_page_array_ctrl_leak(iommu_mt);
+}
+
+static int tdx_iommu_enable_one(struct dmar_drhd_unit *drhd)
+{
+ unsigned int nr_pages = tdx_sysinfo->connect.iommu_mt_page_count;
+ u64 r, iommu_id;
+ int ret;
+
+ struct tdx_page_array *iommu_mt __free(tdx_page_array_free) =
+ tdx_page_array_create_iommu_mt(1, nr_pages);
+ if (!iommu_mt)
+ return -ENOMEM;
+
+ r = tdh_iommu_setup(drhd->reg_base_addr, iommu_mt, &iommu_id);
+ /* This drhd doesn't support tdx mode, skip. */
+ if ((r & TDX_SEAMCALL_STATUS_MASK) == TDX_OPERAND_INVALID)
+ return 0;
+
+ if (r) {
+ pr_err("fail to enable tdx mode for DRHD[0x%llx]\n",
+ drhd->reg_base_addr);
+ return -EFAULT;
+ }
+
+ ret = xa_insert(&tlink_iommu_xa, (unsigned long)iommu_id,
+ no_free_ptr(iommu_mt), GFP_KERNEL);
+ if (ret) {
+ tdx_iommu_clear(iommu_id, iommu_mt);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void tdx_iommu_disable_all(void *data)
+{
+ struct tdx_page_array *iommu_mt;
+ unsigned long iommu_id;
+
+ xa_for_each(&tlink_iommu_xa, iommu_id, iommu_mt)
+ tdx_iommu_clear(iommu_id, iommu_mt);
+}
+
+static int tdx_iommu_enable_all(void)
+{
+ int ret;
+
+ ret = do_for_each_drhd_unit(tdx_iommu_enable_one);
+ if (ret)
+ tdx_iommu_disable_all(NULL);
+
+ return ret;
+}
+
static int __maybe_unused tdx_connect_init(struct device *dev)
{
struct tsm_dev *link;
@@ -130,6 +207,14 @@ static int __maybe_unused tdx_connect_init(struct device *dev)
if (!(tdx_sysinfo->features.tdx_features0 & TDX_FEATURES0_TDXCONNECT))
return 0;
+ ret = tdx_iommu_enable_all();
+ if (ret)
+ return dev_err_probe(dev, ret, "Enable tdx iommu failed\n");
+
+ ret = devm_add_action_or_reset(dev, tdx_iommu_disable_all, NULL);
+ if (ret)
+ return ret;
+
link = tsm_register(dev, &tdx_tsm_link_ops);
if (IS_ERR(link))
return dev_err_probe(dev, PTR_ERR(link),
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 24/31] coco/tdx-host: Add a helper to exchange SPDM messages through DOE
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (22 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 23/31] coco/tdx-host: Setup all trusted IOMMUs on TDX Connect init Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 25/31] x86/virt/tdx: Add SEAMCALL wrappers for SPDM management Xu Yilun
` (6 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
From: Zhenzhong Duan <zhenzhong.duan@intel.com>
TDX host uses this function to exchange TDX Module encrypted data with
devices via SPDM. It is unfortunate that TDX passes raw DOE frames with
headers included and the PCI DOE core wants payloads separated from
headers.
This conversion code is about the same amount of work as teaching the PCI
DOE driver to support raw frames. Unless and until another raw frame use
case shows up, just do this conversion in the TDX TSM driver.
Co-developed-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
---
drivers/virt/coco/tdx-host/tdx-host.c | 61 +++++++++++++++++++++++++++
1 file changed, 61 insertions(+)
diff --git a/drivers/virt/coco/tdx-host/tdx-host.c b/drivers/virt/coco/tdx-host/tdx-host.c
index 98ed93ac0153..06f3d194e0a8 100644
--- a/drivers/virt/coco/tdx-host/tdx-host.c
+++ b/drivers/virt/coco/tdx-host/tdx-host.c
@@ -5,11 +5,13 @@
* Copyright (C) 2025 Intel Corporation
*/
+#include <linux/bitfield.h>
#include <linux/device/faux.h>
#include <linux/dmar.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/pci.h>
+#include <linux/pci-doe.h>
#include <linux/pci-tsm.h>
#include <linux/tsm.h>
@@ -39,6 +41,65 @@ static struct tdx_tsm_link *to_tdx_tsm_link(struct pci_tsm *tsm)
return container_of(tsm, struct tdx_tsm_link, pci.base_tsm);
}
+#define PCI_DOE_DATA_OBJECT_HEADER_1_OFFSET 0
+#define PCI_DOE_DATA_OBJECT_HEADER_2_OFFSET 4
+#define PCI_DOE_DATA_OBJECT_HEADER_SIZE 8
+#define PCI_DOE_DATA_OBJECT_PAYLOAD_OFFSET PCI_DOE_DATA_OBJECT_HEADER_SIZE
+
+#define PCI_DOE_PROTOCOL_SECURE_SPDM 2
+
+static int __maybe_unused tdx_spdm_msg_exchange(struct tdx_tsm_link *tlink,
+ void *request, size_t request_sz,
+ void *response, size_t response_sz)
+{
+ struct pci_dev *pdev = tlink->pci.base_tsm.pdev;
+ void *req_pl_addr, *resp_pl_addr;
+ size_t req_pl_sz, resp_pl_sz;
+ u32 data, len;
+ u16 vendor;
+ u8 type;
+ int ret;
+
+ /*
+ * pci_doe() accept DOE PAYLOAD only but request carries DOE HEADER so
+ * shift the buffers, skip DOE HEADER in request buffer, and fill DOE
+ * HEADER in response buffer manually.
+ */
+
+ data = le32_to_cpu(*(__le32 *)(request + PCI_DOE_DATA_OBJECT_HEADER_1_OFFSET));
+ vendor = FIELD_GET(PCI_DOE_DATA_OBJECT_HEADER_1_VID, data);
+ type = FIELD_GET(PCI_DOE_DATA_OBJECT_HEADER_1_TYPE, data);
+
+ data = le32_to_cpu(*(__le32 *)(request + PCI_DOE_DATA_OBJECT_HEADER_2_OFFSET));
+ len = FIELD_GET(PCI_DOE_DATA_OBJECT_HEADER_2_LENGTH, data);
+
+ req_pl_sz = len * sizeof(__le32) - PCI_DOE_DATA_OBJECT_HEADER_SIZE;
+ resp_pl_sz = response_sz - PCI_DOE_DATA_OBJECT_HEADER_SIZE;
+ req_pl_addr = request + PCI_DOE_DATA_OBJECT_HEADER_SIZE;
+ resp_pl_addr = response + PCI_DOE_DATA_OBJECT_HEADER_SIZE;
+
+ ret = pci_tsm_doe_transfer(pdev, type, req_pl_addr, req_pl_sz,
+ resp_pl_addr, resp_pl_sz);
+ if (ret < 0) {
+ pci_err(pdev, "spdm msg exchange fail %d\n", ret);
+ return ret;
+ }
+
+ data = FIELD_PREP(PCI_DOE_DATA_OBJECT_HEADER_1_VID, vendor) |
+ FIELD_PREP(PCI_DOE_DATA_OBJECT_HEADER_1_TYPE, type);
+ *(__le32 *)(response + PCI_DOE_DATA_OBJECT_HEADER_1_OFFSET) = cpu_to_le32(data);
+
+ len = (ret + PCI_DOE_DATA_OBJECT_HEADER_SIZE) / sizeof(__le32);
+ data = FIELD_PREP(PCI_DOE_DATA_OBJECT_HEADER_2_LENGTH, len);
+ *(__le32 *)(response + PCI_DOE_DATA_OBJECT_HEADER_2_OFFSET) = cpu_to_le32(data);
+
+ ret += PCI_DOE_DATA_OBJECT_HEADER_SIZE;
+
+ pci_dbg(pdev, "%s complete: vendor 0x%x type 0x%x rsp_sz %d\n",
+ __func__, vendor, type, ret);
+ return ret;
+}
+
static int tdx_tsm_link_connect(struct pci_dev *pdev)
{
return -ENXIO;
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 25/31] x86/virt/tdx: Add SEAMCALL wrappers for SPDM management
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (23 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 24/31] coco/tdx-host: Add a helper to exchange SPDM messages through DOE Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 26/31] mm: Add __free() support for __free_page() Xu Yilun
` (5 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
From: Zhenzhong Duan <zhenzhong.duan@intel.com>
Add several SEAMCALL wrappers for SPDM management. TDX Module requires
HPA_ARRAY_T structure as input/output parameters for these SEAMCALLs.
So use tdx_page_array for these wrappers.
- TDH.SPDM.CREATE creates SPDM session metadata buffers for TDX Module.
- TDH.SPDM.DELETE destroys SPDM session metadata and returns these
buffers to host, after checking no reference attached to the metadata.
- TDH.SPDM.CONNECT establishes a new SPDM session with the device.
- TDH.SPDM.DISCONNECT tears down the SPDM session with the device.
- TDH.SPDM.MNG supports three SPDM runtime operations: HEARTBEAT,
KEY_UPDATE and DEV_INFO_RECOLLECTION.
Co-developed-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
arch/x86/include/asm/tdx.h | 13 ++++
arch/x86/virt/vmx/tdx/tdx.h | 5 ++
arch/x86/virt/vmx/tdx/tdx.c | 114 +++++++++++++++++++++++++++++++++++-
3 files changed, 130 insertions(+), 2 deletions(-)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index a59e0e43e465..8abdad084972 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -247,6 +247,19 @@ u64 tdh_phymem_page_wbinvd_tdr(struct tdx_td *td);
u64 tdh_phymem_page_wbinvd_hkid(u64 hkid, struct page *page);
u64 tdh_iommu_setup(u64 vtbar, struct tdx_page_array *iommu_mt, u64 *iommu_id);
u64 tdh_iommu_clear(u64 iommu_id, struct tdx_page_array *iommu_mt);
+u64 tdh_spdm_create(u64 func_id, struct tdx_page_array *spdm_mt, u64 *spdm_id);
+u64 tdh_spdm_delete(u64 spdm_id, struct tdx_page_array *spdm_mt,
+ unsigned int *nr_released, u64 *released_hpa);
+u64 tdh_exec_spdm_connect(u64 spdm_id, struct page *spdm_conf,
+ struct page *spdm_rsp, struct page *spdm_req,
+ struct tdx_page_array *spdm_out,
+ u64 *spdm_req_or_out_len);
+u64 tdh_exec_spdm_disconnect(u64 spdm_id, struct page *spdm_rsp,
+ struct page *spdm_req, u64 *spdm_req_len);
+u64 tdh_exec_spdm_mng(u64 spdm_id, u64 spdm_op, struct page *spdm_param,
+ struct page *spdm_rsp, struct page *spdm_req,
+ struct tdx_page_array *spdm_out,
+ u64 *spdm_req_or_out_len);
#else
static inline void tdx_init(void) { }
static inline int tdx_cpu_enable(void) { return -ENODEV; }
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index b25c418f6e61..4784db2d1d92 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -64,6 +64,11 @@
#define TDH_EXT_MEM_ADD 61
#define TDH_IOMMU_SETUP 128
#define TDH_IOMMU_CLEAR 129
+#define TDH_SPDM_CREATE 130
+#define TDH_SPDM_DELETE 131
+#define TDH_SPDM_CONNECT 142
+#define TDH_SPDM_DISCONNECT 143
+#define TDH_SPDM_MNG 144
/* TDX page types */
#define PT_NDA 0x0
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 790713881f1f..02882c2ad177 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -654,7 +654,7 @@ static u64 hpa_list_info_assign_raw(struct tdx_page_array *array)
#define HPA_ARRAY_T_PFN GENMASK_U64(51, 12)
#define HPA_ARRAY_T_SIZE GENMASK_U64(63, 55)
-static u64 __maybe_unused hpa_array_t_assign_raw(struct tdx_page_array *array)
+static u64 hpa_array_t_assign_raw(struct tdx_page_array *array)
{
unsigned long pfn;
@@ -667,7 +667,7 @@ static u64 __maybe_unused hpa_array_t_assign_raw(struct tdx_page_array *array)
FIELD_PREP(HPA_ARRAY_T_SIZE, array->nents - 1);
}
-static u64 __maybe_unused hpa_array_t_release_raw(struct tdx_page_array *array)
+static u64 hpa_array_t_release_raw(struct tdx_page_array *array)
{
if (array->nents == 1)
return 0;
@@ -2107,6 +2107,15 @@ static u64 __seamcall_ir_resched(sc_func_t sc_func, u64 fn,
#define seamcall_ret_ir_resched(fn, args) \
__seamcall_ir_resched(__seamcall_ret, fn, args)
+/*
+ * seamcall_ret_ir_exec() aliases seamcall_ret_ir_resched() for
+ * documentation purposes. It documents the TDX Module extension
+ * seamcalls that are long running / hard-irq preemptible flows that
+ * generate events. The calls using seamcall_ret_ir_resched() are long
+ * running flows, that periodically yield.
+ */
+#define seamcall_ret_ir_exec seamcall_ret_ir_resched
+
noinstr u64 tdh_vp_enter(struct tdx_vp *td, struct tdx_module_args *args)
{
args->rcx = td->tdvpr_pa;
@@ -2506,3 +2515,104 @@ u64 tdh_iommu_clear(u64 iommu_id, struct tdx_page_array *iommu_mt)
return seamcall_ret_ir_resched(TDH_IOMMU_CLEAR, &args);
}
EXPORT_SYMBOL_FOR_MODULES(tdh_iommu_clear, "tdx-host");
+
+u64 tdh_spdm_create(u64 func_id, struct tdx_page_array *spdm_mt, u64 *spdm_id)
+{
+ struct tdx_module_args args = {
+ .rcx = func_id,
+ .rdx = hpa_array_t_assign_raw(spdm_mt)
+ };
+ u64 r;
+
+ tdx_clflush_page_array(spdm_mt);
+
+ r = seamcall_ret(TDH_SPDM_CREATE, &args);
+
+ *spdm_id = args.rcx;
+
+ return r;
+}
+EXPORT_SYMBOL_FOR_MODULES(tdh_spdm_create, "tdx-host");
+
+u64 tdh_spdm_delete(u64 spdm_id, struct tdx_page_array *spdm_mt,
+ unsigned int *nr_released, u64 *released_hpa)
+{
+ struct tdx_module_args args = {
+ .rcx = spdm_id,
+ .rdx = hpa_array_t_release_raw(spdm_mt),
+ };
+ u64 r;
+
+ r = seamcall_ret(TDH_SPDM_DELETE, &args);
+ if (r != TDX_SUCCESS)
+ return r;
+
+ *nr_released = FIELD_GET(HPA_ARRAY_T_SIZE, args.rcx) + 1;
+ *released_hpa = FIELD_GET(HPA_ARRAY_T_PFN, args.rcx) << PAGE_SHIFT;
+
+ return r;
+}
+EXPORT_SYMBOL_FOR_MODULES(tdh_spdm_delete, "tdx-host");
+
+u64 tdh_exec_spdm_connect(u64 spdm_id, struct page *spdm_conf,
+ struct page *spdm_rsp, struct page *spdm_req,
+ struct tdx_page_array *spdm_out,
+ u64 *spdm_req_or_out_len)
+{
+ struct tdx_module_args args = {
+ .rcx = spdm_id,
+ .rdx = page_to_phys(spdm_conf),
+ .r8 = page_to_phys(spdm_rsp),
+ .r9 = page_to_phys(spdm_req),
+ .r10 = hpa_array_t_assign_raw(spdm_out),
+ };
+ u64 r;
+
+ r = seamcall_ret_ir_exec(TDH_SPDM_CONNECT, &args);
+
+ *spdm_req_or_out_len = args.rcx;
+
+ return r;
+}
+EXPORT_SYMBOL_FOR_MODULES(tdh_exec_spdm_connect, "tdx-host");
+
+u64 tdh_exec_spdm_disconnect(u64 spdm_id, struct page *spdm_rsp,
+ struct page *spdm_req, u64 *spdm_req_len)
+{
+ struct tdx_module_args args = {
+ .rcx = spdm_id,
+ .rdx = page_to_phys(spdm_rsp),
+ .r8 = page_to_phys(spdm_req),
+ };
+ u64 r;
+
+ r = seamcall_ret_ir_exec(TDH_SPDM_DISCONNECT, &args);
+
+ *spdm_req_len = args.rcx;
+
+ return r;
+}
+EXPORT_SYMBOL_FOR_MODULES(tdh_exec_spdm_disconnect, "tdx-host");
+
+u64 tdh_exec_spdm_mng(u64 spdm_id, u64 spdm_op, struct page *spdm_param,
+ struct page *spdm_rsp, struct page *spdm_req,
+ struct tdx_page_array *spdm_out,
+ u64 *spdm_req_or_out_len)
+{
+ struct tdx_module_args args = {
+ .rcx = spdm_id,
+ .rdx = spdm_op,
+ .r8 = spdm_param ? page_to_phys(spdm_param) : -1,
+ .r9 = page_to_phys(spdm_rsp),
+ .r10 = page_to_phys(spdm_req),
+ .r11 = spdm_out ? hpa_array_t_assign_raw(spdm_out) : -1,
+ };
+ u64 r;
+
+ r = seamcall_ret_ir_exec(TDH_SPDM_MNG, &args);
+
+ *spdm_req_or_out_len = args.rcx;
+
+ return r;
+}
+EXPORT_SYMBOL_FOR_MODULES(tdh_exec_spdm_mng, "tdx-host");
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 26/31] mm: Add __free() support for __free_page()
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (24 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 25/31] x86/virt/tdx: Add SEAMCALL wrappers for SPDM management Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 27/31] coco/tdx-host: Implement SPDM session setup Xu Yilun
` (4 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Allow for the declaration of struct page * variables that trigger
__free_page() when they go out of scope.
A example usage would be in the following patch:
static struct pci_tsm *tdx_tsm_link_pf0_probe(...)
{
...
struct page *in_msg_page __free(__free_page) =
alloc_page(GFP_KERNEL | __GFP_ZERO);
if (!in_msg_page)
return NULL;
struct page *out_msg_page __free(__free_page) =
alloc_page(GFP_KERNEL | __GFP_ZERO);
if (!out_msg_page)
return NULL;
...
tlink->in_msg = no_free_ptr(in_msg_page);
tlink->out_msg = no_free_ptr(out_msg_page);
...
}
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
---
include/linux/gfp.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/include/linux/gfp.h b/include/linux/gfp.h
index 51ef13ed756e..d37e5564234e 100644
--- a/include/linux/gfp.h
+++ b/include/linux/gfp.h
@@ -391,6 +391,7 @@ extern void free_pages_nolock(struct page *page, unsigned int order);
extern void free_pages(unsigned long addr, unsigned int order);
#define __free_page(page) __free_pages((page), 0)
+DEFINE_FREE(__free_page, struct page *, if (_T) __free_page(_T))
#define free_page(addr) free_pages((addr), 0)
void page_alloc_init_cpuhp(void);
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 27/31] coco/tdx-host: Implement SPDM session setup
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (25 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 26/31] mm: Add __free() support for __free_page() Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 28/31] coco/tdx-host: Parse ACPI KEYP table to init IDE for PCI host bridges Xu Yilun
` (3 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
From: Zhenzhong Duan <zhenzhong.duan@intel.com>
Implementation for a most straightforward SPDM session setup, using all
default session options. Retrieve device info data from TDX Module which
contains the SPDM negotiation results.
TDH.SPDM.CONNECT/DISCONNECT are TDX Module Extension introduced
SEAMCALLs which can run for longer periods and interruptible. But there
is resource constraints that limit how many SEAMCALLs of this kind can
run simultaneously. The current situation is One SEAMCALL at a time.
Otherwise TDX_OPERAND_BUSY is returned. To avoid "broken indefinite"
retry, a tdx_ext_lock is used to guard these SEAMCALLs.
Co-developed-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
---
arch/x86/include/asm/shared/tdx_errno.h | 2 +
drivers/virt/coco/tdx-host/tdx-host.c | 301 +++++++++++++++++++++++-
2 files changed, 299 insertions(+), 4 deletions(-)
diff --git a/arch/x86/include/asm/shared/tdx_errno.h b/arch/x86/include/asm/shared/tdx_errno.h
index 8bf6765cf082..7db04fe30378 100644
--- a/arch/x86/include/asm/shared/tdx_errno.h
+++ b/arch/x86/include/asm/shared/tdx_errno.h
@@ -29,6 +29,8 @@
#define TDX_EPT_WALK_FAILED 0xC0000B0000000000ULL
#define TDX_EPT_ENTRY_STATE_INCORRECT 0xC0000B0D00000000ULL
#define TDX_METADATA_FIELD_NOT_READABLE 0xC0000C0200000000ULL
+#define TDX_SPDM_SESSION_KEY_REQUIRE_REFRESH 0xC0000F4500000000ULL
+#define TDX_SPDM_REQUEST 0xC0000F5700000000ULL
/*
* SW-defined error codes.
diff --git a/drivers/virt/coco/tdx-host/tdx-host.c b/drivers/virt/coco/tdx-host/tdx-host.c
index 06f3d194e0a8..4d127b7c2591 100644
--- a/drivers/virt/coco/tdx-host/tdx-host.c
+++ b/drivers/virt/coco/tdx-host/tdx-host.c
@@ -14,6 +14,7 @@
#include <linux/pci-doe.h>
#include <linux/pci-tsm.h>
#include <linux/tsm.h>
+#include <linux/vmalloc.h>
#include <asm/cpu_device_id.h>
#include <asm/tdx.h>
@@ -32,8 +33,43 @@ MODULE_DEVICE_TABLE(x86cpu, tdx_host_ids);
*/
static const struct tdx_sys_info *tdx_sysinfo;
+#define TDISP_FUNC_ID GENMASK(15, 0)
+#define TDISP_FUNC_ID_SEGMENT GENMASK(23, 16)
+#define TDISP_FUNC_ID_SEG_VALID BIT(24)
+
+static inline u32 tdisp_func_id(struct pci_dev *pdev)
+{
+ u32 func_id;
+
+ func_id = FIELD_PREP(TDISP_FUNC_ID_SEGMENT, pci_domain_nr(pdev->bus));
+ if (func_id)
+ func_id |= TDISP_FUNC_ID_SEG_VALID;
+ func_id |= FIELD_PREP(TDISP_FUNC_ID,
+ PCI_DEVID(pdev->bus->number, pdev->devfn));
+
+ return func_id;
+}
+
+struct spdm_config_info_t {
+ u32 vmm_spdm_cap;
+#define SPDM_CAP_HBEAT BIT(13)
+#define SPDM_CAP_KEY_UPD BIT(14)
+ u8 spdm_session_policy;
+ u8 certificate_slot_mask;
+ u8 raw_bitstream_requested;
+} __packed;
+
struct tdx_tsm_link {
struct pci_tsm_pf0 pci;
+ u32 func_id;
+ struct page *in_msg;
+ struct page *out_msg;
+
+ u64 spdm_id;
+ struct page *spdm_conf;
+ struct tdx_page_array *spdm_mt;
+ unsigned int dev_info_size;
+ void *dev_info_data;
};
static struct tdx_tsm_link *to_tdx_tsm_link(struct pci_tsm *tsm)
@@ -48,9 +84,9 @@ static struct tdx_tsm_link *to_tdx_tsm_link(struct pci_tsm *tsm)
#define PCI_DOE_PROTOCOL_SECURE_SPDM 2
-static int __maybe_unused tdx_spdm_msg_exchange(struct tdx_tsm_link *tlink,
- void *request, size_t request_sz,
- void *response, size_t response_sz)
+static int tdx_spdm_msg_exchange(struct tdx_tsm_link *tlink,
+ void *request, size_t request_sz,
+ void *response, size_t response_sz)
{
struct pci_dev *pdev = tlink->pci.base_tsm.pdev;
void *req_pl_addr, *resp_pl_addr;
@@ -100,18 +136,246 @@ static int __maybe_unused tdx_spdm_msg_exchange(struct tdx_tsm_link *tlink,
return ret;
}
+static int tdx_spdm_session_keyupdate(struct tdx_tsm_link *tlink);
+
+static int tdx_tsm_link_event_handler(struct tdx_tsm_link *tlink,
+ u64 tdx_ret, u64 out_msg_sz)
+{
+ int ret;
+
+ if (tdx_ret == TDX_SUCCESS)
+ return 0;
+
+ if (tdx_ret == TDX_SPDM_REQUEST) {
+ ret = tdx_spdm_msg_exchange(tlink,
+ page_address(tlink->out_msg),
+ out_msg_sz,
+ page_address(tlink->in_msg),
+ PAGE_SIZE);
+ if (ret < 0)
+ return ret;
+
+ return -EAGAIN;
+ }
+
+ if (tdx_ret == TDX_SPDM_SESSION_KEY_REQUIRE_REFRESH) {
+ /* keyupdate won't trigger this error again, no recursion risk */
+ ret = tdx_spdm_session_keyupdate(tlink);
+ if (ret)
+ return ret;
+
+ return -EAGAIN;
+ }
+
+ return -EFAULT;
+}
+
+/*
+ * TDX Module extension introduced SEAMCALLs work like a request queue.
+ * The caller is responsible for grabbing a queue slot before SEAMCALL,
+ * otherwise will fail with TDX_OPERAND_BUSY. Currently the queue depth is 1.
+ * So a mutex could work for simplicity.
+ */
+static DEFINE_MUTEX(tdx_ext_lock);
+
+enum tdx_spdm_mng_op {
+ TDX_SPDM_MNG_HEARTBEAT = 0,
+ TDX_SPDM_MNG_KEY_UPDATE = 1,
+ TDX_SPDM_MNG_RECOLLECT = 2,
+};
+
+static int tdx_spdm_session_mng(struct tdx_tsm_link *tlink,
+ enum tdx_spdm_mng_op op)
+{
+ u64 r, out_msg_sz;
+ int ret;
+
+ guard(mutex)(&tdx_ext_lock);
+ do {
+ r = tdh_exec_spdm_mng(tlink->spdm_id, op, NULL, tlink->in_msg,
+ tlink->out_msg, NULL, &out_msg_sz);
+ ret = tdx_tsm_link_event_handler(tlink, r, out_msg_sz);
+ } while (ret == -EAGAIN);
+
+ return ret;
+}
+
+static int tdx_spdm_session_keyupdate(struct tdx_tsm_link *tlink)
+{
+ return tdx_spdm_session_mng(tlink, TDX_SPDM_MNG_KEY_UPDATE);
+}
+
+static void *tdx_dup_array_data(struct tdx_page_array *array,
+ unsigned int data_size)
+{
+ unsigned int npages = (data_size + PAGE_SIZE - 1) / PAGE_SIZE;
+ void *data, *dup_data;
+
+ if (npages > array->nr_pages)
+ return NULL;
+
+ data = vm_map_ram(array->pages, npages, -1);
+ if (!data)
+ return NULL;
+
+ dup_data = kmemdup(data, data_size, GFP_KERNEL);
+ vm_unmap_ram(data, npages);
+
+ return dup_data;
+}
+
+static struct tdx_tsm_link *
+tdx_spdm_session_connect(struct tdx_tsm_link *tlink,
+ struct tdx_page_array *dev_info)
+{
+ u64 r, out_msg_sz;
+ int ret;
+
+ guard(mutex)(&tdx_ext_lock);
+ do {
+ r = tdh_exec_spdm_connect(tlink->spdm_id, tlink->spdm_conf,
+ tlink->in_msg, tlink->out_msg,
+ dev_info, &out_msg_sz);
+ ret = tdx_tsm_link_event_handler(tlink, r, out_msg_sz);
+ } while (ret == -EAGAIN);
+
+ if (ret)
+ return ERR_PTR(ret);
+
+ tlink->dev_info_size = out_msg_sz;
+ return tlink;
+}
+
+static void tdx_spdm_session_disconnect(struct tdx_tsm_link *tlink)
+{
+ u64 r, out_msg_sz;
+ int ret;
+
+ guard(mutex)(&tdx_ext_lock);
+ do {
+ r = tdh_exec_spdm_disconnect(tlink->spdm_id, tlink->in_msg,
+ tlink->out_msg, &out_msg_sz);
+ ret = tdx_tsm_link_event_handler(tlink, r, out_msg_sz);
+ } while (ret == -EAGAIN);
+
+ WARN_ON(ret);
+}
+
+DEFINE_FREE(tdx_spdm_session_disconnect, struct tdx_tsm_link *,
+ if (!IS_ERR_OR_NULL(_T)) tdx_spdm_session_disconnect(_T))
+
+static struct tdx_tsm_link *tdx_spdm_create(struct tdx_tsm_link *tlink)
+{
+ unsigned int nr_pages = tdx_sysinfo->connect.spdm_mt_page_count;
+ u64 spdm_id, r;
+
+ struct tdx_page_array *spdm_mt __free(tdx_page_array_free) =
+ tdx_page_array_create(nr_pages);
+ if (!spdm_mt)
+ return ERR_PTR(-ENOMEM);
+
+ r = tdh_spdm_create(tlink->func_id, spdm_mt, &spdm_id);
+ if (r)
+ return ERR_PTR(-EFAULT);
+
+ tlink->spdm_id = spdm_id;
+ tlink->spdm_mt = no_free_ptr(spdm_mt);
+ return tlink;
+}
+
+static void tdx_spdm_delete(struct tdx_tsm_link *tlink)
+{
+ struct pci_dev *pdev = tlink->pci.base_tsm.pdev;
+ unsigned int nr_released;
+ u64 released_hpa, r;
+
+ r = tdh_spdm_delete(tlink->spdm_id, tlink->spdm_mt, &nr_released, &released_hpa);
+ if (r) {
+ pci_err(pdev, "fail to delete spdm 0x%llx\n", r);
+ goto leak;
+ }
+
+ if (tdx_page_array_ctrl_release(tlink->spdm_mt, nr_released, released_hpa)) {
+ pci_err(pdev, "fail to release spdm_mt pages\n");
+ goto leak;
+ }
+
+ return;
+
+leak:
+ tdx_page_array_ctrl_leak(tlink->spdm_mt);
+}
+
+DEFINE_FREE(tdx_spdm_delete, struct tdx_tsm_link *, if (!IS_ERR_OR_NULL(_T)) tdx_spdm_delete(_T))
+
+static struct tdx_tsm_link *tdx_spdm_session_setup(struct tdx_tsm_link *tlink)
+{
+ unsigned int nr_pages = tdx_sysinfo->connect.spdm_max_dev_info_pages;
+
+ struct tdx_tsm_link *tlink_create __free(tdx_spdm_delete) =
+ tdx_spdm_create(tlink);
+ if (IS_ERR(tlink_create))
+ return tlink_create;
+
+ struct tdx_page_array *dev_info __free(tdx_page_array_free) =
+ tdx_page_array_create(nr_pages);
+ if (!dev_info)
+ return ERR_PTR(-ENOMEM);
+
+ struct tdx_tsm_link *tlink_connect __free(tdx_spdm_session_disconnect) =
+ tdx_spdm_session_connect(tlink, dev_info);
+ if (IS_ERR(tlink_connect))
+ return tlink_connect;
+
+ tlink->dev_info_data = tdx_dup_array_data(dev_info,
+ tlink->dev_info_size);
+ if (!tlink->dev_info_data)
+ return ERR_PTR(-ENOMEM);
+
+ retain_and_null_ptr(tlink_create);
+ retain_and_null_ptr(tlink_connect);
+
+ return tlink;
+}
+
+static void tdx_spdm_session_teardown(struct tdx_tsm_link *tlink)
+{
+ kfree(tlink->dev_info_data);
+
+ tdx_spdm_session_disconnect(tlink);
+ tdx_spdm_delete(tlink);
+}
+
+DEFINE_FREE(tdx_spdm_session_teardown, struct tdx_tsm_link *,
+ if (!IS_ERR_OR_NULL(_T)) tdx_spdm_session_teardown(_T))
+
static int tdx_tsm_link_connect(struct pci_dev *pdev)
{
- return -ENXIO;
+ struct tdx_tsm_link *tlink = to_tdx_tsm_link(pdev->tsm);
+
+ struct tdx_tsm_link *tlink_spdm __free(tdx_spdm_session_teardown) =
+ tdx_spdm_session_setup(tlink);
+ if (IS_ERR(tlink_spdm)) {
+ pci_err(pdev, "fail to setup spdm session\n");
+ return PTR_ERR(tlink_spdm);
+ }
+
+ retain_and_null_ptr(tlink_spdm);
+
+ return 0;
}
static void tdx_tsm_link_disconnect(struct pci_dev *pdev)
{
+ struct tdx_tsm_link *tlink = to_tdx_tsm_link(pdev->tsm);
+
+ tdx_spdm_session_teardown(tlink);
}
static struct pci_tsm *tdx_tsm_link_pf0_probe(struct tsm_dev *tsm_dev,
struct pci_dev *pdev)
{
+ struct spdm_config_info_t *spdm_conf;
int rc;
struct tdx_tsm_link *tlink __free(kfree) = kzalloc_obj(*tlink);
@@ -122,6 +386,32 @@ static struct pci_tsm *tdx_tsm_link_pf0_probe(struct tsm_dev *tsm_dev,
if (rc)
return NULL;
+ tlink->func_id = tdisp_func_id(pdev);
+
+ struct page *in_msg_page __free(__free_page) =
+ alloc_page(GFP_KERNEL | __GFP_ZERO);
+ if (!in_msg_page)
+ return NULL;
+
+ struct page *out_msg_page __free(__free_page) =
+ alloc_page(GFP_KERNEL | __GFP_ZERO);
+ if (!out_msg_page)
+ return NULL;
+
+ struct page *spdm_conf_page __free(kfree) =
+ alloc_page(GFP_KERNEL | __GFP_ZERO);
+ if (!spdm_conf_page)
+ return NULL;
+
+ /* use a default configuration, may require user input later */
+ spdm_conf = page_address(spdm_conf_page);
+ spdm_conf->vmm_spdm_cap = SPDM_CAP_KEY_UPD;
+ spdm_conf->certificate_slot_mask = 0xff;
+
+ tlink->in_msg = no_free_ptr(in_msg_page);
+ tlink->out_msg = no_free_ptr(out_msg_page);
+ tlink->spdm_conf = no_free_ptr(spdm_conf_page);
+
return &no_free_ptr(tlink)->pci.base_tsm;
}
@@ -129,6 +419,9 @@ static void tdx_tsm_link_pf0_remove(struct pci_tsm *tsm)
{
struct tdx_tsm_link *tlink = to_tdx_tsm_link(tsm);
+ __free_page(tlink->spdm_conf);
+ __free_page(tlink->out_msg);
+ __free_page(tlink->in_msg);
pci_tsm_pf0_destructor(&tlink->pci);
kfree(tlink);
}
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 28/31] coco/tdx-host: Parse ACPI KEYP table to init IDE for PCI host bridges
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (26 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 27/31] coco/tdx-host: Implement SPDM session setup Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 29/31] x86/virt/tdx: Add SEAMCALL wrappers for IDE stream management Xu Yilun
` (2 subsequent siblings)
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Parse the KEYP Key Configuration Units (KCU), to decide the max IDE
streams supported for each host bridge.
The KEYP table points to a number of KCU structures that each associates
with a list of root ports (RP) via segment, bus, and devfn. Sanity check
the KEYP table, ensure all RPs listed for each KCU are included in one
host bridge. Then extact the max IDE streams supported to
pci_host_bridge via pci_ide_set_nr_streams().
Co-developed-by: Dave Jiang <dave.jiang@intel.com>
Signed-off-by: Dave Jiang <dave.jiang@intel.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
drivers/virt/coco/tdx-host/tdx-host.c | 111 ++++++++++++++++++++++++++
1 file changed, 111 insertions(+)
diff --git a/drivers/virt/coco/tdx-host/tdx-host.c b/drivers/virt/coco/tdx-host/tdx-host.c
index 4d127b7c2591..d5072a68b81a 100644
--- a/drivers/virt/coco/tdx-host/tdx-host.c
+++ b/drivers/virt/coco/tdx-host/tdx-host.c
@@ -5,6 +5,7 @@
* Copyright (C) 2025 Intel Corporation
*/
+#include <linux/acpi.h>
#include <linux/bitfield.h>
#include <linux/device/faux.h>
#include <linux/dmar.h>
@@ -12,6 +13,7 @@
#include <linux/mod_devicetable.h>
#include <linux/pci.h>
#include <linux/pci-doe.h>
+#include <linux/pci-ide.h>
#include <linux/pci-tsm.h>
#include <linux/tsm.h>
#include <linux/vmalloc.h>
@@ -474,6 +476,111 @@ static void unregister_link_tsm(void *link)
tsm_unregister(link);
}
+#define KCU_STR_CAP_NUM_STREAMS GENMASK(8, 0)
+
+/* The bus_end is inclusive */
+struct keyp_hb_info {
+ /* input */
+ u16 segment;
+ u8 bus_start;
+ u8 bus_end;
+ /* output */
+ u8 nr_ide_streams;
+};
+
+static bool keyp_info_match(struct acpi_keyp_rp_info *rp,
+ struct keyp_hb_info *hb)
+{
+ return rp->segment == hb->segment && rp->bus >= hb->bus_start &&
+ rp->bus <= hb->bus_end;
+}
+
+static int keyp_config_unit_handler(union acpi_subtable_headers *header,
+ void *arg, const unsigned long end)
+{
+ struct acpi_keyp_config_unit *acpi_cu =
+ (struct acpi_keyp_config_unit *)&header->keyp;
+ struct keyp_hb_info *hb_info = arg;
+ int rp_size, rp_count, i;
+ void __iomem *addr;
+ bool match = false;
+ u32 cap;
+
+ rp_size = acpi_cu->header.length - sizeof(*acpi_cu);
+ if (rp_size % sizeof(struct acpi_keyp_rp_info))
+ return -EINVAL;
+
+ rp_count = rp_size / sizeof(struct acpi_keyp_rp_info);
+ if (!rp_count || rp_count != acpi_cu->root_port_count)
+ return -EINVAL;
+
+ for (i = 0; i < rp_count; i++) {
+ struct acpi_keyp_rp_info *rp_info = &acpi_cu->rp_info[i];
+
+ if (i == 0) {
+ match = keyp_info_match(rp_info, hb_info);
+ /* The host bridge already matches another KCU */
+ if (match && hb_info->nr_ide_streams)
+ return -EINVAL;
+
+ continue;
+ }
+
+ if (match ^ keyp_info_match(rp_info, hb_info))
+ return -EINVAL;
+ }
+
+ if (!match)
+ return 0;
+
+ addr = ioremap(acpi_cu->register_base_address, sizeof(cap));
+ if (!addr)
+ return -ENOMEM;
+ cap = ioread32(addr);
+ iounmap(addr);
+
+ hb_info->nr_ide_streams = FIELD_GET(KCU_STR_CAP_NUM_STREAMS, cap) + 1;
+
+ return 0;
+}
+
+static u8 keyp_find_nr_ide_stream(u16 segment, u8 bus_start, u8 bus_end)
+{
+ struct keyp_hb_info hb_info = {
+ .segment = segment,
+ .bus_start = bus_start,
+ .bus_end = bus_end,
+ };
+ int rc;
+
+ rc = acpi_table_parse_keyp(ACPI_KEYP_TYPE_CONFIG_UNIT,
+ keyp_config_unit_handler, &hb_info);
+ if (rc < 0)
+ return 0;
+
+ return hb_info.nr_ide_streams;
+}
+
+static void keyp_setup_nr_ide_stream(struct pci_bus *bus)
+{
+ struct pci_host_bridge *hb = pci_find_host_bridge(bus);
+ u8 nr_ide_streams;
+
+ nr_ide_streams = keyp_find_nr_ide_stream(pci_domain_nr(bus),
+ bus->busn_res.start,
+ bus->busn_res.end);
+
+ pci_ide_set_nr_streams(hb, nr_ide_streams);
+}
+
+static void tdx_setup_nr_ide_stream(void)
+{
+ struct pci_bus *bus = NULL;
+
+ while ((bus = pci_find_next_bus(bus)))
+ keyp_setup_nr_ide_stream(bus);
+}
+
static DEFINE_XARRAY(tlink_iommu_xa);
static void tdx_iommu_clear(u64 iommu_id, struct tdx_page_array *iommu_mt)
@@ -569,6 +676,8 @@ static int __maybe_unused tdx_connect_init(struct device *dev)
if (ret)
return ret;
+ tdx_setup_nr_ide_stream();
+
link = tsm_register(dev, &tdx_tsm_link_ops);
if (IS_ERR(link))
return dev_err_probe(dev, PTR_ERR(link),
@@ -612,5 +721,7 @@ static void __exit tdx_host_exit(void)
}
module_exit(tdx_host_exit);
+MODULE_IMPORT_NS("ACPI");
+MODULE_IMPORT_NS("PCI_IDE");
MODULE_DESCRIPTION("TDX Host Services");
MODULE_LICENSE("GPL");
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 29/31] x86/virt/tdx: Add SEAMCALL wrappers for IDE stream management
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (27 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 28/31] coco/tdx-host: Parse ACPI KEYP table to init IDE for PCI host bridges Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 30/31] coco/tdx-host: Implement IDE stream setup/teardown Xu Yilun
2026-03-27 16:01 ` [PATCH v2 31/31] coco/tdx-host: Finally enable SPDM session and IDE Establishment Xu Yilun
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Add several SEAMCALL wrappers for IDE stream management.
- TDH.IDE.STREAM.CREATE creates IDE stream metadata buffers for TDX
Module, and does root port side IDE configuration.
- TDH.IDE.STREAM.BLOCK clears the root port side IDE configuration.
- TDH.IDE.STREAM.DELETE releases the IDE stream metadata buffers.
- TDH.IDE.STREAM.KM deals with the IDE Key Management protocol (IDE-KM)
More information see Intel TDX Connect ABI Specification [1]
Section 3.2 TDX Connect Host-Side (SEAMCALL) Interface Functions.
[1]: https://cdrdv2.intel.com/v1/dl/getContent/858625
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
arch/x86/include/asm/tdx.h | 14 ++++++
arch/x86/virt/vmx/tdx/tdx.h | 4 ++
arch/x86/virt/vmx/tdx/tdx.c | 86 +++++++++++++++++++++++++++++++++++++
3 files changed, 104 insertions(+)
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 8abdad084972..7bdd66acda5b 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -260,6 +260,20 @@ u64 tdh_exec_spdm_mng(u64 spdm_id, u64 spdm_op, struct page *spdm_param,
struct page *spdm_rsp, struct page *spdm_req,
struct tdx_page_array *spdm_out,
u64 *spdm_req_or_out_len);
+u64 tdh_ide_stream_create(u64 stream_info, u64 spdm_id,
+ struct tdx_page_array *stream_mt, u64 stream_ctrl,
+ u64 rid_assoc1, u64 rid_assoc2,
+ u64 addr_assoc1, u64 addr_assoc2,
+ u64 addr_assoc3,
+ u64 *stream_id,
+ u64 *rp_ide_id);
+u64 tdh_ide_stream_block(u64 spdm_id, u64 stream_id);
+u64 tdh_ide_stream_delete(u64 spdm_id, u64 stream_id,
+ struct tdx_page_array *stream_mt,
+ unsigned int *nr_released, u64 *released_hpa);
+u64 tdh_ide_stream_km(u64 spdm_id, u64 stream_id, u64 operation,
+ struct page *spdm_rsp, struct page *spdm_req,
+ u64 *spdm_req_len);
#else
static inline void tdx_init(void) { }
static inline int tdx_cpu_enable(void) { return -ENODEV; }
diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
index 4784db2d1d92..d0a9694432de 100644
--- a/arch/x86/virt/vmx/tdx/tdx.h
+++ b/arch/x86/virt/vmx/tdx/tdx.h
@@ -66,6 +66,10 @@
#define TDH_IOMMU_CLEAR 129
#define TDH_SPDM_CREATE 130
#define TDH_SPDM_DELETE 131
+#define TDH_IDE_STREAM_CREATE 132
+#define TDH_IDE_STREAM_BLOCK 133
+#define TDH_IDE_STREAM_DELETE 134
+#define TDH_IDE_STREAM_KM 135
#define TDH_SPDM_CONNECT 142
#define TDH_SPDM_DISCONNECT 143
#define TDH_SPDM_MNG 144
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 02882c2ad177..72d836b25bd6 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -2616,3 +2616,89 @@ u64 tdh_exec_spdm_mng(u64 spdm_id, u64 spdm_op, struct page *spdm_param,
return r;
}
EXPORT_SYMBOL_FOR_MODULES(tdh_exec_spdm_mng, "tdx-host");
+
+u64 tdh_ide_stream_create(u64 stream_info, u64 spdm_id,
+ struct tdx_page_array *stream_mt, u64 stream_ctrl,
+ u64 rid_assoc1, u64 rid_assoc2,
+ u64 addr_assoc1, u64 addr_assoc2,
+ u64 addr_assoc3,
+ u64 *stream_id,
+ u64 *rp_ide_id)
+{
+ struct tdx_module_args args = {
+ .rcx = stream_info,
+ .rdx = spdm_id,
+ .r8 = hpa_array_t_assign_raw(stream_mt),
+ .r9 = stream_ctrl,
+ .r10 = rid_assoc1,
+ .r11 = rid_assoc2,
+ .r12 = addr_assoc1,
+ .r13 = addr_assoc2,
+ .r14 = addr_assoc3,
+ };
+ u64 r;
+
+ tdx_clflush_page_array(stream_mt);
+
+ r = seamcall_saved_ret(TDH_IDE_STREAM_CREATE, &args);
+
+ *stream_id = args.rcx;
+ *rp_ide_id = args.rdx;
+
+ return r;
+}
+EXPORT_SYMBOL_FOR_MODULES(tdh_ide_stream_create, "tdx-host");
+
+u64 tdh_ide_stream_block(u64 spdm_id, u64 stream_id)
+{
+ struct tdx_module_args args = {
+ .rcx = spdm_id,
+ .rdx = stream_id,
+ };
+
+ return seamcall(TDH_IDE_STREAM_BLOCK, &args);
+}
+EXPORT_SYMBOL_FOR_MODULES(tdh_ide_stream_block, "tdx-host");
+
+u64 tdh_ide_stream_delete(u64 spdm_id, u64 stream_id,
+ struct tdx_page_array *stream_mt,
+ unsigned int *nr_released, u64 *released_hpa)
+{
+ struct tdx_module_args args = {
+ .rcx = spdm_id,
+ .rdx = stream_id,
+ .r8 = hpa_array_t_release_raw(stream_mt),
+ };
+ u64 r;
+
+ r = seamcall_ret(TDH_IDE_STREAM_DELETE, &args);
+ if (r != TDX_SUCCESS)
+ return r;
+
+ *nr_released = FIELD_GET(HPA_ARRAY_T_SIZE, args.rcx) + 1;
+ *released_hpa = FIELD_GET(HPA_ARRAY_T_PFN, args.rcx) << PAGE_SHIFT;
+
+ return r;
+}
+EXPORT_SYMBOL_FOR_MODULES(tdh_ide_stream_delete, "tdx-host");
+
+u64 tdh_ide_stream_km(u64 spdm_id, u64 stream_id, u64 operation,
+ struct page *spdm_rsp, struct page *spdm_req,
+ u64 *spdm_req_len)
+{
+ struct tdx_module_args args = {
+ .rcx = spdm_id,
+ .rdx = stream_id,
+ .r8 = operation,
+ .r9 = page_to_phys(spdm_rsp),
+ .r10 = page_to_phys(spdm_req),
+ };
+ u64 r;
+
+ r = seamcall_ret_ir_resched(TDH_IDE_STREAM_KM, &args);
+
+ *spdm_req_len = args.rcx;
+
+ return r;
+}
+EXPORT_SYMBOL_FOR_MODULES(tdh_ide_stream_km, "tdx-host");
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 30/31] coco/tdx-host: Implement IDE stream setup/teardown
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (28 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 29/31] x86/virt/tdx: Add SEAMCALL wrappers for IDE stream management Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
2026-03-27 16:01 ` [PATCH v2 31/31] coco/tdx-host: Finally enable SPDM session and IDE Establishment Xu Yilun
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
Implementation for a most straightforward Selective IDE stream setup.
Hard code all parameters for Stream Control Register. And no IDE Key
Refresh support.
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
include/linux/pci-ide.h | 2 +
drivers/pci/ide.c | 5 +-
drivers/virt/coco/tdx-host/tdx-host.c | 226 ++++++++++++++++++++++++++
3 files changed, 231 insertions(+), 2 deletions(-)
diff --git a/include/linux/pci-ide.h b/include/linux/pci-ide.h
index 381a1bf22a95..f0c6975fd429 100644
--- a/include/linux/pci-ide.h
+++ b/include/linux/pci-ide.h
@@ -106,6 +106,8 @@ struct pci_ide {
void pci_ide_set_nr_streams(struct pci_host_bridge *hb, u16 nr);
struct pci_ide_partner *pci_ide_to_settings(struct pci_dev *pdev,
struct pci_ide *ide);
+void pci_ide_stream_to_regs(struct pci_dev *pdev, struct pci_ide *ide,
+ struct pci_ide_regs *regs);
struct pci_ide *pci_ide_stream_alloc(struct pci_dev *pdev);
void pci_ide_stream_free(struct pci_ide *ide);
int pci_ide_stream_register(struct pci_ide *ide);
diff --git a/drivers/pci/ide.c b/drivers/pci/ide.c
index b35e8aba7ecb..1337608448c2 100644
--- a/drivers/pci/ide.c
+++ b/drivers/pci/ide.c
@@ -556,8 +556,8 @@ static void mem_assoc_to_regs(struct pci_bus_region *region,
* @ide: registered IDE settings descriptor
* @regs: output register values
*/
-static void pci_ide_stream_to_regs(struct pci_dev *pdev, struct pci_ide *ide,
- struct pci_ide_regs *regs)
+void pci_ide_stream_to_regs(struct pci_dev *pdev, struct pci_ide *ide,
+ struct pci_ide_regs *regs)
{
struct pci_ide_partner *settings = pci_ide_to_settings(pdev, ide);
int assoc_idx = 0;
@@ -586,6 +586,7 @@ static void pci_ide_stream_to_regs(struct pci_dev *pdev, struct pci_ide *ide,
regs->nr_addr = assoc_idx;
}
+EXPORT_SYMBOL_GPL(pci_ide_stream_to_regs);
/**
* pci_ide_stream_setup() - program settings to Selective IDE Stream registers
diff --git a/drivers/virt/coco/tdx-host/tdx-host.c b/drivers/virt/coco/tdx-host/tdx-host.c
index d5072a68b81a..0f6056945788 100644
--- a/drivers/virt/coco/tdx-host/tdx-host.c
+++ b/drivers/virt/coco/tdx-host/tdx-host.c
@@ -72,6 +72,10 @@ struct tdx_tsm_link {
struct tdx_page_array *spdm_mt;
unsigned int dev_info_size;
void *dev_info_data;
+
+ struct pci_ide *ide;
+ struct tdx_page_array *stream_mt;
+ unsigned int stream_id;
};
static struct tdx_tsm_link *to_tdx_tsm_link(struct pci_tsm *tsm)
@@ -351,6 +355,219 @@ static void tdx_spdm_session_teardown(struct tdx_tsm_link *tlink)
DEFINE_FREE(tdx_spdm_session_teardown, struct tdx_tsm_link *,
if (!IS_ERR_OR_NULL(_T)) tdx_spdm_session_teardown(_T))
+enum tdx_ide_stream_km_op {
+ TDX_IDE_STREAM_KM_SETUP = 0,
+ TDX_IDE_STREAM_KM_REFRESH = 1,
+ TDX_IDE_STREAM_KM_STOP = 2,
+};
+
+static int tdx_ide_stream_km(struct tdx_tsm_link *tlink,
+ enum tdx_ide_stream_km_op op)
+{
+ u64 r, out_msg_sz;
+ int ret;
+
+ do {
+ r = tdh_ide_stream_km(tlink->spdm_id, tlink->stream_id, op,
+ tlink->in_msg, tlink->out_msg,
+ &out_msg_sz);
+ ret = tdx_tsm_link_event_handler(tlink, r, out_msg_sz);
+ } while (ret == -EAGAIN);
+
+ return ret;
+}
+
+static struct tdx_tsm_link *
+tdx_ide_stream_key_program(struct tdx_tsm_link *tlink)
+{
+ int ret;
+
+ ret = tdx_ide_stream_km(tlink, TDX_IDE_STREAM_KM_SETUP);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return tlink;
+}
+
+static void tdx_ide_stream_key_stop(struct tdx_tsm_link *tlink)
+{
+ tdx_ide_stream_km(tlink, TDX_IDE_STREAM_KM_STOP);
+}
+
+DEFINE_FREE(tdx_ide_stream_key_stop, struct tdx_tsm_link *,
+ if (!IS_ERR_OR_NULL(_T)) tdx_ide_stream_key_stop(_T))
+
+static void sel_stream_block_regs(struct pci_dev *pdev, struct pci_ide *ide,
+ struct pci_ide_regs *regs)
+{
+ struct pci_dev *rp = pcie_find_root_port(pdev);
+ struct pci_ide_partner *setting = pci_ide_to_settings(rp, ide);
+
+ /* only support address association for prefetchable memory */
+ setting->mem_assoc = (struct pci_bus_region) { 0, -1 };
+ pci_ide_stream_to_regs(rp, ide, regs);
+}
+
+#define STREAM_INFO_RP_DEVFN GENMASK_ULL(7, 0)
+#define STREAM_INFO_TYPE BIT_ULL(8)
+#define STREAM_INFO_TYPE_LINK 0
+#define STREAM_INFO_TYPE_SEL 1
+
+static struct tdx_tsm_link *tdx_ide_stream_create(struct tdx_tsm_link *tlink,
+ struct pci_ide *ide)
+{
+ u64 stream_info, stream_ctrl;
+ u64 stream_id, rp_ide_id;
+ unsigned int nr_pages = tdx_sysinfo->connect.ide_mt_page_count;
+ struct pci_dev *pdev = tlink->pci.base_tsm.pdev;
+ struct pci_dev *rp = pcie_find_root_port(pdev);
+ struct pci_ide_regs regs;
+ u64 r;
+
+ struct tdx_page_array *stream_mt __free(tdx_page_array_free) =
+ tdx_page_array_create(nr_pages);
+ if (!stream_mt)
+ return ERR_PTR(-ENOMEM);
+
+ stream_info = FIELD_PREP(STREAM_INFO_RP_DEVFN, rp->devfn);
+ stream_info |= FIELD_PREP(STREAM_INFO_TYPE, STREAM_INFO_TYPE_SEL);
+
+ /*
+ * For Selective IDE stream, below values must be 0:
+ * NPR_AGG/PR_AGG/CPL_AGG/CONF_REQ/ALGO/DEFAULT/STREAM_ID
+ *
+ * below values are configurable but now hardcode to 0:
+ * PCRC/TC
+ */
+ stream_ctrl = FIELD_PREP(PCI_IDE_SEL_CTL_EN, 0) |
+ FIELD_PREP(PCI_IDE_SEL_CTL_TX_AGGR_NPR, 0) |
+ FIELD_PREP(PCI_IDE_SEL_CTL_TX_AGGR_PR, 0) |
+ FIELD_PREP(PCI_IDE_SEL_CTL_TX_AGGR_CPL, 0) |
+ FIELD_PREP(PCI_IDE_SEL_CTL_PCRC_EN, 0) |
+ FIELD_PREP(PCI_IDE_SEL_CTL_CFG_EN, 0) |
+ FIELD_PREP(PCI_IDE_SEL_CTL_ALG, 0) |
+ FIELD_PREP(PCI_IDE_SEL_CTL_TC, 0) |
+ FIELD_PREP(PCI_IDE_SEL_CTL_ID, 0);
+
+ sel_stream_block_regs(pdev, ide, ®s);
+ if (regs.nr_addr != 1)
+ return ERR_PTR(-EFAULT);
+
+ r = tdh_ide_stream_create(stream_info, tlink->spdm_id,
+ stream_mt, stream_ctrl,
+ regs.rid1, regs.rid2, regs.addr[0].assoc1,
+ regs.addr[0].assoc2, regs.addr[0].assoc3,
+ &stream_id, &rp_ide_id);
+ if (r)
+ return ERR_PTR(-EFAULT);
+
+ tlink->stream_id = stream_id;
+ tlink->stream_mt = no_free_ptr(stream_mt);
+
+ pci_dbg(pdev, "%s stream id 0x%x rp ide_id 0x%llx\n", __func__,
+ tlink->stream_id, rp_ide_id);
+ return tlink;
+}
+
+static void tdx_ide_stream_delete(struct tdx_tsm_link *tlink)
+{
+ struct pci_dev *pdev = tlink->pci.base_tsm.pdev;
+ unsigned int nr_released;
+ u64 released_hpa, r;
+
+ r = tdh_ide_stream_block(tlink->spdm_id, tlink->stream_id);
+ if (r) {
+ pci_err(pdev, "ide stream block fail 0x%llx\n", r);
+ goto leak;
+ }
+
+ r = tdh_ide_stream_delete(tlink->spdm_id, tlink->stream_id,
+ tlink->stream_mt, &nr_released,
+ &released_hpa);
+ if (r) {
+ pci_err(pdev, "ide stream delete fail 0x%llx\n", r);
+ goto leak;
+ }
+
+ if (tdx_page_array_ctrl_release(tlink->stream_mt, nr_released,
+ released_hpa)) {
+ pci_err(pdev, "fail to release IDE stream_mt pages\n");
+ goto leak;
+ }
+
+ return;
+
+leak:
+ tdx_page_array_ctrl_leak(tlink->stream_mt);
+}
+
+DEFINE_FREE(tdx_ide_stream_delete, struct tdx_tsm_link *,
+ if (!IS_ERR_OR_NULL(_T)) tdx_ide_stream_delete(_T))
+
+static struct tdx_tsm_link *tdx_ide_stream_setup(struct tdx_tsm_link *tlink)
+{
+ struct pci_dev *pdev = tlink->pci.base_tsm.pdev;
+ int ret;
+
+ struct pci_ide *ide __free(pci_ide_stream_release) =
+ pci_ide_stream_alloc(pdev);
+ if (!ide)
+ return ERR_PTR(-ENOMEM);
+
+ /* Configure IDE capability for RP & get stream_id */
+ struct tdx_tsm_link *tlink_create __free(tdx_ide_stream_delete) =
+ tdx_ide_stream_create(tlink, ide);
+ if (IS_ERR(tlink_create))
+ return tlink_create;
+
+ ide->stream_id = tlink->stream_id;
+ ret = pci_ide_stream_register(ide);
+ if (ret)
+ return ERR_PTR(ret);
+
+ /*
+ * Configure IDE capability for target device
+ *
+ * Some test devices work only with DEFAULT_STREAM enabled. For
+ * simplicity, enable DEFAULT_STREAM for all devices. A future decent
+ * solution may be to have a quirk table to specify which devices need
+ * DEFAULT_STREAM.
+ */
+ ide->partner[PCI_IDE_EP].default_stream = 1;
+ pci_ide_stream_setup(pdev, ide);
+
+ /* Key Programming for RP & target device, enable IDE stream for RP */
+ struct tdx_tsm_link *tlink_program __free(tdx_ide_stream_key_stop) =
+ tdx_ide_stream_key_program(tlink);
+ if (IS_ERR(tlink_program))
+ return tlink_program;
+
+ ret = tsm_ide_stream_register(ide);
+ if (ret)
+ return ERR_PTR(ret);
+
+ /* Enable IDE stream for target device */
+ ret = pci_ide_stream_enable(pdev, ide);
+ if (ret)
+ return ERR_PTR(ret);
+
+ retain_and_null_ptr(tlink_create);
+ retain_and_null_ptr(tlink_program);
+ tlink->ide = no_free_ptr(ide);
+
+ return tlink;
+}
+
+static void tdx_ide_stream_teardown(struct tdx_tsm_link *tlink)
+{
+ tdx_ide_stream_key_stop(tlink);
+ tdx_ide_stream_delete(tlink);
+ pci_ide_stream_release(tlink->ide);
+}
+
+DEFINE_FREE(tdx_ide_stream_teardown, struct tdx_tsm_link *,
+ if (!IS_ERR_OR_NULL(_T)) tdx_ide_stream_teardown(_T))
+
static int tdx_tsm_link_connect(struct pci_dev *pdev)
{
struct tdx_tsm_link *tlink = to_tdx_tsm_link(pdev->tsm);
@@ -362,7 +579,15 @@ static int tdx_tsm_link_connect(struct pci_dev *pdev)
return PTR_ERR(tlink_spdm);
}
+ struct tdx_tsm_link *tlink_ide __free(tdx_ide_stream_teardown) =
+ tdx_ide_stream_setup(tlink);
+ if (IS_ERR(tlink_ide)) {
+ pci_err(pdev, "fail to setup ide stream\n");
+ return PTR_ERR(tlink_ide);
+ }
+
retain_and_null_ptr(tlink_spdm);
+ retain_and_null_ptr(tlink_ide);
return 0;
}
@@ -371,6 +596,7 @@ static void tdx_tsm_link_disconnect(struct pci_dev *pdev)
{
struct tdx_tsm_link *tlink = to_tdx_tsm_link(pdev->tsm);
+ tdx_ide_stream_teardown(tlink);
tdx_spdm_session_teardown(tlink);
}
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread* [PATCH v2 31/31] coco/tdx-host: Finally enable SPDM session and IDE Establishment
2026-03-27 16:01 [PATCH v2 00/31] PCI/TSM: PCIe Link Encryption Establishment via TDX platform services Xu Yilun
` (29 preceding siblings ...)
2026-03-27 16:01 ` [PATCH v2 30/31] coco/tdx-host: Implement IDE stream setup/teardown Xu Yilun
@ 2026-03-27 16:01 ` Xu Yilun
30 siblings, 0 replies; 38+ messages in thread
From: Xu Yilun @ 2026-03-27 16:01 UTC (permalink / raw)
To: linux-coco, linux-pci, dan.j.williams, x86
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, yilun.xu,
zhenzhong.duan, kvm, rick.p.edgecombe, dave.hansen, kas,
xiaoyao.li, vishal.l.verma, linux-kernel
The basic SPDM session and IDE functionalities are all implemented,
enable them.
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
---
drivers/virt/coco/tdx-host/tdx-host.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/drivers/virt/coco/tdx-host/tdx-host.c b/drivers/virt/coco/tdx-host/tdx-host.c
index 0f6056945788..7800afb0893d 100644
--- a/drivers/virt/coco/tdx-host/tdx-host.c
+++ b/drivers/virt/coco/tdx-host/tdx-host.c
@@ -883,7 +883,7 @@ static int tdx_iommu_enable_all(void)
return ret;
}
-static int __maybe_unused tdx_connect_init(struct device *dev)
+static int tdx_connect_init(struct device *dev)
{
struct tsm_dev *link;
int ret;
@@ -914,8 +914,7 @@ static int __maybe_unused tdx_connect_init(struct device *dev)
static int tdx_host_probe(struct faux_device *fdev)
{
- /* TODO: do tdx_connect_init() when it is fully implemented. */
- return 0;
+ return tdx_connect_init(&fdev->dev);
}
static struct faux_device_ops tdx_host_ops = {
--
2.25.1
^ permalink raw reply related [flat|nested] 38+ messages in thread