// SPDX-License-Identifier: GPL-2.0 /* * TDX host user interface driver * * Copyright (C) 2025 Intel Corporation */ #include #include #include #include #include #include #include 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");