]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
coco/tdx-host: Implement firmware upload sysfs ABI for TDX module updates
authorChao Gao <chao.gao@intel.com>
Wed, 20 May 2026 22:29:00 +0000 (15:29 -0700)
committerDave Hansen <dave.hansen@linux.intel.com>
Wed, 3 Jun 2026 15:14:51 +0000 (08:14 -0700)
tl;dr: Select fw_upload for doing TDX module updates. The process of
selecting among available update images is complicated and nuanced. Punt
the selection process out to userspace. One existing userspace
implementation today is the script in the Intel TDX Module Binaries
repository[1].

Long Version:

The kernel supports two primary firmware update mechanisms:
 1. request_firmware() - used by microcode, SEV firmware, hundreds of
 other drivers
 2. 'struct fw_upload' - used by CXL, FPGA updates, dozens of others

The key difference between is that request_firmware() loads a named file
from the filesystem where the filename is kernel-controlled, while
fw_upload accepts firmware data directly from userspace.

TDX module firmware update selection policy is too complex for the kernel.
Leave it to userspace and use fw_upload.

Add a skeleton fw_upload implementation to be fleshed out in subsequent
patches.

Refactor the sysfs visiblity attribute function so it can be used as a
more generic flag for the presence of viable runtime update support.

Why fw_upload instead of request_firmware()?
============================================

Selecting a TDX module update image is not a simple "load the latest"
decision. Userspace needs to choose an image that is compatible with both
the platform and the currently running module.

Some constraints are hard requirements:

a. Module version series are platform-specific. For example, the 1.5.x
   series runs on Sapphire Rapids but not Granite Rapids, which needs
   2.0.x.

b. Updates are also constrained by version distance. A 1.5.6 module
   might permit updates to 1.5.7 but not to 1.5.50.

There may also be userspace policy choices:

c. Decide the update direction: upgrade or downgrade

d. Choose whether to optimize for fewer updates or smaller version
   steps, for example, 1.2.3=>1.2.5 versus 1.2.3=>1.2.4=>1.2.5.

Given that complexity, leave module selection to userspace and use
fw_upload.

1. https://github.com/intel/confidential-computing.tdx.tdx-module.binaries/blob/main/version_select_and_load.py

[ dhansen: add version script link, add more explanation of code moves,
     fix some minor whitespace issues ]

Signed-off-by: Chao Gao <chao.gao@intel.com>
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
Reviewed-by: Kai Huang <kai.huang@intel.com>
Reviewed-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
Link: https://lore.kernel.org/kvm/01fc8946-eb84-46fa-9458-f345dd3f6033@intel.com/
Link: https://patch.msgid.link/20260520133909.409394-13-chao.gao@intel.com
arch/x86/include/asm/seamldr.h
arch/x86/virt/vmx/tdx/seamldr.c
drivers/virt/coco/tdx-host/Kconfig
drivers/virt/coco/tdx-host/tdx-host.c

index a74151b755990d074d07041adbb7c5a65a80707b..43084e2daa2d4d3dc804537bf9bb1dc3561b892a 100644 (file)
@@ -31,5 +31,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 data_len);
 
 #endif /* _ASM_X86_SEAMLDR_H */
index baa86f2da04ebad04f0a851a20d7e9e4c0d0dd4b..b2b6a439d41770110d215a7c9f9c007cb173f603 100644 (file)
@@ -54,3 +54,17 @@ 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 image.
+ * @data_len: Size of the TDX module image.
+ *
+ * Returns 0 on success, negative error code on failure.
+ */
+int seamldr_install_module(const u8 *data, u32 data_len)
+{
+       /* TODO: Update TDX module here */
+       return 0;
+}
+EXPORT_SYMBOL_FOR_MODULES(seamldr_install_module, "tdx-host");
index cfe81b9c036466499f781db8fb59ea1cae3279b7..57d0c01a435777326e317ee838d34725d8436a8d 100644 (file)
@@ -1,4 +1,6 @@
 config TDX_HOST_SERVICES
        tristate
        depends on INTEL_TDX_HOST
+       select FW_LOADER
+       select FW_UPLOAD
        default m
index 2886554f0e60068961df56791c6db454382e750e..e5a672be6200ce27c2c26e1bf9fbf06f70f7829a 100644 (file)
@@ -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>
@@ -88,15 +89,15 @@ static struct attribute *seamldr_attrs[] = {
        NULL,
 };
 
-static umode_t seamldr_group_visible(struct kobject *kobj, struct attribute *attr, int idx)
+static bool supports_runtime_update(void)
 {
        const struct tdx_sys_info *sysinfo = tdx_get_sysinfo();
 
        if (!sysinfo)
-               return 0;
+               return false;
 
        if (!tdx_supports_runtime_update(sysinfo))
-               return 0;
+               return false;
 
        /*
         * This bug makes P-SEAMLDR calls clobber the current VMCS
@@ -104,6 +105,14 @@ static umode_t seamldr_group_visible(struct kobject *kobj, struct attribute *att
         * attributes if the CPU has this bug.
         */
        if (boot_cpu_has_bug(X86_BUG_SEAMRET_INVD_VMCS))
+               return false;
+
+       return true;
+}
+
+static umode_t seamldr_group_visible(struct kobject *kobj, struct attribute *attr, int idx)
+{
+       if (!supports_runtime_update())
                return 0;
 
        return attr->mode;
@@ -120,6 +129,80 @@ 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 data_len)
+{
+       return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err tdx_fw_write(struct fw_upload *fwl, const u8 *data,
+                                      u32 offset, u32 data_len, u32 *written)
+{
+       int ret;
+
+       ret = seamldr_install_module(data, data_len);
+       switch (ret) {
+       case 0:
+               *written = data_len;
+               return FW_UPLOAD_ERR_NONE;
+       default:
+               return FW_UPLOAD_ERR_FW_INVALID;
+       }
+}
+
+static enum fw_upload_err tdx_fw_poll_complete(struct fw_upload *fwl)
+{
+       /*
+        * The upload completed during tdx_fw_write().
+        * Never poll for completion.
+        */
+       return FW_UPLOAD_ERR_NONE;
+}
+
+static void tdx_fw_cancel(struct fw_upload *fwl)
+{
+       /*
+        * TDX module updates are not cancellable.
+        * Provide a no-op callback to satisfy fw_upload_ops.
+        */
+}
+
+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_deinit(void *tdx_fwl)
+{
+       firmware_upload_unregister(tdx_fwl);
+}
+
+static int seamldr_init(struct device *dev)
+{
+       struct fw_upload *tdx_fwl;
+
+       if (!supports_runtime_update())
+               return 0;
+
+       tdx_fwl = firmware_upload_register(THIS_MODULE, dev, "tdx_module",
+                                          &tdx_fw_ops, NULL);
+       if (IS_ERR(tdx_fwl))
+               return PTR_ERR(tdx_fwl);
+
+       return devm_add_action_or_reset(dev, seamldr_deinit, tdx_fwl);
+}
+
+static int tdx_host_probe(struct faux_device *fdev)
+{
+       return seamldr_init(&fdev->dev);
+}
+
+static const struct faux_device_ops tdx_host_ops = {
+       .probe          = tdx_host_probe,
+};
+
 static struct faux_device *fdev;
 
 static int __init tdx_host_init(void)
@@ -127,7 +210,9 @@ 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;