root/drivers/virt/coco/tdx-host/tdx-host.c
// SPDX-License-Identifier: GPL-2.0
/*
 * TDX host user interface driver
 *
 * Copyright (C) 2025 Intel Corporation
 */

#include <linux/device/faux.h>
#include <linux/firmware.h>
#include <linux/module.h>
#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[] = {
        X86_MATCH_FEATURE(X86_FEATURE_TDX_HOST_PLATFORM, NULL),
        {}
};
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;
        int ret;

        if (!tdx_sysinfo)
                return -ENXIO;

        /*
         * The version number can change during an update.
         * Lock out updates while printing the version.
         */
        seamldr_lock_module_update();

        ver = &tdx_sysinfo->version;
        ret = sysfs_emit(buf, TDX_VERSION_FMT "\n", ver->major_version,
                                                    ver->minor_version,
                                                    ver->update_version);
        seamldr_unlock_module_update();

        return ret;
}
static DEVICE_ATTR_RO(version);

static struct attribute *tdx_host_attrs[] = {
        &dev_attr_version.attr,
        NULL,
};

static const 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, TDX_VERSION_FMT "\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);
}

/*
 * These attributes are intended for managing TDX module updates. Reading
 * them issues a slow, serialized P-SEAMLDR query, so keep them admin-only.
 */
static DEVICE_ATTR_ADMIN_RO(seamldr_version);
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 bool supports_runtime_update(void)
{
        const struct tdx_sys_info *sysinfo = tdx_get_sysinfo();

        if (!sysinfo)
                return false;

        if (!tdx_supports_runtime_update(sysinfo))
                return false;

        /*
         * This bug makes P-SEAMLDR calls clobber the current VMCS
         * which breaks KVM. Avoid P-SEAMLDR calls by hiding all
         * 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;
}

static const struct attribute_group seamldr_group = {
        .attrs = seamldr_attrs,
        .is_visible = seamldr_group_visible,
};

static const struct attribute_group *tdx_host_groups[] = {
        &tdx_host_group,
        &seamldr_group,
        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)
{
        if (!x86_match_cpu(tdx_host_ids) || !tdx_get_sysinfo())
                return -ENODEV;

        fdev = faux_device_create_with_groups(KBUILD_MODNAME, NULL,
                                              &tdx_host_ops,
                                              tdx_host_groups);
        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");