* [PATCH v2 12/31] x86/virt/tdx: Enable the Extensions after basic TDX Module init
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 13/31] x86/virt/tdx: Extend tdx_clflush_page() to handle compound pages
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 14/31] PCI/TSM: Report active IDE streams per host bridge
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 15/31] coco/tdx-host: Introduce a "tdx_host" device
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 16/31] coco/tdx-host: Support Link TSM for TDX host
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 17/31] acpi: Add KEYP support to fw_table parsing
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 18/31] iommu/vt-d: Cache max domain ID to avoid redundant calculation
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 19/31] iommu/vt-d: Reserve the MSB domain ID bit for the TDX module
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 20/31] x86/virt/tdx: Add a helper to loop on TDX_INTERRUPTED_RESUMABLE
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 21/31] x86/virt/tdx: Add SEAMCALL wrappers for trusted IOMMU setup and clear
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 22/31] iommu/vt-d: Export a helper to do function for each dmar_drhd_unit
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 23/31] coco/tdx-host: Setup all trusted IOMMUs on TDX Connect init
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 24/31] coco/tdx-host: Add a helper to exchange SPDM messages through DOE
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 25/31] x86/virt/tdx: Add SEAMCALL wrappers for SPDM management
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 26/31] mm: Add __free() support for __free_page()
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 27/31] coco/tdx-host: Implement SPDM session setup
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 28/31] coco/tdx-host: Parse ACPI KEYP table to init IDE for PCI host bridges
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 29/31] x86/virt/tdx: Add SEAMCALL wrappers for IDE stream management
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 30/31] coco/tdx-host: Implement IDE stream setup/teardown
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* [PATCH v2 31/31] coco/tdx-host: Finally enable SPDM session and IDE Establishment
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
In-Reply-To: <20260327160132.2946114-1-yilun.xu@linux.intel.com>
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
* Re: [PATCH v2 5/7] KVM: guest_memfd: Add cleanup interface for guest teardown
From: Ackerley Tng @ 2026-03-27 17:16 UTC (permalink / raw)
To: Kalra, Ashish, tglx, mingo, bp, dave.hansen, x86, hpa, seanjc,
peterz, thomas.lendacky, herbert, davem, ardb
Cc: pbonzini, aik, Michael.Roth, KPrateek.Nayak, Tycho.Andersen,
Nathan.Fontenot, jackyli, pgonda, rientjes, jacobhxu, xin,
pawan.kumar.gupta, babu.moger, dyoung, nikunj, john.allen, darwi,
linux-kernel, linux-crypto, kvm, linux-coco
In-Reply-To: <75cd28a5-fb51-47ae-97c7-191fe9a6e045@amd.com>
"Kalra, Ashish" <ashish.kalra@amd.com> writes:
> Hello Ackerley,
>
> On 3/11/2026 1:00 AM, Ackerley Tng wrote:
>> "Kalra, Ashish" <ashish.kalra@amd.com> writes:
>>
>>> Hello Ackerley,
>>>
>>> On 3/9/2026 4:01 AM, Ackerley Tng wrote:
>>>> Ashish Kalra <Ashish.Kalra@amd.com> writes:
>>>>
>>>>> From: Ashish Kalra <ashish.kalra@amd.com>
>>>>>
>>>>> Introduce kvm_arch_gmem_cleanup() to perform architecture-specific
>>>>> cleanups when the last file descriptor for the guest_memfd inode is
>>>>> closed. This typically occurs during guest shutdown and termination
>>>>> and allows for final resource release.
>>>>>
>>>>> Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
>>>>> ---
>>>>>
>>>>> [...snip...]
>>>>>
>>>>> diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
>>>>> index 017d84a7adf3..2724dd1099f2 100644
>>>>> --- a/virt/kvm/guest_memfd.c
>>>>> +++ b/virt/kvm/guest_memfd.c
>>>>> @@ -955,6 +955,14 @@ static void kvm_gmem_destroy_inode(struct inode *inode)
>>>>>
>>>>> static void kvm_gmem_free_inode(struct inode *inode)
>>>>> {
>>>>> +#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_CLEANUP
>>>>> + /*
>>>>> + * Finalize cleanup for the inode once the last guest_memfd
>>>>> + * reference is released. This usually occurs after guest
>>>>> + * termination.
>>>>> + */
>>>>> + kvm_arch_gmem_cleanup();
>>>>> +#endif
>>>>
>>>> Folks have already talked about the performance implications of doing
>>>> the scan and rmpopt, I just want to call out that one VM could have more
>>>> than one associated guest_memfd too.
>>>
>>> Yes, i have observed that kvm_gmem_free_inode() gets invoked multiple times
>>> at SNP guest shutdown.
>>>
>>> And the same is true for kvm_gmem_destroy_inode() too.
>>>
>>>>
>>>> I think the cleanup function should be thought of as cleanup for the
>>>> inode (even if it doesn't take an inode pointer since it's not (yet)
>>>> required).
>>>>
>>>> So, the gmem cleanup function should not handle deduplicating cleanup
>>>> requests, but the arch function should, if the cleanup needs
>>>> deduplicating.
>>>
>>> I agree, the arch function will have to handle deduplicating, and for that
>>> the arch function will probably need to be passed the inode pointer,
>>> to have a parameter to assist with deduplicating.
>>>
>>
>> By the time .free_folio() is called, folio->mapping may no longer exist,
>> so if we definitely want to deduplicate using something in the inode,
>> .free_folio() won't be the right callback to use.
>
> Ok.
>
>>
>> I was thinking that deduplicating using something in the folio would be
>> better. Can rmpopt take a PFN range? Then there's really no
>> deduplication, the cleanup would be nicely narrowed to whatever was just
>> freed. Perhaps the PFNs could be aligned up to the nearest PMD or PUD
>> size for rmpopt to do the right thing.
>>
>
> It will really be ideal if the cleanup can be narrowed down to whatever was just freed.
>
> RMPOPT takes a SPA which is GB aligned, so if the PFNs are aligned to the nearest
> PUD, then RMPOPT will be perfectly aligned to optimize the 1G regions that contained
> memory associated with that guest being freed.
>
> This will also be the most optimal way to use RMPOPT, as we only optimize the 1G regions
> that contains memory associated with that guest, which should be much smaller than
> optimizing the whole 2TB RAM.
>
> And that's what the actual plans for RMPOPT are.
>
> We had planned for a phased RMPOPT implementation.
>
> In the first phase, we were planning to do RMP re-optimizations for entire 2TB
> RAM.
>
> Once 1GB hugetlb guest_memfd support is merged, we planned to support re-enabling
> RMPOPT optimizations during 1GB page cleanup as a follow-on series.
>
> But i believe this support is dependent on:
> 1). in-place conversion for guest_memfd,
> 2). 2M hugepage support for guest_memfd.
>
You're right about this dependency. Do you meant guest_memfd THP support
for "2M hugepage"?
> Another alternative we are considering is implementing a bitmap of 1GB regions in guest_memfd
> that tracks when they are being freed and then issue RMPOPT on those 1GB regions.
> (and this will be independent of the 1GB hugeTLB support for guest_memfd).
>
>> Or perhaps some more tracking is required to check that the entire
>> aligned range is freed before doing the rmpopt.
>>
>> I need to implement some of this tracking for guest_memfd HugeTLB
>> support, so if the tracking is useful for you, we should discuss!
>
> Yes, this tracking is going to be useful for RMPOPT.
>
> Is this going to be implemented as part of the 1GB hugeTLB support for guest_memfd ?
>
Yes, this is going to be implemented as part of the HugeTLB support
for guest_memfd. HugeTLB support for guest_memfd extends to any HugeTLB
page size the host supports, so not just 1G, 2M as well. :)
>>
>>>>
>>>> Also, .free_inode() is called through RCU, so it could be called after
>>>> some delay. Could it be possible that .free_inode() ends up being called
>>>> way after the associated VM gets torn down, or after KVM the module gets
>>>> unloaded? Does rmpopt still work fine if KVM the module got unloaded?
>>>
>>> Yes, .free_inode() can probably get called after the associated VM has
>>> been torn down and which should be fine for issuing RMPOPT to do
>>> RMP re-optimizations.
>>>
>>> As far as about KVM module getting unloaded, then as part of the forthcoming patch-series,
>>> during KVM module unload, X86_SNP_SHUTDOWN would be issued which means SNP would get
>>> disabled and therefore, RMP checks are also disabled.
>>>
>>> And as CC_ATTR_HOST_SEV_SNP would then be cleared, therefore, snp_perform_rmp_optimization()
>>> will simply return.
>>>
>>
>> I think relying on CC_ATTR_HOST_SEV_SNP to skip optimization should be
>> best as long as there are no races (like the .free_inode() will
>> definitely not try to optimize when SNP is half shut down or something
>> like that.
>
> Yeah, i will have to take a look at such races.
>
>>
>>> Another option is to add a new guest_memfd superblock operation, and then do the
>>> final guest_memfd cleanup using the .evict_inode() callback. This will then ensure
>>> that the cleanup is not called through RCU and avoids any kind of delays, as following:
>>>
>>> +static void kvm_gmem_evict_inode(struct inode *inode)
>>> +{
>>> +#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_CLEANUP
>>> + kvm_arch_gmem_cleanup();
>>> +#endif
>>> + truncate_inode_pages_final(&inode->i_data);
>>> + clear_inode(inode);
>>> +}
>>> +
>>>
>>
>> At the point of .evict_inode(), CoCo-shared guest_memfd pages could
>> still be pinned (for DMA or whatever, accidentally or maliciously), can
>> rmpopt work on shared pages that might still be used for DMA?
>>
>
> Yes, RMPOPT should be safe to work here, as it checks the RMP table for assigned
> or private pages in the 1GB range specified. For a 1GB range full of shared pages,
> it will mark that range to be RMP optimized.
>
> If all RMPUPDATE's for all private->shared pages conversion have been completed at
> the point of .evict_inode(), then RMPOPT re-optimizations will work nicely.
>
Ah okay. The kvm_arch_gmem_invalidate() call in .free_folio is the part
that updates the RMP table to make anything private become shared.
So the RMPOPT probably needs to happen after the invalidate in .free_folio
The RMPOPT stuff is still useful even if the host never uses huge pages
for guest_memfd, right? If so, I think we still need a solution
regardless of when huge page support for guest_memfd lands.
What if we do it this way: in .free_folio, after doing the invalidate,
take the pfn of the folio being freed, align that to the GB containing
that pfn, then RMPOPT that? This way there is no dependency on the inode
being around.
RMPOPT looks up the shared/private-ness of the page in the RMP table
anyway so as long as the RMP table is updated, we should be good?
The awkward part is if RMPOPT is run twice when the RMP table state
hasn't changed. Is my understanding right that there will be no
correctness issues, just performance?
We can perhaps optimize (away or otherwise) unnecessary RMPOPTs later?
With this aligning-up-to-the-GB, at least we're not iterating the entire
host memory.
>> .invalidate_folio() and .free_folio() both actually happen on removal
>> from guest_memfd ownership, though both are not exactly when the folio
>> is completely not in use.
>>
>> Is the best time to optimize when the pages are truly freed?
>>
>
> Yes.
>
> Thanks,
> Ashish
>
Thank you!
>>> @@ -971,6 +979,7 @@ static const struct super_operations kvm_gmem_super_operations = {
>>> .alloc_inode = kvm_gmem_alloc_inode,
>>> .destroy_inode = kvm_gmem_destroy_inode,
>>> .free_inode = kvm_gmem_free_inode,
>>> + .evict_inode = kvm_gmem_evict_inode,
>>> };
>>>
>>>
>>> Thanks,
>>> Ashish
>>>
>>>>
>>>> IIUC the current kmem_cache_free(kvm_gmem_inode_cachep, GMEM_I(inode));
>>>> is fine because in kvm_gmem_exit(), there is a rcu_barrier() before
>>>> kmem_cache_destroy(kvm_gmem_inode_cachep);.
>>>>
>>>>> kmem_cache_free(kvm_gmem_inode_cachep, GMEM_I(inode));
>>>>> }
>>>>>
>>>>> --
>>>>> 2.43.0
^ permalink raw reply
* Re: [PATCH v5 2/2] dma-buf: heaps: system: add system_cc_shared heap for explicitly shared memory
From: T.J. Mercier @ 2026-03-27 19:43 UTC (permalink / raw)
To: Jiri Pirko
Cc: dri-devel, linaro-mm-sig, iommu, linux-media, sumit.semwal,
benjamin.gaignard, Brian.Starkey, jstultz, christian.koenig,
m.szyprowski, robin.murphy, jgg, leon, sean.anderson, ptesarik,
catalin.marinas, aneesh.kumar, suzuki.poulose, steven.price,
thomas.lendacky, john.allen, ashish.kalra, suravee.suthikulpanit,
linux-coco
In-Reply-To: <20260325192352.437608-3-jiri@resnulli.us>
On Wed, Mar 25, 2026 at 12:23 PM Jiri Pirko <jiri@resnulli.us> wrote:
>
> From: Jiri Pirko <jiri@nvidia.com>
>
> Add a new "system_cc_shared" dma-buf heap to allow userspace to
> allocate shared (decrypted) memory for confidential computing (CoCo)
> VMs.
>
> On CoCo VMs, guest memory is private by default. The hardware uses an
> encryption bit in page table entries (C-bit on AMD SEV, "shared" bit on
> Intel TDX) to control whether a given memory access is private or
> shared. The kernel's direct map is set up as private,
> so pages returned by alloc_pages() are private in the direct map
> by default. To make this memory usable for devices that do not support
> DMA to private memory (no TDISP support), it has to be explicitly
> shared. A couple of things are needed to properly handle
> shared memory for the dma-buf use case:
>
> - set_memory_decrypted() on the direct map after allocation:
> Besides clearing the encryption bit in the direct map PTEs, this
> also notifies the hypervisor about the page state change. On free,
> the inverse set_memory_encrypted() must be called before returning
> pages to the allocator. If re-encryption fails, pages
> are intentionally leaked to prevent shared memory from being
> reused as private.
>
> - pgprot_decrypted() for userspace and kernel virtual mappings:
> Any new mapping of the shared pages, be it to userspace via
> mmap or to kernel vmalloc space via vmap, creates PTEs independent
> of the direct map. These must also have the encryption bit cleared,
> otherwise accesses through them would see encrypted (garbage) data.
>
> - DMA_ATTR_CC_SHARED for DMA mapping:
> Since the pages are already shared, the DMA API needs to be
> informed via DMA_ATTR_CC_SHARED so it can map them correctly
> as unencrypted for device access.
>
> On non-CoCo VMs, the system_cc_shared heap is not registered
> to prevent misuse by userspace that does not understand
> the security implications of explicitly shared memory.
>
> Signed-off-by: Jiri Pirko <jiri@nvidia.com>
Reviewed-by: T.J. Mercier <tjmercier@google.com>
^ permalink raw reply
* Re: [PATCH v5 0/2] dma-buf: heaps: system: add an option to allocate explicitly shared/decrypted memory
From: T.J. Mercier @ 2026-03-27 19:43 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: Marek Szyprowski, Jiri Pirko, dri-devel, linaro-mm-sig, iommu,
linux-media, sumit.semwal, benjamin.gaignard, Brian.Starkey,
jstultz, christian.koenig, robin.murphy, leon, sean.anderson,
ptesarik, catalin.marinas, aneesh.kumar, suzuki.poulose,
steven.price, thomas.lendacky, john.allen, ashish.kalra,
suravee.suthikulpanit, linux-coco
In-Reply-To: <20260327121021.GB246076@ziepe.ca>
On Fri, Mar 27, 2026 at 5:10 AM Jason Gunthorpe <jgg@ziepe.ca> wrote:
>
> On Fri, Mar 27, 2026 at 10:38:10AM +0100, Marek Szyprowski wrote:
> > On 25.03.2026 20:23, Jiri Pirko wrote:
> > > From: Jiri Pirko <jiri@nvidia.com>
> > >
> > > Confidential computing (CoCo) VMs/guests, such as AMD SEV and Intel TDX,
> > > run with private/encrypted memory which creates a challenge
> > > for devices that do not support DMA to it (no TDISP support).
> > >
> > > For kernel-only DMA operations, swiotlb bounce buffering provides a
> > > transparent solution by copying data through shared memory.
> > > However, the only way to get this memory into userspace is via the DMA
> > > API's dma_alloc_pages()/dma_mmap_pages() type interfaces which limits
> > > the use of the memory to a single DMA device, and is incompatible with
> > > pin_user_pages().
> > >
> > > These limitations are particularly problematic for the RDMA subsystem
> > > which makes heavy use of pin_user_pages() and expects flexible memory
> > > usage between many different DMA devices.
> > >
> > > This patch series enables userspace to explicitly request shared
> > > (decrypted) memory allocations from new dma-buf system_cc_shared heap.
> > > Userspace can mmap this memory and pass the dma-buf fd to other
> > > existing importers such as RDMA or DRM devices to access the
> > > memory. The DMA API is improved to allow the dma heap exporter to DMA
> > > map the shared memory to each importing device.
> > >
> > > Based on dma-mapping-for-next e7442a68cd1ee797b585f045d348781e9c0dde0d
> >
> > I would like to merge this to dma-mapping-next, but I feel a bit
> > uncomfortable with my lack of knowledge about CoCo and friends. Could
> > those who know a bit more about it provide some Reviewed-by tags?
>
> I'm confident in the CC stuff, I was hoping to see someone from dmabuf
> heap land ack that the uAPI design is OK.. TJ?
>
> Jason
Hi, yes LGTM. From a uAPI perspective it's just another dma-buf heap.
^ permalink raw reply
* [GIT PULL] Confidential Computing: Attestation fixes for 7.0-rc6
From: Dan Williams @ 2026-03-27 20:55 UTC (permalink / raw)
To: Linus Torvalds
Cc: linux-coco, Zubin Mithra, Kiryl Shutsemau,
Kuppuswamy Sathyanarayanan, Dave Hansen, Xiaoyao Li, Mikko Ylinen,
linux-kernel
Hi Linus, please pull from:
git://git.kernel.org/pub/scm/linux/kernel/git/devsec/tsm tags/tsm-fixes-7.0-rc6
...to receive a fix for the tdx-guest driver. It has appeared in linux-next and
collides with a fix coming from tip/x86/tdx (resolution below). Going forward
all tdx-guest updates should move to the tip/x86/tdx topic, and tsm.git can
remain focused on core attestation report infrastructure.
My conflict resolution matches linux-next's:
diff --cc drivers/virt/coco/tdx-guest/tdx-guest.c
index 23ef3991c4d5,7cee97559ba2..a9ecc46df187
--- a/drivers/virt/coco/tdx-guest/tdx-guest.c
+++ b/drivers/virt/coco/tdx-guest/tdx-guest.c
@@@ -306,12 -309,12 +309,17 @@@ static int tdx_report_new_locked(struc
return ret;
}
+ if (quote_buf->status != GET_QUOTE_SUCCESS) {
+ pr_debug("GetQuote request failed, status:%llx\n", quote_buf->status);
+ return -EIO;
+ }
+
- buf = kvmemdup(quote_buf->data, quote_buf->out_len, GFP_KERNEL);
+ out_len = READ_ONCE(quote_buf->out_len);
+
+ if (out_len > TDX_QUOTE_MAX_LEN)
+ return -EFBIG;
+
+ buf = kvmemdup(quote_buf->data, out_len, GFP_KERNEL);
if (!buf)
return -ENOMEM;
---
The following changes since commit f338e77383789c0cae23ca3d48adcc5e9e137e3c:
Linux 7.0-rc4 (2026-03-15 13:52:05 -0700)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/devsec/tsm tags/tsm-fixes-7.0-rc6
for you to fetch changes up to c3fd16c3b98ed726294feab2f94f876290bf7b61:
virt: tdx-guest: Fix handling of host controlled 'quote' buffer length (2026-03-20 21:05:50 -0700)
----------------------------------------------------------------
tsm fixes for v7.0-rc6
- Fix a VMM controlled buffer length used to emit TDX attestation
reports.
----------------------------------------------------------------
Zubin Mithra (1):
virt: tdx-guest: Fix handling of host controlled 'quote' buffer length
drivers/virt/coco/tdx-guest/tdx-guest.c | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
^ permalink raw reply
* Re: [PATCH v2 00/16] fs,x86/resctrl: Add kernel-mode (e.g., PLZA) support to the resctrl subsystem
From: Reinette Chatre @ 2026-03-27 22:11 UTC (permalink / raw)
To: Babu Moger, corbet, tony.luck, Dave.Martin, james.morse, tglx,
mingo, bp, dave.hansen
Cc: skhan, x86, hpa, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid, kas,
rick.p.edgecombe, akpm, pmladek, rdunlap, dapeng1.mi, kees, elver,
paulmck, lirongqing, safinaskar, fvdl, seanjc, pawan.kumar.gupta,
xin, tiala, Neeraj.Upadhyay, chang.seok.bae, thomas.lendacky,
elena.reshetova, linux-doc, linux-kernel, linux-coco, kvm,
eranian, peternewman
In-Reply-To: <47c0db32-d0e0-4c53-90bd-b74863d233dc@amd.com>
Hi Babu,
On 3/26/26 10:12 AM, Babu Moger wrote:
> Hi Reinette,
>
> Thanks for the review comments. Will address one by one.
>
> On 3/24/26 17:51, Reinette Chatre wrote:
>> Hi Babu,
>>
>> On 3/12/26 1:36 PM, Babu Moger wrote:
>>> This series adds support for Privilege-Level Zero Association (PLZA) to the
>>> resctrl subsystem. PLZA is an AMD feature that allows specifying a CLOSID
>>> and/or RMID for execution in kernel mode (privilege level zero), so that
>>> kernel work is not subject to the same resource constrains as the current
>>> user-space task. This avoids kernel operations being aggressively throttled
>>> when a task's memory bandwidth is heavily limited.
>>>
>>> The feature documentation is not yet publicly available, but it is expected
>>> to be released in the next few weeks. In the meantime, a brief description
>>> of the features is provided below.
>>>
>>> Privilege Level Zero Association (PLZA)
>>>
>>> Privilege Level Zero Association (PLZA) allows the hardware to
>>> automatically associate execution in Privilege Level Zero (CPL=0) with a
>>> specific COS (Class of Service) and/or RMID (Resource Monitoring
>>> Identifier). The QoS feature set already has a mechanism to associate
>>> execution on each logical processor with an RMID or COS. PLZA allows the
>>> system to override this per-thread association for a thread that is
>>> executing with CPL=0.
>>> ------------------------------------------------------------------------
>>>
>>> The series introduces the feature in a way that supports the interface in
>>> a generic manner to accomodate MPAM or other vendor specific implimentation.
>>>
>>> Below is the detailed requirements provided by Reinette:
>>> https://lore.kernel.org/lkml/2ab556af-095b-422b-9396-f845c6fd0342@intel.com/
>> Our discussion considered how resctrl could support PLZA in a generic way while
>> also preparing to support MPAM's variants and how PLZA may evolve to have similar
>> capabilities when considering the capabilities of its registers.
>>
>> This does not mean that your work needs to implement everything that was discussed.
>> Instead, this work is expected to just support what PLZA is capable of today but
>> do so in a way that the future enhancements could be added to.
>>
>> This series is quite difficult to follow since it appears to implement a full
>> featured generic interface while PLZA cannot take advantage of it.
>>
>> Could you please simplify this work to focus on just enabling PLZA and only
>> add interfaces needed to do so?
> Sure. Will try. Lets continue the discussion.
>>
>>> Summary:
>>> 1. Kernel-mode/PLZA controls and status should be exposed under the resctrl
>>> info directory:/sys/fs/resctrl/info/, not as a separate or arch-specific path.
>>>
>>> 2. Add two info files
>>>
>>> a. kernel_mode
>>> Purpose: Control how resource allocation and monitoring apply in kernel mode
>>> (e.g. inherit from task vs global assign).
>>>
>>> Read: List supported modes and show current one (e.g. with [brackets]).
>>> Write: Set current mode by name (e.g. inherit_ctrl_and_mon, global_assign_ctrl_assign_mon).
>>>
>>> b. kernel_mode_assignment
>>>
>>> Purpose: When a “global assign” kernel mode is active, specify which resctrl group
>>> (CLOSID/RMID) is used for kernel work.
>>>
>>> Read: Show the assigned group in a path-like form (e.g. //, ctrl1//, ctrl1/mon1/).
>>> Write: Assign or clear the group used for kernel mode (and optionally clear with an empty write).
>>>
>>> The patches are based on top of commit (v7.0.0-rc3)
>>> 839e91ce3f41b (tip/master) Merge branch into tip/master: 'x86/tdx'
>>> ------------------------------------------------------------------------
>>>
>>> Examples: kernel_mode and kernel_mode_assignment
>>>
>>> All paths below are under /sys/fs/resctrl/ (e.g. info/kernel_mode means
>>> /sys/fs/resctrl/info/kernel_mode). Resctrl must be mounted and the platform
>>> must support the relevant modes (e.g. AMD with PLZA).
>>>
>>> 1) kernel_mode — show and set the current kernel mode
>>>
>>> Read supported modes and which one is active (current in brackets):
>>>
>>> $ cat info/kernel_mode
>>> [inherit_ctrl_and_mon]
>>> global_assign_ctrl_inherit_mon
>>> global_assign_ctrl_assign_mon
>>>
>>> Set the active mode (e.g. use one CLOSID+RMID for all kernel work):
>>>
>>> $ echo "global_assign_ctrl_assign_mon" > info/kernel_mode
>>> $ cat info/kernel_mode
>>> inherit_ctrl_and_mon
>>> global_assign_ctrl_inherit_mon
>>> [global_assign_ctrl_assign_mon]
>>>
>>> Mode meanings:
>>> - inherit_ctrl_and_mon: kernel uses same CLOSID/RMID as the current task (default).
>>> - global_assign_ctrl_inherit_mon: one CLOSID for all kernel work; RMID inherited from user.
>>> - global_assign_ctrl_assign_mon: one resource group (CLOSID+RMID) for all kernel work.
>>>
>>> 2) kernel_mode_assignment — show and set which group is used for kernel work
>>>
>>> Only relevant when kernel_mode is not "inherit_ctrl_and_mon". Read the
>> To help with future usages please connect visibility of this file with the mode in
>> info/kernel_mode. This helps us to support future modes with other resctrl files, possible
>> within each resource group.
>> Specifically, kernel_mode_assignment is not visible to user space if mode is "inherit_ctrl_and_mon",
>> while it is visible when mode is global_assign_ctrl_inherit_mon or global_assign_ctrl_assign_mon.
>
> Sure. Will do.
>
>>
>>> currently assigned group (path format is "CTRL_MON/MON/"):
>> The format depends on the mode, right? If the mode is "global_assign_ctrl_inherit_mon"
>> then it should only contain a control group, alternatively, if the mode is
>> "global_assign_ctrl_assign_mon" then it contains control and mon group. This gives
>> resctrl future flexibility to change format for future modes.
>
> This can be done both ways. Whole purpose of these groups is to get CLOSID and RMID to enable PLZA. User can echo CTRL_MON or MON group to kernel_mode_assignment in any of the modes. We can decide what needs to be updated in MSR (PQR_PLZA_ASSOC) based on what kernel mode is selected.
The "both ways" are specific to one of the two active modes though.
PLZA only needs the RMID when the mode is "global_assign_ctrl_assign_mon".
Displaying and parsing monitor group when the mode is
"global_assign_ctrl_inherit_mon" creates an inconsistent interface since the mode
only uses a control group. The interface to user space should match the mode otherwise
it becomes confusing.
...
>>>
>>> Tony suggested using global variables to store the kernel mode
>>> CLOSID and RMID. However, the kernel mode CLOSID and RMID are
>>> coming from rdtgroup structure with the new interface. Accessing
>>> them requires holding the associated lock, which would make the
>>> context switch path unnecessarily expensive. So, dropped the idea.
>>> https://lore.kernel.org/lkml/aXuxVSbk1GR2ttzF@agluck-desk3/
>>> Let me know if there are other ways to optimize this.
>> I do not see why the context switch path needs to be touched at all with this
>> implementation. Since PLZA only supports global assignment does it not mean that resctrl
>> only needs to update PQR_PLZA_ASSOC when user writes to info/kernel_mode and
>> info/kernel_mode_assignment?
>
> Each thread has an MSR to configure whether to associate privilege level zero execution with a separate COS and/or RMID, and the value of the COS and/or RMID. PLZA may be enabled or disabled on a per-thread basis. However, the COS and RMID association and configuration must be the same for all threads in the QOS Domain.
Based on previous comment in https://lore.kernel.org/lkml/abb049fa-3a3d-4601-9ae3-61eeb7fd8fcf@amd.com/
and this implementation all fields of PQR_PLZA_ASSOC except PQR_PLZA_ASSOC.plza_en must be the
same for all CPUs on the system, not just per QoS domain. Could you please confirm?
>
> So, PQR_PLZA_ASSOC is a per thread MSR just like PQR_ASSOC.
>
> Privilege-Level Zero Association (PLZA) allows the user to specify a COS and/or RMID associated with execution in Privilege-Level Zero. When enabled on a HW thread, when that thread enters Privilige-Level Zero, transactions associated with that thread will be associated with the PLZA COS and/or RMID. Otherwise, the HW thread will be associated with the COS and RMID identified by PQR_ASSOC.
>
> More below.
>
>>
>> Consider some of the scenarios:
>>
>> resctrl mount with default state:
>>
>> # cat info/kernel_mode
>> [inherit_ctrl_and_mon]
>> global_assign_ctrl_inherit_mon
>> global_assign_ctrl_assign_mon
>> # ls info/kernel_mode_assignment
>> ls: cannot access 'info/kernel_mode_assignment': No such file or directory
>>
>> enable global_assign_ctrl_assign_mon mode:
>> # echo "global_assign_ctrl_assign_mon" > info/kernel_mode
>>
>> Expectation here is that when user space sets this mode as above then resctrl would
>> in turn program MSR_IA32_PQR_PLZA_ASSOC on all CPUs to be:
>> MSR_IA32_PQR_PLZA_ASSOC.rmid=0
>> MSR_IA32_PQR_PLZA_ASSOC.rmid_en=1
>> MSR_IA32_PQR_PLZA_ASSOC.closid=0
>> MSR_IA32_PQR_PLZA_ASSOC.closid_en=1
>> MSR_IA32_PQR_PLZA_ASSOC.plza_en=1
>>
>> I do not see why it is necessary to maintain any per-CPU or per-task state or needing
>> to touch the context switch code. Since PLZA only supports global could it not
>> just set MSR_IA32_PQR_PLZA_ASSOC on all online CPUs and be done with it?
>> Only caveat is that if a CPU is offline then this setting needs to be stashed
>> so that MSR_IA32_PQR_PLZA_ASSOC can be set when new CPU comes online.
>>
>> The way that rdtgroup_config_kmode() introduced in patch #11 assumes it is dealing
>> with RDT_RESOURCE_L3 and traverses the resource domain list and resource group
>> CPU mask seems unnecessary to me as well as error prone since the system may only
>> have, for example, RDT_RESOURCE_MBA enabled or even just monitoring. Why not just set
>> MSR_IA32_PQR_PLZA_ASSOC on all CPUs and be done?
>>
>> To continue the scenarios ...
>>
>> After user's setting above related files read:
>> # cat info/kernel_mode
>> inherit_ctrl_and_mon
>> global_assign_ctrl_inherit_mon
>> [global_assign_ctrl_assign_mon]
>> # cat info/kernel_mode_assignment
>> //
>>
>> Modify group used by global_assign_ctrl_assign_mon mode:
>> # echo 'ctrl1/mon1/' > info/kernel_mode_assignment
>>
>> Expectation here is that when user space sets this then resctrl would
>> program MSR_IA32_PQR_PLZA_ASSOC on all CPUs to be:
>> MSR_IA32_PQR_PLZA_ASSOC.rmid=<rmid of mon1>
>> MSR_IA32_PQR_PLZA_ASSOC.rmid_en=1
>> MSR_IA32_PQR_PLZA_ASSOC.closid=<closid of ctrl1>
>> MSR_IA32_PQR_PLZA_ASSOC.closid_en=1
>> MSR_IA32_PQR_PLZA_ASSOC.plza_en=1
>
>
> This works correctly when PLZA associations are defined by per CPU. For example, lets assume that *ctrl1* is assigned *CLOSID 1*.
>
> In this scenario, every task in the system running on a any CPU will use the limits associated with *CLOSID 1* whenever it enters Privilege-Level Zero, because the CPU's *PQR_PLZA_ASSOC* register has PLZA enabled and CLOSID is 1.
>
> Now consider task-based association:
>
> We have two resctrl groups:
>
> * *ctrl1 -> CLOSID 1 -> task1.plza = 1 : *User wants PLZA be enabled
> for this task.
> * *ctrl2 -> CLOSID 2 -> task2.plza = 0 : *User wants PLZA
> disabled for this task.
>
> Suppose *task1* is first scheduled on *CPU 0*. This behaves as expected: since CPU 0 's *PQR_PLZA_ASSOC* contains *CLOSID 1, plza_en =1*, task1 will use the limits from CLOSID 1 when it enters Privilege-Level Zero.
>
> However, if *task2* later runs on *CPU 0*, we expect it to use *CLOSID 2* in both user mode and kernel mode, because user has PLZA disabled for this task. But CPU 0 still has *CLOSID 1, **plza_en =1* in its PQR_PLZA_ASSOC register.
>
> As a result, task2 will incorrectly run with *CLOSID 1* when entering Privilege-Level Zero something we explicitly want to avoid.
>
> At that point, PLZA must be disabled on CPU 0 to prevent the unintended association. Hope this explanation makes the issue clear.
>
A couple of points:
- Looks like we still need to come to agreement what is meant by "global" when it
comes to kernel mode.
In your description there is a "global" configuration, but the assignment is "per-task".
To me this sounds like a new and distinct kernel_mode from the "global" modes
considered so far. This seems to move to the "per_task" mode mentioned in but
the implementation does not take into account any of the earlier discussions
surrounding it:
https://lore.kernel.org/lkml/2ab556af-095b-422b-9396-f845c6fd0342@intel.com/
We only learned about one use case in https://lore.kernel.org/lkml/CABPqkBSq=cgn-am4qorA_VN0vsbpbfDePSi7gubicpROB1=djw@mail.gmail.com/
As I understand this use case requires PLZA globally enabled for all tasks. Thus
I consider task assignment to be "global" when in the "global_*" kernel modes.
If this is indeed a common use case then supporting only global configuration
but then requiring user space to manually assign all tasks afterwards sounds
cumbersome for user space and also detrimental to system performance with all
the churn to modify all the task_structs involved. The accompanying documentation
does not mention all this additional user space interactions required by user
space to use this implementation.
I find this implementation difficult and inefficient to use in the one use case
we know of. I would suggest that resctrl optimizes for the one known use case.
- This implementation ignores discussion on how existing resctrl files should
not be repurposed.
This implementation allows user space to set a resource group in
kernel_mode_assignment with the consequence that this resource group's
"tasks" file changes behavior. I consider this a break of resctrl interface.
We did briefly consider per-task configuration/assignment in previous discussion
and the proposal was for it to use a new file (only when and if needed!).
- Now a user is required to write the task id of every task that participates
in PLZA. Apart from the churn already mentioned this also breaks existing
usage since it is no longer possible for new tasks to be added to this
resource group. This creates an awkward interface where all tasks belonging
to a resource group inherits the allocations/monitoring for their user space
work and will get PLZA enabled whether user requested it or not while
tasks from other resource groups need to be explicitly enabled. This creates
an inconsistency when it comes to task assignment. The only way to "remove"
PLZA from such a task would be to assign it to another resource group which
may not have the user space allocations ... and once this is done the task
cannot be moved back.
There is no requirement that CLOSID/RMID should be dedicated to kernel work
but this implementation does so in an inconsistent way.
- Apart from the same issues as with repurposing of tasks file, why should same
CPU allocation be used for kernel and user space?
Reinette
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox