* [PATCH v4 08/24] x86/virt/seamldr: Block TDX Module updates if any CPU is offline
From: Chao Gao @ 2026-02-12 14:35 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, binbin.wu,
tony.lindgren, Chao Gao, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, H. Peter Anvin
In-Reply-To: <20260212143606.534586-1-chao.gao@intel.com>
P-SEAMLDR requires every CPU to call SEAMLDR.INSTALL during updates. So,
every CPU should be online during updates.
Check if all CPUs are online and abort the update if any CPU is offline at
the very beginning. Without this check, P-SEAMLDR will report failure at a
later phase where the old TDX module is gone and TDs have to be killed.
Hold cpus_read_lock to avoid races between CPU hotplug and TDX Module
updates.
Signed-off-by: Chao Gao <chao.gao@intel.com>
Reviewed-by: Xu Yilun <yilun.xu@linux.intel.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
---
arch/x86/virt/vmx/tdx/seamldr.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index 4d40b08f9bed..694243f1f220 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -6,6 +6,8 @@
*/
#define pr_fmt(fmt) "seamldr: " fmt
+#include <linux/cpuhplock.h>
+#include <linux/cpumask.h>
#include <linux/mm.h>
#include <linux/spinlock.h>
@@ -53,6 +55,12 @@ int seamldr_install_module(const u8 *data, u32 size)
if (WARN_ON_ONCE(!is_vmalloc_addr(data)))
return -EINVAL;
+ guard(cpus_read_lock)();
+ if (!cpumask_equal(cpu_online_mask, cpu_present_mask)) {
+ pr_err("Cannot update the TDX Module if any CPU is offline\n");
+ return -EBUSY;
+ }
+
/* TODO: Update TDX Module here */
return 0;
}
--
2.47.3
^ permalink raw reply related
* [PATCH v4 07/24] coco/tdx-host: Implement firmware upload sysfs ABI for TDX Module updates
From: Chao Gao @ 2026-02-12 14:35 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, binbin.wu,
tony.lindgren, Chao Gao, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, H. Peter Anvin
In-Reply-To: <20260212143606.534586-1-chao.gao@intel.com>
Linux kernel supports two primary firmware update mechanisms:
- request_firmware()
- firmware upload (or fw_upload)
The former is used by microcode updates, SEV firmware updates, etc. The
latter is used by CXL and FPGA firmware updates.
One key difference between them is: request_firmware() loads a named
file from the filesystem, while fw_upload accepts a bitstream directly
from userspace.
Use fw_upload for TDX Module updates as loading a named file isn't
suitable for TDX (see below for more reasons). Specifically, register
TDX faux device with fw_upload framework to expose sysfs interfaces
and implement operations to process data blobs supplied by userspace.
Implementation notes:
1. P-SEAMLDR processes the entire update at once rather than
chunk-by-chunk, so .write() is called only once per update; so the
offset should be always 0.
2. An update completes synchronously within .write(), meaning
.poll_complete() is only called after the update succeeds and so always
returns success
Why fw_upload instead of request_firmware()?
============================================
The explicit file selection capabilities of fw_upload is preferred over
the implicit file selection of request_firmware() for the following
reasons:
a. Intel distributes all versions of the TDX Module, allowing admins to
load any version rather than always defaulting to the latest. This
flexibility is necessary because future extensions may require reverting to
a previous version to clear fatal errors.
b. Some module version series are platform-specific. For example, the 1.5.x
series is for certain platform generations, while the 2.0.x series is
intended for others.
c. The update policy for TDX Module updates is non-linear at times. The
latest TDX Module may not be compatible. For example, TDX Module 1.5.x
may be updated to 1.5.y but not to 1.5.y+1. This policy is documented
separately in a file released along with each TDX Module release.
So, the default policy of "request_firmware()" of "always load latest", is
not suitable for TDX. Userspace needs to deploy a more sophisticated policy
check (e.g., latest may not be compatible), and there is potential
operator choice to consider.
Just have userspace pick rather than add kernel mechanism to change the
default policy of request_firmware().
Signed-off-by: Chao Gao <chao.gao@intel.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
---
v4:
- make tdx_fwl static [Kai]
- don't support update canceling [Yilun]
- explain why seamldr_init() doesn't return an error [Kai]
- bail out if TDX module updates are not supported [Kai]
- name the firmware "tdx_module" instead of "seamldr_upload" [Cedric]
v3:
- clear "cancel_request" in the "prepare" phase [Binbin]
- Don't fail the whole tdx-host device if seamldr_init() met an error
[Yilun]
- Add kdoc for seamldr_install_module() and verify that the input
buffer is vmalloc'd. [Yilun]
---
arch/x86/include/asm/seamldr.h | 1 +
arch/x86/include/asm/tdx.h | 5 ++
arch/x86/virt/vmx/tdx/seamldr.c | 19 +++++
drivers/virt/coco/tdx-host/Kconfig | 2 +
drivers/virt/coco/tdx-host/tdx-host.c | 114 +++++++++++++++++++++++++-
5 files changed, 140 insertions(+), 1 deletion(-)
diff --git a/arch/x86/include/asm/seamldr.h b/arch/x86/include/asm/seamldr.h
index 954d850e34e3..e354d0bfc9f8 100644
--- a/arch/x86/include/asm/seamldr.h
+++ b/arch/x86/include/asm/seamldr.h
@@ -32,5 +32,6 @@ struct seamldr_info {
static_assert(sizeof(struct seamldr_info) == 256);
int seamldr_get_info(struct seamldr_info *seamldr_info);
+int seamldr_install_module(const u8 *data, u32 size);
#endif /* _ASM_X86_SEAMLDR_H */
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index cb2219302dfc..ffadbf64d0c1 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -103,6 +103,11 @@ int tdx_enable(void);
const char *tdx_dump_mce_info(struct mce *m);
const struct tdx_sys_info *tdx_get_sysinfo(void);
+static inline bool tdx_supports_runtime_update(const struct tdx_sys_info *sysinfo)
+{
+ return false; /* To be enabled when kernel is ready */
+}
+
int tdx_guest_keyid_alloc(void);
u32 tdx_get_nr_guest_keyids(void);
void tdx_guest_keyid_free(unsigned int keyid);
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index d17db3c0151e..4d40b08f9bed 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -6,6 +6,7 @@
*/
#define pr_fmt(fmt) "seamldr: " fmt
+#include <linux/mm.h>
#include <linux/spinlock.h>
#include <asm/seamldr.h>
@@ -38,3 +39,21 @@ int seamldr_get_info(struct seamldr_info *seamldr_info)
return seamldr_call(P_SEAMLDR_INFO, &args);
}
EXPORT_SYMBOL_FOR_MODULES(seamldr_get_info, "tdx-host");
+
+/**
+ * seamldr_install_module - Install a new TDX module
+ * @data: Pointer to the TDX module update blob. It should be vmalloc'd
+ * memory.
+ * @size: Size of the TDX module update blob
+ *
+ * Returns 0 on success, negative error code on failure.
+ */
+int seamldr_install_module(const u8 *data, u32 size)
+{
+ if (WARN_ON_ONCE(!is_vmalloc_addr(data)))
+ return -EINVAL;
+
+ /* TODO: Update TDX Module here */
+ return 0;
+}
+EXPORT_SYMBOL_FOR_MODULES(seamldr_install_module, "tdx-host");
diff --git a/drivers/virt/coco/tdx-host/Kconfig b/drivers/virt/coco/tdx-host/Kconfig
index e58bad148a35..3d580d783106 100644
--- a/drivers/virt/coco/tdx-host/Kconfig
+++ b/drivers/virt/coco/tdx-host/Kconfig
@@ -1,6 +1,8 @@
config TDX_HOST_SERVICES
tristate "TDX Host Services Driver"
depends on INTEL_TDX_HOST
+ select FW_LOADER
+ select FW_UPLOAD
default m
help
Enable access to TDX host services like module update and
diff --git a/drivers/virt/coco/tdx-host/tdx-host.c b/drivers/virt/coco/tdx-host/tdx-host.c
index fd6ffb4f2ff1..9ade3028a5bd 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/firmware.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/sysfs.h>
@@ -20,6 +21,8 @@ static const struct x86_cpu_id tdx_host_ids[] = {
};
MODULE_DEVICE_TABLE(x86cpu, tdx_host_ids);
+static struct fw_upload *tdx_fwl;
+
static ssize_t version_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
@@ -103,6 +106,115 @@ static const struct attribute_group *tdx_host_groups[] = {
NULL,
};
+static enum fw_upload_err tdx_fw_prepare(struct fw_upload *fwl,
+ const u8 *data, u32 size)
+{
+ return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err tdx_fw_write(struct fw_upload *fwl, const u8 *data,
+ u32 offset, u32 size, u32 *written)
+{
+ int ret;
+
+ /*
+ * tdx_fw_write() always processes all data on the first call with
+ * offset == 0. Since it never returns partial success (it either
+ * succeeds completely or fails), there is no subsequent call with
+ * non-zero offsets.
+ */
+ WARN_ON_ONCE(offset);
+ ret = seamldr_install_module(data, size);
+ switch (ret) {
+ case 0:
+ *written = size;
+ return FW_UPLOAD_ERR_NONE;
+ case -EBUSY:
+ return FW_UPLOAD_ERR_BUSY;
+ case -EIO:
+ return FW_UPLOAD_ERR_HW_ERROR;
+ case -ENOSPC:
+ return FW_UPLOAD_ERR_WEAROUT;
+ case -ENOMEM:
+ return FW_UPLOAD_ERR_RW_ERROR;
+ default:
+ return FW_UPLOAD_ERR_FW_INVALID;
+ }
+}
+
+static enum fw_upload_err tdx_fw_poll_complete(struct fw_upload *fwl)
+{
+ /*
+ * TDX Module updates are completed in the previous phase
+ * (tdx_fw_write()). If any error occurred, the previous phase
+ * would return an error code to abort the update process. In
+ * other words, reaching this point means the update succeeded.
+ */
+ return FW_UPLOAD_ERR_NONE;
+}
+
+/*
+ * TDX Module updates cannot be cancelled. Provide a stub function since
+ * the firmware upload framework requires a .cancel operation.
+ */
+static void tdx_fw_cancel(struct fw_upload *fwl)
+{
+}
+
+static const struct fw_upload_ops tdx_fw_ops = {
+ .prepare = tdx_fw_prepare,
+ .write = tdx_fw_write,
+ .poll_complete = tdx_fw_poll_complete,
+ .cancel = tdx_fw_cancel,
+};
+
+static void seamldr_init(struct device *dev)
+{
+ const struct tdx_sys_info *tdx_sysinfo = tdx_get_sysinfo();
+ int ret;
+
+ if (WARN_ON_ONCE(!tdx_sysinfo))
+ return;
+
+ if (!tdx_supports_runtime_update(tdx_sysinfo)) {
+ pr_info("Current TDX Module cannot be updated. Consider BIOS updates\n");
+ return;
+ }
+
+ tdx_fwl = firmware_upload_register(THIS_MODULE, dev, "tdx_module",
+ &tdx_fw_ops, NULL);
+ ret = PTR_ERR_OR_ZERO(tdx_fwl);
+ if (ret)
+ pr_err("failed to register module uploader %d\n", ret);
+}
+
+static void seamldr_deinit(void)
+{
+ if (tdx_fwl)
+ firmware_upload_unregister(tdx_fwl);
+}
+
+static int tdx_host_probe(struct faux_device *fdev)
+{
+ /*
+ * P-SEAMLDR capabilities are optional. Don't fail the entire
+ * device probe if initialization fails.
+ */
+ seamldr_init(&fdev->dev);
+
+ return 0;
+}
+
+static void tdx_host_remove(struct faux_device *fdev)
+{
+ seamldr_deinit();
+}
+
+static struct faux_device_ops tdx_host_ops = {
+ .probe = tdx_host_probe,
+ .remove = tdx_host_remove,
+};
+
static struct faux_device *fdev;
static int __init tdx_host_init(void)
@@ -110,7 +222,7 @@ static int __init tdx_host_init(void)
if (!x86_match_cpu(tdx_host_ids) || !tdx_get_sysinfo())
return -ENODEV;
- fdev = faux_device_create_with_groups(KBUILD_MODNAME, NULL, NULL, tdx_host_groups);
+ fdev = faux_device_create_with_groups(KBUILD_MODNAME, NULL, &tdx_host_ops, tdx_host_groups);
if (!fdev)
return -ENODEV;
--
2.47.3
^ permalink raw reply related
* [PATCH v4 06/24] coco/tdx-host: Expose P-SEAMLDR information via sysfs
From: Chao Gao @ 2026-02-12 14:35 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, binbin.wu,
tony.lindgren, Chao Gao, Farrah Chen
In-Reply-To: <20260212143606.534586-1-chao.gao@intel.com>
TDX Module updates require userspace to select the appropriate module
to load. Expose necessary information to facilitate this decision. Two
values are needed:
- P-SEAMLDR version: for compatibility checks between TDX Module and
P-SEAMLDR
- num_remaining_updates: indicates how many updates can be performed
Expose them as tdx-host device attributes.
Signed-off-by: Chao Gao <chao.gao@intel.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
Tested-by: Farrah Chen <farrah.chen@intel.com>
---
v4:
- Make seamldr attribute permission "0400" [Dave]
- Don't include implementation details in OS ABI docs [Dave]
- Tag tdx_host_group as static [Kai]
v3:
- use #ifdef rather than .is_visible() to control P-SEAMLDR sysfs
visibility [Yilun]
---
.../ABI/testing/sysfs-devices-faux-tdx-host | 23 +++++++
drivers/virt/coco/tdx-host/tdx-host.c | 63 ++++++++++++++++++-
2 files changed, 85 insertions(+), 1 deletion(-)
diff --git a/Documentation/ABI/testing/sysfs-devices-faux-tdx-host b/Documentation/ABI/testing/sysfs-devices-faux-tdx-host
index 901abbae2e61..88a9c0b2bdfe 100644
--- a/Documentation/ABI/testing/sysfs-devices-faux-tdx-host
+++ b/Documentation/ABI/testing/sysfs-devices-faux-tdx-host
@@ -4,3 +4,26 @@ Description: (RO) Report the version of the loaded TDX Module. The TDX Module
version is formatted as x.y.z, where "x" is the major version,
"y" is the minor version and "z" is the update version. Versions
are used for bug reporting, TDX Module updates and etc.
+
+What: /sys/devices/faux/tdx_host/seamldr/version
+Contact: linux-coco@lists.linux.dev
+Description: (RO) Report the version of the loaded SEAM loader. The SEAM
+ loader version is formatted as x.y.z, where "x" is the major
+ version, "y" is the minor version and "z" is the update version.
+ Versions are used for bug reporting and compatibility checks.
+
+What: /sys/devices/faux/tdx_host/seamldr/num_remaining_updates
+Contact: linux-coco@lists.linux.dev
+Description: (RO) Report the number of remaining updates. TDX maintains a
+ log about each TDX Module which has been loaded. This log has
+ a finite size which limits the number of TDX Module updates
+ which can be performed.
+
+ After each successful update, the number reduces by one. Once it
+ reaches zero, further updates will fail until next reboot. The
+ number is always zero if the P-SEAMLDR doesn't support updates.
+
+ See Intel® Trust Domain Extensions - SEAM Loader (SEAMLDR)
+ Interface Specification, Revision 343755-003, Chapter 3.3
+ "SEAMLDR_INFO" and Chapter 4.2 "SEAMLDR.INSTALL" for more
+ information.
diff --git a/drivers/virt/coco/tdx-host/tdx-host.c b/drivers/virt/coco/tdx-host/tdx-host.c
index 0424933b2560..fd6ffb4f2ff1 100644
--- a/drivers/virt/coco/tdx-host/tdx-host.c
+++ b/drivers/virt/coco/tdx-host/tdx-host.c
@@ -11,6 +11,7 @@
#include <linux/sysfs.h>
#include <asm/cpu_device_id.h>
+#include <asm/seamldr.h>
#include <asm/tdx.h>
static const struct x86_cpu_id tdx_host_ids[] = {
@@ -40,7 +41,67 @@ static struct attribute *tdx_host_attrs[] = {
&dev_attr_version.attr,
NULL,
};
-ATTRIBUTE_GROUPS(tdx_host);
+
+static struct attribute_group tdx_host_group = {
+ .attrs = tdx_host_attrs,
+};
+
+static ssize_t seamldr_version_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct seamldr_info info;
+ int ret;
+
+ ret = seamldr_get_info(&info);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u.%u.%02u\n", info.major_version,
+ info.minor_version,
+ info.update_version);
+}
+
+static ssize_t num_remaining_updates_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct seamldr_info info;
+ int ret;
+
+ ret = seamldr_get_info(&info);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "%u\n", info.num_remaining_updates);
+}
+
+/*
+ * Open-code DEVICE_ATTR_ADMIN_RO to specify a different 'show' function
+ * for P-SEAMLDR version as version_show() is used for TDX Module version.
+ *
+ * admin-only readable as reading these attributes calls into P-SEAMLDR,
+ * which may have potential performance and system impact.
+ */
+static struct device_attribute dev_attr_seamldr_version =
+ __ATTR(version, 0400, seamldr_version_show, NULL);
+static DEVICE_ATTR_ADMIN_RO(num_remaining_updates);
+
+static struct attribute *seamldr_attrs[] = {
+ &dev_attr_seamldr_version.attr,
+ &dev_attr_num_remaining_updates.attr,
+ NULL,
+};
+
+static struct attribute_group seamldr_group = {
+ .name = "seamldr",
+ .attrs = seamldr_attrs,
+};
+
+static const struct attribute_group *tdx_host_groups[] = {
+ &tdx_host_group,
+ &seamldr_group,
+ NULL,
+};
static struct faux_device *fdev;
--
2.47.3
^ permalink raw reply related
* [PATCH v4 05/24] x86/virt/seamldr: Retrieve P-SEAMLDR information
From: Chao Gao @ 2026-02-12 14:35 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, binbin.wu,
tony.lindgren, Chao Gao, Farrah Chen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin
In-Reply-To: <20260212143606.534586-1-chao.gao@intel.com>
P-SEAMLDR returns its information such as version number, in response to
the SEAMLDR.INFO SEAMCALL.
This information is useful for userspace. For example, the admin can decide
which TDX module versions are compatible with the P-SEAMLDR according to
the P-SEAMLDR version.
Retrieve P-SEAMLDR information in preparation for exposing P-SEAMLDR
version and other necessary information to userspace. Export the new kAPI
for use by tdx-host.ko.
Note that there are two distinct P-SEAMLDR APIs with similar names:
SEAMLDR.INFO: Returns a SEAMLDR_INFO structure containing SEAMLDR
information such as version and remaining updates.
SEAMLDR.SEAMINFO: Returns a SEAMLDR_SEAMINFO structure containing SEAM
and system information such as Convertible Memory
Regions (CMRs) and number of CPUs and sockets.
The former is used here.
For details, see "Intel® Trust Domain Extensions - SEAM Loader (SEAMLDR)
Interface Specification" revision 343755-003.
Signed-off-by: Chao Gao <chao.gao@intel.com>
Tested-by: Farrah Chen <farrah.chen@intel.com>
---
v4:
- put seamldr_info on stack [Dave]
- improve changelogs to explain SEAMLDR.INFO and SEAMLDR.SEAMINFO [Dave]
- add SEAMLDR spec information in the changelog [Dave]
- add proper comments above ABI structure definition [Dave]
- add unused ABI structure fields rather than marking them as reserved
to better align with the specc [Dave] (I omitted "not used by kernel"
tags since there are 5-6 such fields and maintaining these tags would
be tedious.)
---
arch/x86/include/asm/seamldr.h | 36 +++++++++++++++++++++++++++++++++
arch/x86/virt/vmx/tdx/seamldr.c | 15 +++++++++++++-
2 files changed, 50 insertions(+), 1 deletion(-)
create mode 100644 arch/x86/include/asm/seamldr.h
diff --git a/arch/x86/include/asm/seamldr.h b/arch/x86/include/asm/seamldr.h
new file mode 100644
index 000000000000..954d850e34e3
--- /dev/null
+++ b/arch/x86/include/asm/seamldr.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_X86_SEAMLDR_H
+#define _ASM_X86_SEAMLDR_H
+
+#include <linux/types.h>
+
+/*
+ * This called the "SEAMLDR_INFO" data structure and is defined
+ * in "SEAM Loader (SEAMLDR) Interface Specification".
+ *
+ * The SEAMLDR.INFO documentation requires this to be aligned to a
+ * 256-byte boundary.
+ */
+struct seamldr_info {
+ u32 version;
+ u32 attributes;
+ u32 vendor_id;
+ u32 build_date;
+ u16 build_num;
+ u16 minor_version;
+ u16 major_version;
+ u16 update_version;
+ u32 acm_x2apicid;
+ u32 num_remaining_updates;
+ u8 seam_info[128];
+ u8 seam_ready;
+ u8 seam_debug;
+ u8 p_seam_ready;
+ u8 reserved[93];
+} __packed __aligned(256);
+
+static_assert(sizeof(struct seamldr_info) == 256);
+
+int seamldr_get_info(struct seamldr_info *seamldr_info);
+
+#endif /* _ASM_X86_SEAMLDR_H */
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
index fb59b3e2aa37..d17db3c0151e 100644
--- a/arch/x86/virt/vmx/tdx/seamldr.c
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -8,15 +8,20 @@
#include <linux/spinlock.h>
+#include <asm/seamldr.h>
+
#include "seamcall_internal.h"
+/* P-SEAMLDR SEAMCALL leaf function */
+#define P_SEAMLDR_INFO 0x8000000000000000
+
/*
* Serialize P-SEAMLDR calls since the hardware only allows a single CPU to
* interact with P-SEAMLDR simultaneously.
*/
static DEFINE_RAW_SPINLOCK(seamldr_lock);
-static __maybe_unused int seamldr_call(u64 fn, struct tdx_module_args *args)
+static int seamldr_call(u64 fn, struct tdx_module_args *args)
{
/*
* Serialize P-SEAMLDR calls and disable interrupts as the calls
@@ -25,3 +30,11 @@ static __maybe_unused int seamldr_call(u64 fn, struct tdx_module_args *args)
guard(raw_spinlock_irqsave)(&seamldr_lock);
return seamcall_prerr(fn, args);
}
+
+int seamldr_get_info(struct seamldr_info *seamldr_info)
+{
+ struct tdx_module_args args = { .rcx = slow_virt_to_phys(seamldr_info) };
+
+ return seamldr_call(P_SEAMLDR_INFO, &args);
+}
+EXPORT_SYMBOL_FOR_MODULES(seamldr_get_info, "tdx-host");
--
2.47.3
^ permalink raw reply related
* [PATCH v4 04/24] x86/virt/seamldr: Introduce a wrapper for P-SEAMLDR SEAMCALLs
From: Chao Gao @ 2026-02-12 14:35 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, binbin.wu,
tony.lindgren, Chao Gao, Farrah Chen, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin
In-Reply-To: <20260212143606.534586-1-chao.gao@intel.com>
The TDX architecture uses the "SEAMCALL" instruction to communicate with
SEAM mode software. Right now, the only SEAM mode software that the kernel
communicates with is the TDX module. But, there is actually another
component that runs in SEAM mode but it is separate from the TDX module:
the persistent SEAM loader or "P-SEAMLDR". Right now, the only component
that communicates with it is the BIOS which loads the TDX module itself at
boot. But, to support updating the TDX module, the kernel now needs to be
able to talk to it.
P-SEAMLDR SEAMCALLs differ from TDX Module SEAMCALLs in areas such as
concurrency requirements. Add a P-SEAMLDR wrapper to handle these
differences and prepare for implementing concrete functions.
Note that unlike P-SEAMLDR, there is also a non-persistent SEAM loader
("NP-SEAMLDR"). This is an authenticated code module (ACM) that is not
callable at runtime. Only BIOS launches it to load P-SEAMLDR at boot;
the kernel does not interact with it.
For details of P-SEAMLDR SEAMCALLs, see Intel® Trust Domain CPU
Architectural Extensions, Revision 343754-002, Chapter 2.3 "INSTRUCTION
SET REFERENCE".
Signed-off-by: Chao Gao <chao.gao@intel.com>
Tested-by: Farrah Chen <farrah.chen@intel.com>
Link: https://cdrdv2.intel.com/v1/dl/getContent/733582 # [1]
---
v4:
- Give more background about P-SEAMLDR in changelog [Dave]
- Don't handle P-SEAMLDR's "no_entropy" error [Dave]
- Assume current VMCS is preserved across P-SEAMLDR calls [Dave]
- I'm not adding Reviewed-by tags as the code has changed significantly.
v2:
- don't create a new, inferior framework to save/restore VMCS
- use human-friendly language, just "current VMCS" rather than
SDM term "current-VMCS pointer"
- don't mix guard() with goto
---
arch/x86/virt/vmx/tdx/Makefile | 2 +-
arch/x86/virt/vmx/tdx/seamldr.c | 27 +++++++++++++++++++++++++++
2 files changed, 28 insertions(+), 1 deletion(-)
create mode 100644 arch/x86/virt/vmx/tdx/seamldr.c
diff --git a/arch/x86/virt/vmx/tdx/Makefile b/arch/x86/virt/vmx/tdx/Makefile
index 90da47eb85ee..d1dbc5cc5697 100644
--- a/arch/x86/virt/vmx/tdx/Makefile
+++ b/arch/x86/virt/vmx/tdx/Makefile
@@ -1,2 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
-obj-y += seamcall.o tdx.o
+obj-y += seamcall.o seamldr.o tdx.o
diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
new file mode 100644
index 000000000000..fb59b3e2aa37
--- /dev/null
+++ b/arch/x86/virt/vmx/tdx/seamldr.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * P-SEAMLDR support for TDX Module management features like runtime updates
+ *
+ * Copyright (C) 2025 Intel Corporation
+ */
+#define pr_fmt(fmt) "seamldr: " fmt
+
+#include <linux/spinlock.h>
+
+#include "seamcall_internal.h"
+
+/*
+ * Serialize P-SEAMLDR calls since the hardware only allows a single CPU to
+ * interact with P-SEAMLDR simultaneously.
+ */
+static DEFINE_RAW_SPINLOCK(seamldr_lock);
+
+static __maybe_unused int seamldr_call(u64 fn, struct tdx_module_args *args)
+{
+ /*
+ * Serialize P-SEAMLDR calls and disable interrupts as the calls
+ * can be made from IRQ context.
+ */
+ guard(raw_spinlock_irqsave)(&seamldr_lock);
+ return seamcall_prerr(fn, args);
+}
--
2.47.3
^ permalink raw reply related
* [PATCH v4 03/24] coco/tdx-host: Expose TDX Module version
From: Chao Gao @ 2026-02-12 14:35 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, binbin.wu,
tony.lindgren, Chao Gao
In-Reply-To: <20260212143606.534586-1-chao.gao@intel.com>
For TDX Module updates, userspace needs to select compatible update
versions based on the current module version. This design delegates
module selection complexity to userspace because TDX Module update
policies are complex and version series are platform-specific.
For example, the 1.5.x series is for certain platform generations, while
the 2.0.x series is intended for others. And TDX Module 1.5.x may be
updated to 1.5.y but not to 1.5.y+1.
Expose the TDX Module version to userspace via sysfs to aid module
selection. Since the TDX faux device will drive module updates, expose
the version as its attribute.
One bonus of exposing TDX Module version via sysfs is: TDX Module
version information remains available even after dmesg logs are cleared.
== Background ==
The "faux device + device attribute" approach compares to other update
mechanisms as follows:
1. AMD SEV leverages an existing PCI device for the PSP to expose
metadata. TDX uses a faux device as it doesn't have PCI device
in its architecture.
2. Microcode uses per-CPU virtual devices to report microcode revisions
because CPUs can have different revisions. But, there is only a
single TDX Module, so exposing the TDX Module version through a global
TDX faux device is appropriate
3. ARM's CCA implementation isn't in-tree yet, but will likely follow a
similar faux device approach [1], though it's unclear whether they need
to expose firmware version information
Signed-off-by: Chao Gao <chao.gao@intel.com>
Reviewed-by: Binbin Wu <binbin.wu@linux.intel.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
Reviewed-by: Xu Yilun <yilun.xu@linux.intel.com>
Link: https://lore.kernel.org/all/2025073035-bulginess-rematch-b92e@gregkh/ # [1]
---
v4:
- collect reviews
- Explain other version exposure implementations and why tdx's approach differs
from them
v3:
- Justify the sysfs ABI choice and expand background on other CoCo
implementations.
---
.../ABI/testing/sysfs-devices-faux-tdx-host | 6 +++++
drivers/virt/coco/tdx-host/tdx-host.c | 26 ++++++++++++++++++-
2 files changed, 31 insertions(+), 1 deletion(-)
create mode 100644 Documentation/ABI/testing/sysfs-devices-faux-tdx-host
diff --git a/Documentation/ABI/testing/sysfs-devices-faux-tdx-host b/Documentation/ABI/testing/sysfs-devices-faux-tdx-host
new file mode 100644
index 000000000000..901abbae2e61
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-devices-faux-tdx-host
@@ -0,0 +1,6 @@
+What: /sys/devices/faux/tdx_host/version
+Contact: linux-coco@lists.linux.dev
+Description: (RO) Report the version of the loaded TDX Module. The TDX Module
+ version is formatted as x.y.z, where "x" is the major version,
+ "y" is the minor version and "z" is the update version. Versions
+ are used for bug reporting, TDX Module updates and etc.
diff --git a/drivers/virt/coco/tdx-host/tdx-host.c b/drivers/virt/coco/tdx-host/tdx-host.c
index c77885392b09..0424933b2560 100644
--- a/drivers/virt/coco/tdx-host/tdx-host.c
+++ b/drivers/virt/coco/tdx-host/tdx-host.c
@@ -8,6 +8,7 @@
#include <linux/device/faux.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
+#include <linux/sysfs.h>
#include <asm/cpu_device_id.h>
#include <asm/tdx.h>
@@ -18,6 +19,29 @@ static const struct x86_cpu_id tdx_host_ids[] = {
};
MODULE_DEVICE_TABLE(x86cpu, tdx_host_ids);
+static ssize_t version_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ const struct tdx_sys_info *tdx_sysinfo = tdx_get_sysinfo();
+ const struct tdx_sys_info_version *ver;
+
+ if (!tdx_sysinfo)
+ return -ENXIO;
+
+ ver = &tdx_sysinfo->version;
+
+ return sysfs_emit(buf, "%u.%u.%02u\n", ver->major_version,
+ ver->minor_version,
+ ver->update_version);
+}
+static DEVICE_ATTR_RO(version);
+
+static struct attribute *tdx_host_attrs[] = {
+ &dev_attr_version.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(tdx_host);
+
static struct faux_device *fdev;
static int __init tdx_host_init(void)
@@ -25,7 +49,7 @@ 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);
+ fdev = faux_device_create_with_groups(KBUILD_MODNAME, NULL, NULL, tdx_host_groups);
if (!fdev)
return -ENODEV;
--
2.47.3
^ permalink raw reply related
* [PATCH v4 02/24] coco/tdx-host: Introduce a "tdx_host" device
From: Chao Gao @ 2026-02-12 14:35 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, binbin.wu,
tony.lindgren, Chao Gao, Jonathan Cameron, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, H. Peter Anvin
In-Reply-To: <20260212143606.534586-1-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 as 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: 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>
Link: https://lore.kernel.org/all/2025073035-bulginess-rematch-b92e@gregkh/ # [1]
---
v3:
- add Jonathan's reviewed-by
- add tdx_get_sysinfo() in module_init() to ensure the TDX Module is up
and running.
- note in the changelog that both AMD and ARM have devices for coco
---
arch/x86/virt/vmx/tdx/tdx.c | 2 +-
drivers/virt/coco/Kconfig | 2 ++
drivers/virt/coco/Makefile | 1 +
drivers/virt/coco/tdx-host/Kconfig | 10 +++++++
drivers/virt/coco/tdx-host/Makefile | 1 +
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/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index ddcc1a8c743f..b65b2a609e81 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1435,7 +1435,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/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/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/Kconfig b/drivers/virt/coco/tdx-host/Kconfig
new file mode 100644
index 000000000000..e58bad148a35
--- /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/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/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.47.3
^ permalink raw reply related
* [PATCH v4 01/24] x86/virt/tdx: Move low level SEAMCALL helpers out of <asm/tdx.h>
From: Chao Gao @ 2026-02-12 14:35 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, binbin.wu,
tony.lindgren, Chao Gao, Thomas Gleixner, Ingo Molnar,
Borislav Petkov, H. Peter Anvin
In-Reply-To: <20260212143606.534586-1-chao.gao@intel.com>
From: Kai Huang <kai.huang@intel.com>
TDX host core code implements three seamcall*() helpers to make SEAMCALL
to the TDX module. Currently, they are implemented in <asm/tdx.h> and
are exposed to other kernel code which includes <asm/tdx.h>.
However, other than the TDX host core, seamcall*() are not expected to
be used by other kernel code directly. For instance, for all SEAMCALLs
that are used by KVM, the TDX host core exports a wrapper function for
each of them.
Move seamcall*() and related code out of <asm/tdx.h> and make them only
visible to TDX host core.
Since TDX host core tdx.c is already very heavy, don't put low level
seamcall*() code there but to a new dedicated "seamcall.h". Also,
currently tdx.c has seamcall_prerr*() helpers which additionally print
error message when calling seamcall*() fails. Move them to "seamcall.h"
as well. In such way all low level SEAMCALL helpers are in a dedicated
place, which is much more readable.
Copy the copyright notice from the original files and consolidate the
date ranges to:
Copyright (C) 2021-2023 Intel Corporation
Signed-off-by: Kai Huang <kai.huang@intel.com>
Signed-off-by: Chao Gao <chao.gao@intel.com>
Reviewed-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Reviewed-by: Binbin Wu <binbin.wu@linux.intel.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
Acked-by: Dave Hansen <dave.hansen@linux.intel.com>
---
v4:
- Collect reviews
- add "internal" to the new header file [Dave]
- document the scope of the new header file [Dave]
- correct the copyright notice [Dave]
v2:
- new
---
arch/x86/include/asm/tdx.h | 47 ----------
arch/x86/virt/vmx/tdx/seamcall_internal.h | 107 ++++++++++++++++++++++
arch/x86/virt/vmx/tdx/tdx.c | 47 +---------
3 files changed, 109 insertions(+), 92 deletions(-)
create mode 100644 arch/x86/virt/vmx/tdx/seamcall_internal.h
diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h
index 6b338d7f01b7..cb2219302dfc 100644
--- a/arch/x86/include/asm/tdx.h
+++ b/arch/x86/include/asm/tdx.h
@@ -97,54 +97,7 @@ static inline long tdx_kvm_hypercall(unsigned int nr, unsigned long p1,
#endif /* CONFIG_INTEL_TDX_GUEST && CONFIG_KVM_GUEST */
#ifdef CONFIG_INTEL_TDX_HOST
-u64 __seamcall(u64 fn, struct tdx_module_args *args);
-u64 __seamcall_ret(u64 fn, struct tdx_module_args *args);
-u64 __seamcall_saved_ret(u64 fn, struct tdx_module_args *args);
void tdx_init(void);
-
-#include <linux/preempt.h>
-#include <asm/archrandom.h>
-#include <asm/processor.h>
-
-typedef u64 (*sc_func_t)(u64 fn, struct tdx_module_args *args);
-
-static __always_inline u64 __seamcall_dirty_cache(sc_func_t func, u64 fn,
- struct tdx_module_args *args)
-{
- lockdep_assert_preemption_disabled();
-
- /*
- * SEAMCALLs are made to the TDX module and can generate dirty
- * cachelines of TDX private memory. Mark cache state incoherent
- * so that the cache can be flushed during kexec.
- *
- * This needs to be done before actually making the SEAMCALL,
- * because kexec-ing CPU could send NMI to stop remote CPUs,
- * in which case even disabling IRQ won't help here.
- */
- this_cpu_write(cache_state_incoherent, true);
-
- return func(fn, args);
-}
-
-static __always_inline u64 sc_retry(sc_func_t func, u64 fn,
- struct tdx_module_args *args)
-{
- int retry = RDRAND_RETRY_LOOPS;
- u64 ret;
-
- do {
- preempt_disable();
- ret = __seamcall_dirty_cache(func, fn, args);
- preempt_enable();
- } while (ret == TDX_RND_NO_ENTROPY && --retry);
-
- return ret;
-}
-
-#define seamcall(_fn, _args) sc_retry(__seamcall, (_fn), (_args))
-#define seamcall_ret(_fn, _args) sc_retry(__seamcall_ret, (_fn), (_args))
-#define seamcall_saved_ret(_fn, _args) sc_retry(__seamcall_saved_ret, (_fn), (_args))
int tdx_cpu_enable(void);
int tdx_enable(void);
const char *tdx_dump_mce_info(struct mce *m);
diff --git a/arch/x86/virt/vmx/tdx/seamcall_internal.h b/arch/x86/virt/vmx/tdx/seamcall_internal.h
new file mode 100644
index 000000000000..70c3cf1f4adc
--- /dev/null
+++ b/arch/x86/virt/vmx/tdx/seamcall_internal.h
@@ -0,0 +1,107 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * SEAMCALL utilities for TDX host-side operations.
+ *
+ * Provides convenient wrappers around SEAMCALL assembly with retry logic,
+ * error reporting and cache coherency tracking.
+ *
+ * Copyright (C) 2021-2023 Intel Corporation
+ */
+
+#ifndef _X86_VIRT_SEAMCALL_INTERNAL_H
+#define _X86_VIRT_SEAMCALL_INTERNAL_H
+
+#include <linux/printk.h>
+#include <linux/types.h>
+#include <asm/archrandom.h>
+#include <asm/processor.h>
+#include <asm/tdx.h>
+
+u64 __seamcall(u64 fn, struct tdx_module_args *args);
+u64 __seamcall_ret(u64 fn, struct tdx_module_args *args);
+u64 __seamcall_saved_ret(u64 fn, struct tdx_module_args *args);
+
+typedef u64 (*sc_func_t)(u64 fn, struct tdx_module_args *args);
+
+static __always_inline u64 __seamcall_dirty_cache(sc_func_t func, u64 fn,
+ struct tdx_module_args *args)
+{
+ lockdep_assert_preemption_disabled();
+
+ /*
+ * SEAMCALLs are made to the TDX module and can generate dirty
+ * cachelines of TDX private memory. Mark cache state incoherent
+ * so that the cache can be flushed during kexec.
+ *
+ * This needs to be done before actually making the SEAMCALL,
+ * because kexec-ing CPU could send NMI to stop remote CPUs,
+ * in which case even disabling IRQ won't help here.
+ */
+ this_cpu_write(cache_state_incoherent, true);
+
+ return func(fn, args);
+}
+
+static __always_inline u64 sc_retry(sc_func_t func, u64 fn,
+ struct tdx_module_args *args)
+{
+ int retry = RDRAND_RETRY_LOOPS;
+ u64 ret;
+
+ do {
+ ret = func(fn, args);
+ } while (ret == TDX_RND_NO_ENTROPY && --retry);
+
+ return ret;
+}
+
+#define seamcall(_fn, _args) sc_retry(__seamcall, (_fn), (_args))
+#define seamcall_ret(_fn, _args) sc_retry(__seamcall_ret, (_fn), (_args))
+#define seamcall_saved_ret(_fn, _args) sc_retry(__seamcall_saved_ret, (_fn), (_args))
+
+typedef void (*sc_err_func_t)(u64 fn, u64 err, struct tdx_module_args *args);
+
+static inline void seamcall_err(u64 fn, u64 err, struct tdx_module_args *args)
+{
+ pr_err("SEAMCALL (0x%016llx) failed: 0x%016llx\n", fn, err);
+}
+
+static inline void seamcall_err_ret(u64 fn, u64 err,
+ struct tdx_module_args *args)
+{
+ seamcall_err(fn, err, args);
+ pr_err("RCX 0x%016llx RDX 0x%016llx R08 0x%016llx\n",
+ args->rcx, args->rdx, args->r8);
+ pr_err("R09 0x%016llx R10 0x%016llx R11 0x%016llx\n",
+ args->r9, args->r10, args->r11);
+}
+
+static __always_inline int sc_retry_prerr(sc_func_t func,
+ sc_err_func_t err_func,
+ u64 fn, struct tdx_module_args *args)
+{
+ u64 sret = sc_retry(func, fn, args);
+
+ if (sret == TDX_SUCCESS)
+ return 0;
+
+ if (sret == TDX_SEAMCALL_VMFAILINVALID)
+ return -ENODEV;
+
+ if (sret == TDX_SEAMCALL_GP)
+ return -EOPNOTSUPP;
+
+ if (sret == TDX_SEAMCALL_UD)
+ return -EACCES;
+
+ err_func(fn, sret, args);
+ return -EIO;
+}
+
+#define seamcall_prerr(__fn, __args) \
+ sc_retry_prerr(__seamcall, seamcall_err, (__fn), (__args))
+
+#define seamcall_prerr_ret(__fn, __args) \
+ sc_retry_prerr(__seamcall_ret, seamcall_err_ret, (__fn), (__args))
+
+#endif /* _X86_VIRT_SEAMCALL_INTERNAL_H */
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 5ce4ebe99774..ddcc1a8c743f 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -39,6 +39,8 @@
#include <asm/cpu_device_id.h>
#include <asm/processor.h>
#include <asm/mce.h>
+
+#include "seamcall_internal.h"
#include "tdx.h"
static u32 tdx_global_keyid __ro_after_init;
@@ -59,51 +61,6 @@ static LIST_HEAD(tdx_memlist);
static struct tdx_sys_info tdx_sysinfo;
-typedef void (*sc_err_func_t)(u64 fn, u64 err, struct tdx_module_args *args);
-
-static inline void seamcall_err(u64 fn, u64 err, struct tdx_module_args *args)
-{
- pr_err("SEAMCALL (0x%016llx) failed: 0x%016llx\n", fn, err);
-}
-
-static inline void seamcall_err_ret(u64 fn, u64 err,
- struct tdx_module_args *args)
-{
- seamcall_err(fn, err, args);
- pr_err("RCX 0x%016llx RDX 0x%016llx R08 0x%016llx\n",
- args->rcx, args->rdx, args->r8);
- pr_err("R09 0x%016llx R10 0x%016llx R11 0x%016llx\n",
- args->r9, args->r10, args->r11);
-}
-
-static __always_inline int sc_retry_prerr(sc_func_t func,
- sc_err_func_t err_func,
- u64 fn, struct tdx_module_args *args)
-{
- u64 sret = sc_retry(func, fn, args);
-
- if (sret == TDX_SUCCESS)
- return 0;
-
- if (sret == TDX_SEAMCALL_VMFAILINVALID)
- return -ENODEV;
-
- if (sret == TDX_SEAMCALL_GP)
- return -EOPNOTSUPP;
-
- if (sret == TDX_SEAMCALL_UD)
- return -EACCES;
-
- err_func(fn, sret, args);
- return -EIO;
-}
-
-#define seamcall_prerr(__fn, __args) \
- sc_retry_prerr(__seamcall, seamcall_err, (__fn), (__args))
-
-#define seamcall_prerr_ret(__fn, __args) \
- sc_retry_prerr(__seamcall_ret, seamcall_err_ret, (__fn), (__args))
-
/*
* Do the module global initialization once and return its result.
* It can be done on any cpu. It's always called with interrupts
--
2.47.3
^ permalink raw reply related
* [PATCH v4 00/24] Runtime TDX Module update support
From: Chao Gao @ 2026-02-12 14:35 UTC (permalink / raw)
To: linux-coco, linux-kernel, kvm, x86, linux-doc
Cc: reinette.chatre, ira.weiny, kai.huang, dan.j.williams, yilun.xu,
sagis, vannapurve, paulmck, nik.borisov, zhenzhong.duan, seanjc,
rick.p.edgecombe, kas, dave.hansen, vishal.l.verma, binbin.wu,
tony.lindgren, Chao Gao, Borislav Petkov, H. Peter Anvin,
Ingo Molnar, Jonathan Corbet, Paolo Bonzini, Thomas Gleixner
Hi Reviewers,
With this posting, I'm hoping to collect more Reviewed-by or Acked-by tags.
In the last round of review, the first 6 patches received thorough reviews
from Dave and others. I believe they are in good shape after incorporating
all feedback, so I'm hoping to get more reviews on patch 7 and beyond.
Kai, please take a look at patch 10, which has been updated per your
suggestions.
Note that like v3, this v4 is not based on Sean's VMXON series to make this
series more reviewable.
For transparency, I should note that I used an Intel-operated AI tool to
help proofread this cover-letter and commit messages.
Changelog:
v3->v4:
- Drop INTEL_TDX_MODULE_UPDATE kconfig [Dave]
- Drop two unnecessary cleanup patches [Dave]
- Drop VMCS save/restore across P-SEAMLDR calls [Dave]
(We are pursuing microcode changes to preserve the current VMCS
across P-SEAMLDR calls. Until then, we still need the last patch in
this series which wraps P-SEAMLDR calls with VMCS save/restore for
testing)
- Don't handle P-SEAMLDR's "no_entropy" error [Dave]
- Put seamldr_info on stack and change seamldr attributes permission
to 0x400 [Dave]
- Correct copyright notices [Dave]
- Document TDX Module updates in tdx.rst
- Improve changelogs and comments [Dave, Kai]
- Rename the TDX Module update sysfs directory from "seamldr_upload" to
"tdx_module" [Cedric]
- Merge the patch that support 16KB sigstruct to a previous patch [Kai]
- Update tdx_blob definition to match this series' implementation [Kai]
- Remove tdx_blob checksum verification as it is really optional
- Don't support update canceling [Yilun]
- Other minor code changes and changelog improvements
- Collect review tags from Tony and Yilun
- v3: https://lore.kernel.org/kvm/20260123145645.90444-1-chao.gao@intel.com/
This series adds support for runtime TDX Module updates that preserve
running TDX guests. It is also available at:
https://github.com/gaochaointel/linux-dev/commits/tdx-module-updates-v4/
== Background ==
Intel TDX isolates Trusted Domains (TDs), or confidential guests, from the
host. A key component of Intel TDX is the TDX Module, which enforces
security policies to protect the memory and CPU states of TDs from the
host. However, the TDX Module is software that require updates.
== Problems ==
Currently, the TDX Module is loaded by the BIOS at boot time, and the only
way to update it is through a reboot, which results in significant system
downtime. Users expect the TDX Module to be updatable at runtime without
disrupting TDX guests.
== Solution ==
On TDX platforms, P-SEAMLDR[1] is a component within the protected SEAM
range. It is loaded by the BIOS and provides the host with functions to
install a TDX Module at runtime.
Implement a TDX Module update facility via the fw_upload mechanism. Given
that there is variability in which module update to load based on features,
fix levels, and potentially reloading the same version for error recovery
scenarios, the explicit userspace chosen payload flexibility of fw_upload
is attractive.
This design allows the kernel to accept a bitstream instead of loading a
named file from the filesystem, as the module selection and policy
enforcement for TDX Modules are quite complex (see more in patch 8). By
doing so, much of this complexity is shifted out of the kernel. The kernel
need to expose information, such as the TDX Module version, to userspace.
Userspace must understand the TDX Module versioning scheme and update
policy to select the appropriate TDX Module (see "TDX Module Versioning"
below).
In the unlikely event the update fails, for example userspace picks an
incompatible update image, or the image is otherwise corrupted, all TDs
will experience SEAMCALL failures and be killed. The recovery of TD
operation from that event requires a reboot.
Given there is no mechanism to quiesce SEAMCALLs, the TDs themselves must
pause execution over an update. The most straightforward way to meet the
'pause TDs while update executes' constraint is to run the update in
stop_machine() context. All other evaluated solutions export more
complexity to KVM, or exports more fragility to userspace.
== How to test this series ==
First, load kvm-intel.ko and tdx-host.ko if they haven't been loaded:
# modprobe -r kvm_intel
# modprobe kvm_intel tdx=1
# modprobe tdx-host
Then, use the userspace tool below to select the appropriate TDX module and
install it via the interfaces exposed by this series:
# git clone https://github.com/intel/tdx-module-binaries
# cd tdx-module-binaries
# python version_select_and_load.py --update
this version changes the firmware directory name from seamldr_upload to
tdx_module, so, below change should be applied to version_select_and_load.py:
diff --git a/version_select_and_load.py b/version_select_and_load.py
index 2193bd8..6a3b604 100644
--- a/version_select_and_load.py
+++ b/version_select_and_load.py
@@ -38,7 +38,7 @@ except ImportError:
print("Error: cpuid module is not installed. Please install it using 'pip install cpuid'")
sys.exit(1)
-FIRMWARE_PATH = "/sys/class/firmware/seamldr_upload"
+FIRMWARE_PATH = "/sys/class/firmware/tdx_module"
MODULE_PATH = "/sys/devices/faux/tdx_host"
SEAMLDR_PATH = "/sys/devices/faux/tdx_host/seamldr"
allow_debug = False
== Other information relevant to Runtime TDX Module updates ==
=== TDX Module versioning ===
Each TDX Module is assigned a version number x.y.z, where x represents the
"major" version, y the "minor" version, and z the "update" version.
Runtime TDX Module updates are restricted to Z-stream releases.
Note that Z-stream releases do not necessarily guarantee compatibility. A
new release may not be compatible with all previous versions. To address this,
Intel provides a separate file containing compatibility information, which
specifies the minimum module version required for a particular update. This
information is referenced by the tool to determine if two modules are
compatible.
=== TCB Stability ===
Updates change the TCB as viewed by attestation reports. In TDX there is
a distinction between launch-time version and current version where
runtime TDX Module updates cause that latter version number to change,
subject to Z-stream constraints.
The concern that a malicious host may attack confidential VMs by loading
insecure updates was addressed by Alex in [3]. Similarly, the scenario
where some "theoretical paranoid tenant" in the cloud wants to audit
updates and stop trusting the host after updates until audit completion
was also addressed in [4]. Users not in the cloud control the host machine
and can manage updates themselves, so they don't have these concerns.
See more about the implications of current TCB version changes in
attestation as summarized by Dave in [5].
=== TDX Module Distribution Model ===
At a high level, Intel publishes all TDX Modules on the github [2], along
with a mapping_file.json which documents the compatibility information
about each TDX Module and a userspace tool to install the TDX Module. OS
vendors can package these modules and distribute them. Administrators
install the package and use the tool to select the appropriate TDX Module
and install it via the interfaces exposed by this series.
[1]: https://cdrdv2.intel.com/v1/dl/getContent/733584
[2]: https://github.com/intel/tdx-module-binaries
[3]: https://lore.kernel.org/all/665c5ae0-4b7c-4852-8995-255adf7b3a2f@amazon.com/
[4]: https://lore.kernel.org/all/5d1da767-491b-4077-b472-2cc3d73246d6@amazon.com/
[5]: https://lore.kernel.org/all/94d6047e-3b7c-4bc1-819c-85c16ff85abf@intel.com/
Chao Gao (23):
coco/tdx-host: Introduce a "tdx_host" device
coco/tdx-host: Expose TDX Module version
x86/virt/seamldr: Introduce a wrapper for P-SEAMLDR SEAMCALLs
x86/virt/seamldr: Retrieve P-SEAMLDR information
coco/tdx-host: Expose P-SEAMLDR information via sysfs
coco/tdx-host: Implement firmware upload sysfs ABI for TDX Module
updates
x86/virt/seamldr: Block TDX Module updates if any CPU is offline
x86/virt/seamldr: Check update limit before TDX Module updates
x86/virt/seamldr: Allocate and populate a module update request
x86/virt/seamldr: Introduce skeleton for TDX Module updates
x86/virt/seamldr: Abort updates if errors occurred midway
x86/virt/seamldr: Shut down the current TDX module
x86/virt/tdx: Reset software states during TDX Module shutdown
x86/virt/seamldr: Log TDX Module update failures
x86/virt/seamldr: Install a new TDX Module
x86/virt/seamldr: Do TDX per-CPU initialization after updates
x86/virt/tdx: Restore TDX Module state
x86/virt/tdx: Update tdx_sysinfo and check features post-update
x86/virt/tdx: Enable TDX Module runtime updates
x86/virt/tdx: Avoid updates during update-sensitive operations
coco/tdx-host: Document TDX Module update expectations
x86/virt/tdx: Document TDX Module updates
[NOT-FOR-REVIEW] x86/virt/seamldr: Save and restore current VMCS
Kai Huang (1):
x86/virt/tdx: Move low level SEAMCALL helpers out of <asm/tdx.h>
.../ABI/testing/sysfs-devices-faux-tdx-host | 82 ++++
Documentation/arch/x86/tdx.rst | 34 ++
arch/x86/include/asm/seamldr.h | 37 ++
arch/x86/include/asm/special_insns.h | 22 ++
arch/x86/include/asm/tdx.h | 66 +---
arch/x86/include/asm/tdx_global_metadata.h | 5 +
arch/x86/kvm/vmx/tdx_errno.h | 2 -
arch/x86/virt/vmx/tdx/Makefile | 2 +-
arch/x86/virt/vmx/tdx/seamcall_internal.h | 107 ++++++
arch/x86/virt/vmx/tdx/seamldr.c | 360 ++++++++++++++++++
arch/x86/virt/vmx/tdx/tdx.c | 153 +++++---
arch/x86/virt/vmx/tdx/tdx.h | 11 +-
arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 15 +
drivers/virt/coco/Kconfig | 2 +
drivers/virt/coco/Makefile | 1 +
drivers/virt/coco/tdx-host/Kconfig | 12 +
drivers/virt/coco/tdx-host/Makefile | 1 +
drivers/virt/coco/tdx-host/tdx-host.c | 240 ++++++++++++
18 files changed, 1050 insertions(+), 102 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-devices-faux-tdx-host
create mode 100644 arch/x86/include/asm/seamldr.h
create mode 100644 arch/x86/virt/vmx/tdx/seamcall_internal.h
create mode 100644 arch/x86/virt/vmx/tdx/seamldr.c
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
--
2.47.3
^ permalink raw reply related
* Re: [PATCH 1/5] dma-mapping: avoid random addr value print out on error path
From: Jiri Pirko @ 2026-02-12 12:52 UTC (permalink / raw)
To: Marek Szyprowski
Cc: dri-devel, linaro-mm-sig, iommu, linux-media, sumit.semwal,
benjamin.gaignard, Brian.Starkey, jstultz, tjmercier,
christian.koenig, 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: <621783e8-d77d-4f29-bda0-ef487dd27b5b@samsung.com>
Thu, Feb 12, 2026 at 12:03:49PM +0100, m.szyprowski@samsung.com wrote:
>On 09.02.2026 16:38, Jiri Pirko wrote:
>> From: Jiri Pirko <jiri@nvidia.com>
>>
>> dma_addr is unitialized in dma_direct_map_phys() when swiotlb is forced
>> and DMA_ATTR_MMIO is set which leads to random value print out in
>> warning. Fix that by just returning DMA_MAPPING_ERROR.
>>
>> Fixes: e53d29f957b3 ("dma-mapping: convert dma_direct_*map_page to be phys_addr_t based")
>> Signed-off-by: Jiri Pirko <jiri@nvidia.com>
>
>I will take this patch when v7.0-rc1 is out, as this fix definitely has
>to be applied regardless of the discussion about the remaining patches.
Makes sense. Thanks!
^ permalink raw reply
* Re: [PATCH 1/5] dma-mapping: avoid random addr value print out on error path
From: Marek Szyprowski @ 2026-02-12 11:03 UTC (permalink / raw)
To: Jiri Pirko, dri-devel, linaro-mm-sig, iommu, linux-media
Cc: sumit.semwal, benjamin.gaignard, Brian.Starkey, jstultz,
tjmercier, christian.koenig, 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: <20260209153809.250835-2-jiri@resnulli.us>
On 09.02.2026 16:38, Jiri Pirko wrote:
> From: Jiri Pirko <jiri@nvidia.com>
>
> dma_addr is unitialized in dma_direct_map_phys() when swiotlb is forced
> and DMA_ATTR_MMIO is set which leads to random value print out in
> warning. Fix that by just returning DMA_MAPPING_ERROR.
>
> Fixes: e53d29f957b3 ("dma-mapping: convert dma_direct_*map_page to be phys_addr_t based")
> Signed-off-by: Jiri Pirko <jiri@nvidia.com>
I will take this patch when v7.0-rc1 is out, as this fix definitely has
to be applied regardless of the discussion about the remaining patches.
> ---
> kernel/dma/direct.h | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/kernel/dma/direct.h b/kernel/dma/direct.h
> index da2fadf45bcd..62f0d9d0ba02 100644
> --- a/kernel/dma/direct.h
> +++ b/kernel/dma/direct.h
> @@ -88,7 +88,7 @@ static inline dma_addr_t dma_direct_map_phys(struct device *dev,
>
> if (is_swiotlb_force_bounce(dev)) {
> if (attrs & DMA_ATTR_MMIO)
> - goto err_overflow;
> + return DMA_MAPPING_ERROR;
>
> return swiotlb_map(dev, phys, size, dir, attrs);
> }
Best regards
--
Marek Szyprowski, PhD
Samsung R&D Institute Poland
^ permalink raw reply
* Re: [PATCH v1 3/3] virt: tdx-guest: Increase Quote buffer size to 128KB
From: Kuppuswamy Sathyanarayanan @ 2026-02-11 21:13 UTC (permalink / raw)
To: dan.j.williams, Kiryl Shutsemau
Cc: Dave Hansen, Rick Edgecombe, x86, linux-kernel, linux-coco
In-Reply-To: <698cef4964e8_8c32100a3@dwillia2-mobl4.notmuch>
Hi Dan,
On 2/11/2026 1:06 PM, dan.j.williams@intel.com wrote:
> Kuppuswamy Sathyanarayanan wrote:
>> Hi Kiryl,
>>
>> Thanks for the review!
>>
>> On 2/11/2026 3:17 AM, Kiryl Shutsemau wrote:
>>> On Tue, Feb 10, 2026 at 04:17:12PM -0800, Kuppuswamy Sathyanarayanan wrote:
>>>> Intel platforms are transitioning from traditional SGX-based
>>>> attestation toward DICE-based attestation as part of a broader move
>>>> toward open and standardized attestation models. DICE enables layered
>>>> and extensible attestation, where evidence is accumulated across
>>>> multiple boot stages.
>>>>
>>>> With SGX-based attestation, Quote sizes are typically under 8KB, as the
>>>> payload consists primarily of Quote data and a small certificate bundle.
>>>> Existing TDX guest code sizes the Quote buffer accordingly.
>>>>
>>>> DICE-based attestation produces significantly larger Quotes due to the
>>>> inclusion of evidence (certificate chains) from multiple boot layers.
>>>> The cumulative Quote size can reach approximately 100KB.
>>>>
>>>> Increase GET_QUOTE_BUF_SIZE to 128KB to ensure sufficient buffer
>>>> capacity for DICE-based Quote payloads.
>>>
>>> It worth noting that it requires guest physically-contiguous memory.
>>>
>>> Single order-5 allocation is not that bad as long as the driver
>>> initialized during the boot.
>>
>> Good point! We can add following to the commit log:
>>
>> The Quote buffer requires guest physically-contiguous memory and is
>> allocated once during driver initialization at boot time, where an
>> order-5 allocation (128KB) is expected to succeed reliably.
>
> That is good feedback. I would rather not restart the timer on the
> linux-next exposure with a rebase to add that. I think in this case it
> is sufficient that the commit has a link back to this discussion:
>
> Link: https://patch.msgid.link/20260211001712.1531955-4-sathyanarayanan.kuppuswamy@linux.intel.com
>
> However, when the the patch to add the link to the documentation for the
> CBOR Web Token schema is ready, do take the opportunity to also add a
> patch commenting about the order-5 allocation risk to
> GET_QUOTE_BUF_SIZE.
Sounds good. Once the CWT documentation is ready, I will send a follow-up
patch that includes both the documentation link and the order-5 allocation
comment.
>
> Later, when / if these objects start to get into order-10+ allocations
> for PQC etc, a scatter-gather mechanism will need to be considered.
--
Sathyanarayanan Kuppuswamy
Linux Kernel Developer
^ permalink raw reply
* Re: [PATCH v1 3/3] virt: tdx-guest: Increase Quote buffer size to 128KB
From: dan.j.williams @ 2026-02-11 21:06 UTC (permalink / raw)
To: Kuppuswamy Sathyanarayanan, Kiryl Shutsemau
Cc: Dan Williams, Dave Hansen, Rick Edgecombe, x86, linux-kernel,
linux-coco
In-Reply-To: <6983882c-2c00-42f4-924b-5fb3619840be@linux.intel.com>
Kuppuswamy Sathyanarayanan wrote:
> Hi Kiryl,
>
> Thanks for the review!
>
> On 2/11/2026 3:17 AM, Kiryl Shutsemau wrote:
> > On Tue, Feb 10, 2026 at 04:17:12PM -0800, Kuppuswamy Sathyanarayanan wrote:
> >> Intel platforms are transitioning from traditional SGX-based
> >> attestation toward DICE-based attestation as part of a broader move
> >> toward open and standardized attestation models. DICE enables layered
> >> and extensible attestation, where evidence is accumulated across
> >> multiple boot stages.
> >>
> >> With SGX-based attestation, Quote sizes are typically under 8KB, as the
> >> payload consists primarily of Quote data and a small certificate bundle.
> >> Existing TDX guest code sizes the Quote buffer accordingly.
> >>
> >> DICE-based attestation produces significantly larger Quotes due to the
> >> inclusion of evidence (certificate chains) from multiple boot layers.
> >> The cumulative Quote size can reach approximately 100KB.
> >>
> >> Increase GET_QUOTE_BUF_SIZE to 128KB to ensure sufficient buffer
> >> capacity for DICE-based Quote payloads.
> >
> > It worth noting that it requires guest physically-contiguous memory.
> >
> > Single order-5 allocation is not that bad as long as the driver
> > initialized during the boot.
>
> Good point! We can add following to the commit log:
>
> The Quote buffer requires guest physically-contiguous memory and is
> allocated once during driver initialization at boot time, where an
> order-5 allocation (128KB) is expected to succeed reliably.
That is good feedback. I would rather not restart the timer on the
linux-next exposure with a rebase to add that. I think in this case it
is sufficient that the commit has a link back to this discussion:
Link: https://patch.msgid.link/20260211001712.1531955-4-sathyanarayanan.kuppuswamy@linux.intel.com
However, when the the patch to add the link to the documentation for the
CBOR Web Token schema is ready, do take the opportunity to also add a
patch commenting about the order-5 allocation risk to
GET_QUOTE_BUF_SIZE.
Later, when / if these objects start to get into order-10+ allocations
for PQC etc, a scatter-gather mechanism will need to be considered.
^ permalink raw reply
* Re: [PATCH v1 1/3] virt: tsm: Document size limits for outblob attributes
From: dan.j.williams @ 2026-02-11 20:48 UTC (permalink / raw)
To: Kuppuswamy Sathyanarayanan, dan.j.williams, Kirill A . Shutemov
Cc: Dave Hansen, Rick Edgecombe, x86, linux-kernel, linux-coco
In-Reply-To: <163b74e8-eede-4872-9319-047db25c0050@linux.intel.com>
Kuppuswamy Sathyanarayanan wrote:
> Hi Dan,
>
> On 2/10/2026 6:15 PM, dan.j.williams@intel.com wrote:
> > Kuppuswamy Sathyanarayanan wrote:
> >> The configfs-tsm-report interface can fail with -EFBIG when the
> >> attestation report generated by a TSM provider exceeds internal
> >> maximums (TSM_REPORT_OUTBLOB_MAX). However, this error condition and
> >> its handling are not currently documented in the ABI.
> >>
> >> Userspace tools need to understand how to interpret various error
> >> conditions when reading attestation reports.
> >>
> >> Document that reads fail with -EFBIG when reports exceed size limits,
> >> with guidance on how to resolve them.
> >>
> >> Reviewed-by: Fang Peter <peter.fang@intel.com>
> >> Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
> >
> > Looks good, I will change the subject to:
> >
> > "configfs-tsm-report: Document size limits for outblob attributes"
>
>
> Thanks for the review! Are you planning to apply this with the updated
> subject line, or would you like me to send a v2 with the change?
Given the late date I went ahead and made the change and pushed it out
to start soaking in linux-next:
https://git.kernel.org/pub/scm/linux/kernel/git/devsec/tsm.git/log/?h=next
^ permalink raw reply
* Re: [PATCH v1 3/3] virt: tdx-guest: Increase Quote buffer size to 128KB
From: Kuppuswamy Sathyanarayanan @ 2026-02-11 18:40 UTC (permalink / raw)
To: Kiryl Shutsemau
Cc: Dan Williams, Dave Hansen, Rick Edgecombe, x86, linux-kernel,
linux-coco
In-Reply-To: <aYxjhdES2MOI7rj9@thinkstation>
Hi Kiryl,
Thanks for the review!
On 2/11/2026 3:17 AM, Kiryl Shutsemau wrote:
> On Tue, Feb 10, 2026 at 04:17:12PM -0800, Kuppuswamy Sathyanarayanan wrote:
>> Intel platforms are transitioning from traditional SGX-based
>> attestation toward DICE-based attestation as part of a broader move
>> toward open and standardized attestation models. DICE enables layered
>> and extensible attestation, where evidence is accumulated across
>> multiple boot stages.
>>
>> With SGX-based attestation, Quote sizes are typically under 8KB, as the
>> payload consists primarily of Quote data and a small certificate bundle.
>> Existing TDX guest code sizes the Quote buffer accordingly.
>>
>> DICE-based attestation produces significantly larger Quotes due to the
>> inclusion of evidence (certificate chains) from multiple boot layers.
>> The cumulative Quote size can reach approximately 100KB.
>>
>> Increase GET_QUOTE_BUF_SIZE to 128KB to ensure sufficient buffer
>> capacity for DICE-based Quote payloads.
>
> It worth noting that it requires guest physically-contiguous memory.
>
> Single order-5 allocation is not that bad as long as the driver
> initialized during the boot.
Good point! We can add following to the commit log:
The Quote buffer requires guest physically-contiguous memory and is
allocated once during driver initialization at boot time, where an
order-5 allocation (128KB) is expected to succeed reliably.
>
--
Sathyanarayanan Kuppuswamy
Linux Kernel Developer
^ permalink raw reply
* Re: [PATCH v1 1/3] virt: tsm: Document size limits for outblob attributes
From: Kuppuswamy Sathyanarayanan @ 2026-02-11 16:40 UTC (permalink / raw)
To: dan.j.williams, Kirill A . Shutemov
Cc: Dave Hansen, Rick Edgecombe, x86, linux-kernel, linux-coco
In-Reply-To: <698be62f7802_8c32100e2@dwillia2-mobl4.notmuch>
Hi Dan,
On 2/10/2026 6:15 PM, dan.j.williams@intel.com wrote:
> Kuppuswamy Sathyanarayanan wrote:
>> The configfs-tsm-report interface can fail with -EFBIG when the
>> attestation report generated by a TSM provider exceeds internal
>> maximums (TSM_REPORT_OUTBLOB_MAX). However, this error condition and
>> its handling are not currently documented in the ABI.
>>
>> Userspace tools need to understand how to interpret various error
>> conditions when reading attestation reports.
>>
>> Document that reads fail with -EFBIG when reports exceed size limits,
>> with guidance on how to resolve them.
>>
>> Reviewed-by: Fang Peter <peter.fang@intel.com>
>> Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
>
> Looks good, I will change the subject to:
>
> "configfs-tsm-report: Document size limits for outblob attributes"
Thanks for the review! Are you planning to apply this with the updated
subject line, or would you like me to send a v2 with the change?
--
Sathyanarayanan Kuppuswamy
Linux Kernel Developer
^ permalink raw reply
* Re: [PATCH v1 06/26] x86/virt/tdx: Add tdx_page_array helpers for new TDX Module objects
From: dan.j.williams @ 2026-02-11 16:24 UTC (permalink / raw)
To: Dave Hansen, Xu Yilun, linux-coco, linux-pci
Cc: chao.gao, dave.jiang, baolu.lu, yilun.xu, zhenzhong.duan, kvm,
rick.p.edgecombe, dave.hansen, dan.j.williams, kas, x86
In-Reply-To: <96d66314-a0f8-4c63-8f0d-1d392882e007@intel.com>
Dave Hansen wrote:
> On 11/16/25 18:22, Xu Yilun wrote:
> > + struct page *root __free(__free_page) = alloc_page(GFP_KERNEL |
> > + __GFP_ZERO);
> > + if (!root)
> > + return NULL;
>
> Why don't you just kcalloc() this like the rest of them?
>
> Then you won't need "mm: Add __free() support for __free_page()" either,
> right?
I saw a preview of what this comment does to the implementation and I am
not sure it is an improvement to avoid the __free() support for
alloc_page().
The seamcall prototype is:
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);
The interdiff of the effect of this review feedback is:
@@ drivers/virt/coco/tdx-host/tdx-host.c: static void tdx_spdm_session_teardown(str
+
+ do {
+ r = tdh_ide_stream_km(tlink->spdm_id, tlink->stream_id, op,
-+ tlink->in_msg, tlink->out_msg,
++ virt_to_page(tlink->in_msg),
++ virt_to_page(tlink->out_msg),
+ &out_msg_sz);
+ ret = tdx_link_event_handler(tlink, r, out_msg_sz);
+ } while (ret == -EAGAIN);
This is unfortunate because tdh_ide_stream_km() will just turn around
and call page_to_phys() on those arguments. It forfeits type safety and
inflicts the mental hurdle of "->in_msg and ->out_msg are direct-mapped
page-aligned virtual addresses, right, right!?".
So alloc_page() + __free_page() is more suitable than kzalloc(PAGE_SIZE,
...) as long as the seamcall requires 'struct page *' arguments. Another
path is the seamcall semantic is updated to handle virt_to_phys()
without alignment concerns, but it is a bit late to make that change. I
considered the concept of passing a "va page frame number" around, but
'struct page *' *is* the idiomatic expression for that concept.
^ permalink raw reply
* Re: [PATCH v1 3/3] virt: tdx-guest: Increase Quote buffer size to 128KB
From: Kiryl Shutsemau @ 2026-02-11 11:17 UTC (permalink / raw)
To: Kuppuswamy Sathyanarayanan
Cc: Dan Williams, Dave Hansen, Rick Edgecombe, x86, linux-kernel,
linux-coco
In-Reply-To: <20260211001712.1531955-4-sathyanarayanan.kuppuswamy@linux.intel.com>
On Tue, Feb 10, 2026 at 04:17:12PM -0800, Kuppuswamy Sathyanarayanan wrote:
> Intel platforms are transitioning from traditional SGX-based
> attestation toward DICE-based attestation as part of a broader move
> toward open and standardized attestation models. DICE enables layered
> and extensible attestation, where evidence is accumulated across
> multiple boot stages.
>
> With SGX-based attestation, Quote sizes are typically under 8KB, as the
> payload consists primarily of Quote data and a small certificate bundle.
> Existing TDX guest code sizes the Quote buffer accordingly.
>
> DICE-based attestation produces significantly larger Quotes due to the
> inclusion of evidence (certificate chains) from multiple boot layers.
> The cumulative Quote size can reach approximately 100KB.
>
> Increase GET_QUOTE_BUF_SIZE to 128KB to ensure sufficient buffer
> capacity for DICE-based Quote payloads.
It worth noting that it requires guest physically-contiguous memory.
Single order-5 allocation is not that bad as long as the driver
initialized during the boot.
--
Kiryl Shutsemau / Kirill A. Shutemov
^ permalink raw reply
* Re: [RFC PATCH v5 37/45] KVM: x86/tdp_mmu: Alloc external_spt page for mirror page table splitting
From: Yan Zhao @ 2026-02-11 9:49 UTC (permalink / raw)
To: Sean Christopherson
Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Kiryl Shutsemau, Paolo Bonzini, linux-kernel, linux-coco, kvm,
Kai Huang, Rick Edgecombe, Vishal Annapurve, Ackerley Tng,
Sagi Shahar, Binbin Wu, Xiaoyao Li, Isaku Yamahata
In-Reply-To: <aYYSIndbqLdFkaM-@google.com>
On Fri, Feb 06, 2026 at 08:09:06AM -0800, Sean Christopherson wrote:
> > So, it's incorrect to invoke is_mirror_sptep() which internally contains
> > rcu_dereference(), resulting in "WARNING: suspicious RCU usage".
>
> Ah, now I see why the previous code pass in a bool. I don't love passing a bool,
> but passing @iter is outright dangerous, so I guess this?
LGTM.
> diff --git a/arch/x86/kvm/mmu/tdp_mmu.c b/arch/x86/kvm/mmu/tdp_mmu.c
> index a32192c35099..4d92c0d19d7c 100644
> --- a/arch/x86/kvm/mmu/tdp_mmu.c
> +++ b/arch/x86/kvm/mmu/tdp_mmu.c
> @@ -1448,7 +1448,7 @@ bool kvm_tdp_mmu_wrprot_slot(struct kvm *kvm,
> }
>
> static struct kvm_mmu_page *tdp_mmu_alloc_sp_for_split(struct kvm *kvm,
> - struct tdp_iter *iter)
> + bool is_mirror_sp)
> {
> struct kvm_mmu_page *sp;
>
> @@ -1460,7 +1460,7 @@ static struct kvm_mmu_page *tdp_mmu_alloc_sp_for_split(struct kvm *kvm,
> if (!sp->spt)
> goto err_spt;
>
> - if (is_mirror_sptep(iter->sptep)) {
> + if (is_mirror_sp) {
> sp->external_spt = (void *)kvm_x86_call(alloc_external_sp)(GFP_KERNEL_ACCOUNT);
> if (!sp->external_spt)
> goto err_external_spt;
> @@ -1525,6 +1525,7 @@ static int tdp_mmu_split_huge_pages_root(struct kvm *kvm,
> gfn_t start, gfn_t end,
> int target_level, bool shared)
> {
> + const bool is_mirror_root = is_mirror_sp(root);
> struct kvm_mmu_page *sp = NULL;
> struct tdp_iter iter;
>
> @@ -1557,7 +1558,7 @@ static int tdp_mmu_split_huge_pages_root(struct kvm *kvm,
> else
> write_unlock(&kvm->mmu_lock);
>
> - sp = tdp_mmu_alloc_sp_for_split(kvm, &iter);
> + sp = tdp_mmu_alloc_sp_for_split(kvm, is_mirror_root);
>
> if (shared)
> read_lock(&kvm->mmu_lock);
^ permalink raw reply
* [PATCH 1/1] virt: tdx-guest: Optimize the get-quote polling interval time
From: Jun Miao @ 2026-02-11 8:58 UTC (permalink / raw)
To: kas, dave.hansen, rick.p.edgecombe, sathyanarayanan.kuppuswamy
Cc: linux-coco, kvm, linux-kernel, jun.miao
In-Reply-To: <20260211085801.4036464-1-jun.miao@intel.com>
The TD guest sends TDREPORT to the TD Quoting Enclave via a vsock or
a tdvmcall. In general, vsock is indeed much faster than tdvmcall,
and Quote requests usually take a few millisecond to complete rather
than seconds based on actual measurements.
The following get quote time via tdvmcall were obtained on the GNR:
| msleep_interruptible(time) | 1s | 5ms | 1ms |
| ------------------------------ | -------- | -------- | ---------- |
| Duration | 1.004 s | 1.005 s | 1.036 s |
| Total(Get Quote) | 167 | 142 | 167 |
| Success: | 167 | 142 | 167 |
| Failure: | 0 | 0 | 0 |
| Avg total / 1s | 0.97 | 141.31 | 166.35 |
| Avg success / 1s | 0.97 | 141.31 | 166.35 |
| Avg total / 1s / thread | 0.97 | 141.31 | 166.35 |
| Avg success / 1s / thread | 0.97 | 141.31 | 166.35 |
| Min elapsed_time | 1025.95ms| 6.85 ms | 2.99 ms |
| Max elapsed_time | 1025.95ms| 10.93 ms | 10.76 ms |
According to trace analysis, the typical execution tdvmcall get the
quote time is 4 ms. Therefore, 5 ms is a reasonable balance between
performance efficiency and CPU overhead.
And compared to the previous throughput of one request per second,
the current 5ms can get 142 requests per second delivers a
142× performance improvement, which is critical for high-frequency
use cases without vsock.
So, change the 1s (MSEC_PER_SEC) -> 5ms (MSEC_PER_SEC / 200)
Signed-off-by: Jun Miao <jun.miao@intel.com>
---
drivers/virt/coco/tdx-guest/tdx-guest.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.c b/drivers/virt/coco/tdx-guest/tdx-guest.c
index 4e239ec960c9..71d2d7304b1a 100644
--- a/drivers/virt/coco/tdx-guest/tdx-guest.c
+++ b/drivers/virt/coco/tdx-guest/tdx-guest.c
@@ -251,11 +251,11 @@ static int wait_for_quote_completion(struct tdx_quote_buf *quote_buf, u32 timeou
int i = 0;
/*
- * Quote requests usually take a few seconds to complete, so waking up
- * once per second to recheck the status is fine for this use case.
+ * Quote requests usually take a few milliseconds to complete, so waking up
+ * once per 5 milliseconds to recheck the status is fine for this use case.
*/
- while (quote_buf->status == GET_QUOTE_IN_FLIGHT && i++ < timeout) {
- if (msleep_interruptible(MSEC_PER_SEC))
+ while (quote_buf->status == GET_QUOTE_IN_FLIGHT && i++ < 200 * timeout) {
+ if (msleep_interruptible(MSEC_PER_SEC / 200))
return -EINTR;
}
--
2.32.0
^ permalink raw reply related
* [PATCH 0/1] [Test Report] get qutoe time via tdvmcall
From: Jun Miao @ 2026-02-11 8:58 UTC (permalink / raw)
To: kas, dave.hansen, rick.p.edgecombe, sathyanarayanan.kuppuswamy
Cc: linux-coco, kvm, linux-kernel, jun.miao
[Background]
Currently, many mobile device vendors (such as OPPO and Xiaomi) use TDVM for security management.
Each mobile terminal must perform remote attestation before it can access the TDVM confidential container.
As a result, there are a large number of remote attestation get-quote requests, especially in cases
where vsock is not configured or misconfigured and cannot be used.
[Limitation]
Currently, the polling interval is set to 1 second, which allows at most one quote to be retrieved per second.
For workloads with frequent remote attestations, polling once per second severely limits performance.
Test like this:
[root@INTELTDX ~]# ./test_tdx_attest-thread
Start tdx_att_get_quote concurrent loop, duration: 1 s, threads: 1
Summary (tdx_att_get_quote)
Threads: 1
Mode: concurrent
Duration: requested 1 s, actual 1.036 s
Total: 1
Success: 1
Failure: 0
Avg total per 1s: 0.97
Avg success per 1s: 0.97
Avg total per 1s per thread: 0.97
Avg success per 1s per thread: 0.97
Min elapsed_time: 1025.95 ms
Max elapsed_time: 1025.95 ms
[Optimization Rationale]
But the actual trace the get quote time on GNR platform:
test_tdx_attest-598 [001] ..... 371.214611: tdx_report_new: [debug start wait]===: I am in function wait_for_quote_completion LINE=155===
test_tdx_attest-598 [001] ..... 371.220287: tdx_report_new: [debug end wait]===: I am in function wait_for_quote_completion LINE=162===
Cost time: 371.220287 - 371.215611 = 0.004676 = 4.6ms
The following test results were obtained on the GNR platform:
| msleep_interruptible(time) | 1ms | 5ms | 1s |
| ------------------------------ | -------- | -------- | ---------- |
| Duration | 1.004 s | 1.005 s | 1.036 s |
| Total(Get Quote) | 167 | 142 | 1 |
| Success: | 167 | 142 | 1 |
| Failure: | 0 | 0 | 0 |
| Avg total / 1s | 166.35 | 141.31 | 0.97 |
| Avg success / 1s | 166.35 | 141.31 | 0.97 |
| Avg total / 1s / thread | 166.35 | 141.31 | 0.97 |
| Avg success / 1s / thread | 166.35 | 141.31 | 0.97 |
| Min elapsed_time | 2.99 ms | 6.85 ms | 1025.95 ms |
| Max elapsed_time | 10.76 ms | 10.93 ms | 1025.95 ms |
Jun Miao (1):
virt: tdx-guest: Optimize the get-quote polling interval time
drivers/virt/coco/tdx-guest/tdx-guest.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
--
2.32.0
^ permalink raw reply
* Re: [RFC PATCH v5 44/45] KVM: x86/mmu: Add support for splitting S-EPT hugepages on conversion
From: Yan Zhao @ 2026-02-11 8:43 UTC (permalink / raw)
To: Sean Christopherson
Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Kiryl Shutsemau, Paolo Bonzini, linux-kernel, linux-coco, kvm,
Kai Huang, Rick Edgecombe, Vishal Annapurve, Ackerley Tng,
Sagi Shahar, Binbin Wu, Xiaoyao Li, Isaku Yamahata
In-Reply-To: <aXt_L6QKB9CSTZcW@google.com>
On Thu, Jan 29, 2026 at 07:39:27AM -0800, Sean Christopherson wrote:
> Compile tested only...
It passed my local tests with the fix [1].
[1] https://lore.kernel.org/all/aYX-RpxDYrI65XRC@google.com.
> @@ -1950,6 +1950,7 @@ struct kvm_x86_ops {
> void *(*alloc_apic_backing_page)(struct kvm_vcpu *vcpu);
> int (*gmem_prepare)(struct kvm *kvm, kvm_pfn_t pfn, gfn_t gfn, int max_order);
> void (*gmem_invalidate)(kvm_pfn_t start, kvm_pfn_t end);
> + int (*gmem_convert)(struct kvm *kvm, gfn_t start, gfn_t end, bool to_private);
> int (*gmem_max_mapping_level)(struct kvm *kvm, kvm_pfn_t pfn, bool is_private);
> };
Since tdx_gmem_convert() only performs S-EPT splitting on the specified range,
would it make sense to rename the op gmem_convert() to something like
gmem_split_private_mapping()?
(This would also involve renaming
kvm_gmem_convert() to kvm_gmem_split_private_mapping(), and
kvm_arch_gmem_convert() to kvm_arch_gmem_split_private_mapping()).
This way, it's natural for it to be called by kvm_gmem_set_attributes() for
private-to-shared conversions, kvm_gmem_punch_hole(), or kvm_gmem_error_folio().
> +static int tdx_gmem_convert(struct kvm *kvm, gfn_t start, gfn_t end,
> + bool to_private)
> +{
> + /*
> + * When converting from private=>shared, KVM must first split potential
> + * hugepages, as KVM mustn't overzap private mappings for TDX guests,
> + * i.e. must zap _exactly_ [start, end). Split potential hugepages at
> + * the head and tail of the to-be-converted (and thus zapped) range so
> + * that KVM doesn't overzap due to dropping a hugepage that doesn't
> + * fall wholly inside the range.
> + */
> + if (to_private || !kvm_has_mirrored_tdp(kvm))
> + return 0;
> +
> + /*
> + * Acquire the external cache lock, a.k.a. the Dynamic PAMT lock, to
> + * protect the per-VM cache of pre-allocate pages used to populate the
> + * Dynamic PAMT when splitting S-EPT huge pages.
> + */
> + guard(mutex)(&to_kvm_tdx(kvm)->pamt_cache_lock);
Thanks for the change from spinlock to mutex, which is a smart approach that
eliminates the need to release the lock for topup.
However, I have a question about kvm_tdp_mmu_try_split_huge_pages(), which is
called by dirty page tracking related functions. I'm not sure if we might want
to invoke them from a non-vCPU thread for mirror roots in the future. If that's
the case, would they need some way to acquire this lock?
> + guard(write_lock)(&kvm->mmu_lock);
> +
> + /*
> + * TODO: Also split from PG_LEVEL_1G => PG_LEVEL_2M when KVM supports
> + * 1GiB S-EPT pages.
> + */
> + return tdx_sept_split_huge_pages(kvm, start, end, PG_LEVEL_4K);
> +}
> +
> diff --git a/arch/x86/kvm/vmx/tdx.h b/arch/x86/kvm/vmx/tdx.h
> index f444fc84d93b..2bb4604a64ca 100644
> --- a/arch/x86/kvm/vmx/tdx.h
> +++ b/arch/x86/kvm/vmx/tdx.h
> @@ -48,6 +48,9 @@ struct kvm_tdx {
> * Set/unset is protected with kvm->mmu_lock.
> */
> bool wait_for_sept_zap;
> +
> + struct tdx_pamt_cache pamt_cache;
> + struct mutex pamt_cache_lock;
> };
>
> /* TDX module vCPU states */
> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index c80cc60e7862..c3d71ba9a1dc 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c
> @@ -14061,7 +14061,7 @@ void kvm_arch_gmem_invalidate(kvm_pfn_t start, kvm_pfn_t end)
> int kvm_arch_gmem_convert(struct kvm *kvm, gfn_t start, gfn_t end,
> bool to_private)
> {
> - return 0;
> + return kvm_x86_call(gmem_convert)(kvm, start, end, to_private);
> }
> #endif
> #endif
>
> base-commit: b2791d61e9774d8575525816e864d2e09ee9090a
> --
^ permalink raw reply
* Re: [RFC PATCH v5 08/45] KVM: x86/mmu: Propagate mirror SPTE removal to S-EPT in handle_changed_spte()
From: Yan Zhao @ 2026-02-11 2:16 UTC (permalink / raw)
To: Sean Christopherson
Cc: Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen, x86,
Kiryl Shutsemau, Paolo Bonzini, linux-kernel, linux-coco, kvm,
Kai Huang, Rick Edgecombe, Vishal Annapurve, Ackerley Tng,
Sagi Shahar, Binbin Wu, Xiaoyao Li, Isaku Yamahata
In-Reply-To: <aYuMaRbVQyUfYJTP@google.com>
On Tue, Feb 10, 2026 at 11:52:09AM -0800, Sean Christopherson wrote:
> > For TDX's future implementation of set_external_spte() for split splitting,
> > could we add a new param "bool shared" to op set_external_spte() in the
> > future? i.e.,
> > - when tdx_sept_split_private_spte() is invoked under write mmu_lock, it calls
> > tdh_do_no_vcpus() to retry BUSY error, and TDX_BUG_ON_2() then.
> > - when tdx_sept_split_private_spte() is invoked under read mmu_lock
> > (in the future when calling tdh_mem_range_block() in unnecessary), it could
> > directly return BUSY to TDP MMU on contention.
>
> Yeah, I have no objection to using @shared for things like that.
Great.
> > > + return 0;
> > > +}
> > > +
> > > +static void handle_changed_spte(struct kvm *kvm, int as_id, tdp_ptep_t sptep,
> > > + gfn_t gfn, u64 old_spte, u64 new_spte,
> > > + int level, bool shared)
> > > +{
> > Do we need "WARN_ON_ONCE(is_mirror_sptep(sptep) && shared)" here ?
>
> No, because I want to call this code for all paths, including the fault path.
Hmm. IIUC, handle_changed_spte() can't be invoked for mirror root under read
mmu_lock.
For read mmu_lock + mirror scenarios, they need to invoke
tdp_mmu_set_spte_atomic() --> __handle_changed_spte().
> > > @@ -663,14 +630,44 @@ static inline int __must_check tdp_mmu_set_spte_atomic(struct kvm *kvm,
> > >
> > > lockdep_assert_held_read(&kvm->mmu_lock);
> > >
> > > - ret = __tdp_mmu_set_spte_atomic(kvm, iter, new_spte);
> > >
> > > + /* KVM should never freeze SPTEs using higher level APIs. */
> > > + KVM_MMU_WARN_ON(is_frozen_spte(new_spte));
> > What about
> > KVM_MMU_WARN_ON(is_frozen_spte(new_spte) ||
> > is_frozen_spte(iter->old_spte) || iter->yielded);
> >
> > > + /*
> > > + * Temporarily freeze the SPTE until the external PTE operation has
> > > + * completed (unless the new SPTE itself will be frozen), e.g. so that
> > > + * concurrent faults don't attempt to install a child PTE in the
> > > + * external page table before the parent PTE has been written, or try
> > > + * to re-install a page table before the old one was removed.
> > > + */
> > > + if (is_mirror_sptep(iter->sptep))
> > > + ret = __tdp_mmu_set_spte_atomic(kvm, iter, FROZEN_SPTE);
> > > + else
> > > + ret = __tdp_mmu_set_spte_atomic(kvm, iter, new_spte);
> > and invoking open code try_cmpxchg64() directly?
>
> No, because __tdp_mmu_set_spte_atomic() is still used by kvm_tdp_mmu_age_spte(),
> and the yielded/frozen rules apply there as well.
I see.
> > > + /*
> > > + * Unfreeze the mirror SPTE. If updating the external SPTE failed,
> > > + * restore the old SPTE so that the SPTE isn't frozen in perpetuity,
> > > + * otherwise set the mirror SPTE to the new desired value.
> > > + */
> > > + if (is_mirror_sptep(iter->sptep)) {
> > > + if (ret)
> > > + __kvm_tdp_mmu_write_spte(iter->sptep, iter->old_spte);
> > > + else
> > > + __kvm_tdp_mmu_write_spte(iter->sptep, new_spte);
> > > + } else {
> > > + /*
> > > + * Bug the VM if handling the change failed, as failure is only
> > > + * allowed if KVM couldn't update the external SPTE.
> > > + */
> > > + KVM_BUG_ON(ret, kvm);
> > > + }
> > > + return ret;
> > > }
> > One concern for tdp_mmu_set_spte_atomic() to handle mirror SPTEs:
> > - Previously
> > 1. set *iter->sptep to FROZEN_SPTE.
> > 2. kvm_x86_call(set_external_spte)(old_spte, new_spte)
> > 3. set *iter->sptep to new_spte
> >
> > - Now with this diff
> > 1. set *iter->sptep to FROZEN_SPTE.
> > 2. __handle_changed_spte()
> > --> kvm_x86_call(set_external_spte)(iter->sptep, old_spte, new_spte)
>
> Note, iter->sptep isn't passed to set_external_spte(), the invocation for that is:
>
> return kvm_x86_call(set_external_spte)(kvm, gfn, old_spte,
> new_spte, level);
Oh, sorry. It should be
2. __handle_changed_spte(iter->sptep, old_spte, new_spte)
--> kvm_x86_call(set_external_spte)(old_spte, new_spte)
My concern is that what we pass to __handle_changed_spte() here are
"iter->sptep, iter->old_spte, new_spte".
ret = __handle_changed_spte(kvm, iter->as_id, iter->sptep, iter->gfn,
iter->old_spte, new_spte, iter->level, true);
i.e., new_spte is the target value which we haven't written it to iter->sptep
yet. We'll write the target value new_spte to iter->sptep after
__handle_changed_spte() succeeds. So, upon invoking __handle_changed_spte() in
tdp_mmu_set_spte_atomic(), iter->sptep just holds value FROZEN_SPTE.
So, re-reading iter->sptep will get a different value (which is FROZEN_SPTE)
from the new_spte passed to __handle_changed_spte().
Besides, __handle_changed_spte() contains code like
"kvm_update_page_stats(kvm, level, is_leaf ? 1 : -1);", which may have
incorrectly updated the stats even if kvm_x86_call(set_external_spte)() fails
later and the new_spte is never written to iter->sptep.
> > 3. set *iter->sptep to new_spte
> >
> > what if __handle_changed_spte() reads *iter->sptep in step 2?
>
> For the most part, "don't do that". There are an infinite number of "what ifs".
> I agree that re-reading iter->sptep is slightly more likely than other "what ifs",
> but then if we convert to a boolean it creates the "what if we swap the order of
> @as_id and @is_mirror_sp"? Given that @old_spte is provided, IMO re-reading the
> SPTE from memory will stand out.
As my above concern, re-reading SPTE in __handle_changed_spte() will just get
value FROZEN_SPTE instead of the value of new_spte.
> That said, I think we can have the best of both worlds. Rather than pass @as_id
> and @sptep, pass the @sp, i.e. the owning kvm_mmu_page. That would address your
> concern about re-reading the sptep, without needing another boolean.
Hmm, my intention of passing boolean is to avoid re-reading sptep, because
in step 2, we pass new_spte instead of the real value in sptep (which is
FROZEN_SPTE for mirror sp) to __handle_changed_spte().
So, passing @sp may not help?
^ permalink raw reply
* Re: [PATCH v1 3/3] virt: tdx-guest: Increase Quote buffer size to 128KB
From: dan.j.williams @ 2026-02-11 2:19 UTC (permalink / raw)
To: Kuppuswamy Sathyanarayanan, Dan Williams, Kirill A . Shutemov
Cc: Dave Hansen, Rick Edgecombe, x86, linux-kernel, linux-coco
In-Reply-To: <20260211001712.1531955-4-sathyanarayanan.kuppuswamy@linux.intel.com>
Kuppuswamy Sathyanarayanan wrote:
> Intel platforms are transitioning from traditional SGX-based
> attestation toward DICE-based attestation as part of a broader move
> toward open and standardized attestation models. DICE enables layered
> and extensible attestation, where evidence is accumulated across
> multiple boot stages.
>
> With SGX-based attestation, Quote sizes are typically under 8KB, as the
> payload consists primarily of Quote data and a small certificate bundle.
> Existing TDX guest code sizes the Quote buffer accordingly.
>
> DICE-based attestation produces significantly larger Quotes due to the
> inclusion of evidence (certificate chains) from multiple boot layers.
> The cumulative Quote size can reach approximately 100KB.
>
> Increase GET_QUOTE_BUF_SIZE to 128KB to ensure sufficient buffer
> capacity for DICE-based Quote payloads.
>
> Reviewed-by: Fang Peter <peter.fang@intel.com>
> Signed-off-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
> ---
> Documentation/ABI/testing/configfs-tsm-report | 4 ++++
> drivers/virt/coco/tdx-guest/tdx-guest.c | 4 +++-
> 2 files changed, 7 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/ABI/testing/configfs-tsm-report b/Documentation/ABI/testing/configfs-tsm-report
> index ca3352cfd2f1..7a6a5045a7d5 100644
> --- a/Documentation/ABI/testing/configfs-tsm-report
> +++ b/Documentation/ABI/testing/configfs-tsm-report
> @@ -73,6 +73,10 @@ Description:
> Library Revision 0.8 Appendix 4,5
> https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_TDX_DCAP_Quoting_Library_API.pdf
>
> + Intel TDX platforms with DICE-based attestation use CBOR Web Token
> + (CWT) format for the Quote payload. This is indicated by the Quote
> + size exceeding 8KB.
Looks ok, please follow up with a link to the full format specification
when it is published.
I will change this subject to:
"configfs-tsm-report: tdx_guest: Increase Quote buffer size to 128KB"
^ permalink raw reply
* Re: [PATCH v1 2/3] virt: tsm: Increase TSM_REPORT_OUTBLOB_MAX to 16MB
From: dan.j.williams @ 2026-02-11 2:16 UTC (permalink / raw)
To: Kuppuswamy Sathyanarayanan, Dan Williams, Kirill A . Shutemov
Cc: Dave Hansen, Rick Edgecombe, x86, linux-kernel, linux-coco
In-Reply-To: <20260211001712.1531955-3-sathyanarayanan.kuppuswamy@linux.intel.com>
Kuppuswamy Sathyanarayanan wrote:
> Confidential Computing (CoCo) attestation is evolving toward
> standardized models such as DICE (Device Identifier Composition Engine)
> and Post-Quantum Cryptography (PQC), which rely on layered certificate
> chains and larger cryptographic signatures.
>
> A typical PQC certificate can range from 5KB to 15KB, and DICE-based
> architectures accumulate these certificates across multiple boot
> stages. In such configurations, the total attestation evidence can
> reach several megabytes, exceeding the current 32KB limit.
>
> Increase TSM_REPORT_OUTBLOB_MAX to 16MB to accommodate these larger
> certificate chains. This provides sufficient headroom to handle
> evolving requirements without requiring frequent updates to the limit.
>
> TSM_REPORT_OUTBLOB_MAX is used by the configfs read interface to cap
> the maximum allowed binary blob size for outblob, auxblob and
> manifestblob attributes. Hence, the per-open-file worst case memory
> allocation increases from 32KB to 16MB. Multiple concurrent readers
> multiply this cost (e.g., N readers of an M-byte blob incur NxM bytes
> of vmalloc-backed memory). However, allocations are performed on demand
> and remain proportional to the actual blob length, not the configured
> maximum.
Looks ok, again I will change the subject to:
"configfs-tsm-report: Increase TSM_REPORT_OUTBLOB_MAX to 16MB"
^ 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