xref: /linux/drivers/virt/coco/tdx-host/tdx-host.c (revision d2c9a99135da931377240942d44f3dea104cedb8)
159783353SChao Gao // SPDX-License-Identifier: GPL-2.0
259783353SChao Gao /*
359783353SChao Gao  * TDX host user interface driver
459783353SChao Gao  *
559783353SChao Gao  * Copyright (C) 2025 Intel Corporation
659783353SChao Gao  */
759783353SChao Gao 
859783353SChao Gao #include <linux/device/faux.h>
9c3e70c5eSChao Gao #include <linux/firmware.h>
1059783353SChao Gao #include <linux/module.h>
116fe8a33fSChao Gao #include <linux/sysfs.h>
1259783353SChao Gao 
1359783353SChao Gao #include <asm/cpu_device_id.h>
14885afcf7SChao Gao #include <asm/seamldr.h>
1559783353SChao Gao #include <asm/tdx.h>
1659783353SChao Gao 
1759783353SChao Gao static const struct x86_cpu_id tdx_host_ids[] = {
1859783353SChao Gao 	X86_MATCH_FEATURE(X86_FEATURE_TDX_HOST_PLATFORM, NULL),
1959783353SChao Gao 	{}
2059783353SChao Gao };
2159783353SChao Gao MODULE_DEVICE_TABLE(x86cpu, tdx_host_ids);
2259783353SChao Gao 
version_show(struct device * dev,struct device_attribute * attr,char * buf)236fe8a33fSChao Gao static ssize_t version_show(struct device *dev, struct device_attribute *attr,
246fe8a33fSChao Gao 			    char *buf)
256fe8a33fSChao Gao {
266fe8a33fSChao Gao 	const struct tdx_sys_info *tdx_sysinfo = tdx_get_sysinfo();
276fe8a33fSChao Gao 	const struct tdx_sys_info_version *ver;
28*8d1376e6SDave Hansen 	int ret;
296fe8a33fSChao Gao 
306fe8a33fSChao Gao 	if (!tdx_sysinfo)
316fe8a33fSChao Gao 		return -ENXIO;
326fe8a33fSChao Gao 
33*8d1376e6SDave Hansen 	/*
34*8d1376e6SDave Hansen 	 * The version number can change during an update.
35*8d1376e6SDave Hansen 	 * Lock out updates while printing the version.
36*8d1376e6SDave Hansen 	 */
37*8d1376e6SDave Hansen 	seamldr_lock_module_update();
386fe8a33fSChao Gao 
39*8d1376e6SDave Hansen 	ver = &tdx_sysinfo->version;
40*8d1376e6SDave Hansen 	ret = sysfs_emit(buf, TDX_VERSION_FMT "\n", ver->major_version,
416fe8a33fSChao Gao 						    ver->minor_version,
426fe8a33fSChao Gao 						    ver->update_version);
43*8d1376e6SDave Hansen 	seamldr_unlock_module_update();
44*8d1376e6SDave Hansen 
45*8d1376e6SDave Hansen 	return ret;
466fe8a33fSChao Gao }
476fe8a33fSChao Gao static DEVICE_ATTR_RO(version);
486fe8a33fSChao Gao 
496fe8a33fSChao Gao static struct attribute *tdx_host_attrs[] = {
506fe8a33fSChao Gao 	&dev_attr_version.attr,
516fe8a33fSChao Gao 	NULL,
526fe8a33fSChao Gao };
53885afcf7SChao Gao 
54885afcf7SChao Gao static const struct attribute_group tdx_host_group = {
55885afcf7SChao Gao 	.attrs = tdx_host_attrs,
56885afcf7SChao Gao };
57885afcf7SChao Gao 
seamldr_version_show(struct device * dev,struct device_attribute * attr,char * buf)58885afcf7SChao Gao static ssize_t seamldr_version_show(struct device *dev, struct device_attribute *attr,
59885afcf7SChao Gao 				    char *buf)
60885afcf7SChao Gao {
61885afcf7SChao Gao 	struct seamldr_info info;
62885afcf7SChao Gao 	int ret;
63885afcf7SChao Gao 
64885afcf7SChao Gao 	ret = seamldr_get_info(&info);
65885afcf7SChao Gao 	if (ret)
66885afcf7SChao Gao 		return ret;
67885afcf7SChao Gao 
68885afcf7SChao Gao 	return sysfs_emit(buf, TDX_VERSION_FMT "\n", info.major_version,
69885afcf7SChao Gao 						     info.minor_version,
70885afcf7SChao Gao 						     info.update_version);
71885afcf7SChao Gao }
72885afcf7SChao Gao 
num_remaining_updates_show(struct device * dev,struct device_attribute * attr,char * buf)73885afcf7SChao Gao static ssize_t num_remaining_updates_show(struct device *dev,
74885afcf7SChao Gao 					  struct device_attribute *attr,
75885afcf7SChao Gao 					  char *buf)
76885afcf7SChao Gao {
77885afcf7SChao Gao 	struct seamldr_info info;
78885afcf7SChao Gao 	int ret;
79885afcf7SChao Gao 
80885afcf7SChao Gao 	ret = seamldr_get_info(&info);
81885afcf7SChao Gao 	if (ret)
82885afcf7SChao Gao 		return ret;
83885afcf7SChao Gao 
84885afcf7SChao Gao 	return sysfs_emit(buf, "%u\n", info.num_remaining_updates);
85885afcf7SChao Gao }
86885afcf7SChao Gao 
87885afcf7SChao Gao /*
88885afcf7SChao Gao  * These attributes are intended for managing TDX module updates. Reading
89885afcf7SChao Gao  * them issues a slow, serialized P-SEAMLDR query, so keep them admin-only.
90885afcf7SChao Gao  */
91885afcf7SChao Gao static DEVICE_ATTR_ADMIN_RO(seamldr_version);
92885afcf7SChao Gao static DEVICE_ATTR_ADMIN_RO(num_remaining_updates);
93885afcf7SChao Gao 
94885afcf7SChao Gao static struct attribute *seamldr_attrs[] = {
95885afcf7SChao Gao 	&dev_attr_seamldr_version.attr,
96885afcf7SChao Gao 	&dev_attr_num_remaining_updates.attr,
97885afcf7SChao Gao 	NULL,
98885afcf7SChao Gao };
99885afcf7SChao Gao 
supports_runtime_update(void)100c3e70c5eSChao Gao static bool supports_runtime_update(void)
101885afcf7SChao Gao {
102885afcf7SChao Gao 	const struct tdx_sys_info *sysinfo = tdx_get_sysinfo();
103885afcf7SChao Gao 
104885afcf7SChao Gao 	if (!sysinfo)
105c3e70c5eSChao Gao 		return false;
106885afcf7SChao Gao 
107885afcf7SChao Gao 	if (!tdx_supports_runtime_update(sysinfo))
108c3e70c5eSChao Gao 		return false;
109885afcf7SChao Gao 
1105ce9cc5aSChao Gao 	/*
1115ce9cc5aSChao Gao 	 * This bug makes P-SEAMLDR calls clobber the current VMCS
1125ce9cc5aSChao Gao 	 * which breaks KVM. Avoid P-SEAMLDR calls by hiding all
1135ce9cc5aSChao Gao 	 * attributes if the CPU has this bug.
1145ce9cc5aSChao Gao 	 */
1155ce9cc5aSChao Gao 	if (boot_cpu_has_bug(X86_BUG_SEAMRET_INVD_VMCS))
116c3e70c5eSChao Gao 		return false;
117c3e70c5eSChao Gao 
118c3e70c5eSChao Gao 	return true;
119c3e70c5eSChao Gao }
120c3e70c5eSChao Gao 
seamldr_group_visible(struct kobject * kobj,struct attribute * attr,int idx)121c3e70c5eSChao Gao static umode_t seamldr_group_visible(struct kobject *kobj, struct attribute *attr, int idx)
122c3e70c5eSChao Gao {
123c3e70c5eSChao Gao 	if (!supports_runtime_update())
1245ce9cc5aSChao Gao 		return 0;
1255ce9cc5aSChao Gao 
126885afcf7SChao Gao 	return attr->mode;
127885afcf7SChao Gao }
128885afcf7SChao Gao 
129885afcf7SChao Gao static const struct attribute_group seamldr_group = {
130885afcf7SChao Gao 	.attrs = seamldr_attrs,
131885afcf7SChao Gao 	.is_visible = seamldr_group_visible,
132885afcf7SChao Gao };
133885afcf7SChao Gao 
134885afcf7SChao Gao static const struct attribute_group *tdx_host_groups[] = {
135885afcf7SChao Gao 	&tdx_host_group,
136885afcf7SChao Gao 	&seamldr_group,
137885afcf7SChao Gao 	NULL,
138885afcf7SChao Gao };
1396fe8a33fSChao Gao 
tdx_fw_prepare(struct fw_upload * fwl,const u8 * data,u32 data_len)140c3e70c5eSChao Gao static enum fw_upload_err tdx_fw_prepare(struct fw_upload *fwl,
141c3e70c5eSChao Gao 					 const u8 *data, u32 data_len)
142c3e70c5eSChao Gao {
143c3e70c5eSChao Gao 	return FW_UPLOAD_ERR_NONE;
144c3e70c5eSChao Gao }
145c3e70c5eSChao Gao 
tdx_fw_write(struct fw_upload * fwl,const u8 * data,u32 offset,u32 data_len,u32 * written)146c3e70c5eSChao Gao static enum fw_upload_err tdx_fw_write(struct fw_upload *fwl, const u8 *data,
147c3e70c5eSChao Gao 				       u32 offset, u32 data_len, u32 *written)
148c3e70c5eSChao Gao {
149c3e70c5eSChao Gao 	int ret;
150c3e70c5eSChao Gao 
151c3e70c5eSChao Gao 	ret = seamldr_install_module(data, data_len);
152c3e70c5eSChao Gao 	switch (ret) {
153c3e70c5eSChao Gao 	case 0:
154c3e70c5eSChao Gao 		*written = data_len;
155c3e70c5eSChao Gao 		return FW_UPLOAD_ERR_NONE;
156c3e70c5eSChao Gao 	default:
157c3e70c5eSChao Gao 		return FW_UPLOAD_ERR_FW_INVALID;
158c3e70c5eSChao Gao 	}
159c3e70c5eSChao Gao }
160c3e70c5eSChao Gao 
tdx_fw_poll_complete(struct fw_upload * fwl)161c3e70c5eSChao Gao static enum fw_upload_err tdx_fw_poll_complete(struct fw_upload *fwl)
162c3e70c5eSChao Gao {
163c3e70c5eSChao Gao 	/*
164c3e70c5eSChao Gao 	 * The upload completed during tdx_fw_write().
165c3e70c5eSChao Gao 	 * Never poll for completion.
166c3e70c5eSChao Gao 	 */
167c3e70c5eSChao Gao 	return FW_UPLOAD_ERR_NONE;
168c3e70c5eSChao Gao }
169c3e70c5eSChao Gao 
tdx_fw_cancel(struct fw_upload * fwl)170c3e70c5eSChao Gao static void tdx_fw_cancel(struct fw_upload *fwl)
171c3e70c5eSChao Gao {
172c3e70c5eSChao Gao 	/*
173c3e70c5eSChao Gao 	 * TDX module updates are not cancellable.
174c3e70c5eSChao Gao 	 * Provide a no-op callback to satisfy fw_upload_ops.
175c3e70c5eSChao Gao 	 */
176c3e70c5eSChao Gao }
177c3e70c5eSChao Gao 
178c3e70c5eSChao Gao static const struct fw_upload_ops tdx_fw_ops = {
179c3e70c5eSChao Gao 	.prepare	= tdx_fw_prepare,
180c3e70c5eSChao Gao 	.write		= tdx_fw_write,
181c3e70c5eSChao Gao 	.poll_complete	= tdx_fw_poll_complete,
182c3e70c5eSChao Gao 	.cancel		= tdx_fw_cancel,
183c3e70c5eSChao Gao };
184c3e70c5eSChao Gao 
seamldr_deinit(void * tdx_fwl)185c3e70c5eSChao Gao static void seamldr_deinit(void *tdx_fwl)
186c3e70c5eSChao Gao {
187c3e70c5eSChao Gao 	firmware_upload_unregister(tdx_fwl);
188c3e70c5eSChao Gao }
189c3e70c5eSChao Gao 
seamldr_init(struct device * dev)190c3e70c5eSChao Gao static int seamldr_init(struct device *dev)
191c3e70c5eSChao Gao {
192c3e70c5eSChao Gao 	struct fw_upload *tdx_fwl;
193c3e70c5eSChao Gao 
194c3e70c5eSChao Gao 	if (!supports_runtime_update())
195c3e70c5eSChao Gao 		return 0;
196c3e70c5eSChao Gao 
197c3e70c5eSChao Gao 	tdx_fwl = firmware_upload_register(THIS_MODULE, dev, "tdx_module",
198c3e70c5eSChao Gao 					   &tdx_fw_ops, NULL);
199c3e70c5eSChao Gao 	if (IS_ERR(tdx_fwl))
200c3e70c5eSChao Gao 		return PTR_ERR(tdx_fwl);
201c3e70c5eSChao Gao 
202c3e70c5eSChao Gao 	return devm_add_action_or_reset(dev, seamldr_deinit, tdx_fwl);
203c3e70c5eSChao Gao }
204c3e70c5eSChao Gao 
tdx_host_probe(struct faux_device * fdev)205c3e70c5eSChao Gao static int tdx_host_probe(struct faux_device *fdev)
206c3e70c5eSChao Gao {
207c3e70c5eSChao Gao 	return seamldr_init(&fdev->dev);
208c3e70c5eSChao Gao }
209c3e70c5eSChao Gao 
210c3e70c5eSChao Gao static const struct faux_device_ops tdx_host_ops = {
211c3e70c5eSChao Gao 	.probe		= tdx_host_probe,
212c3e70c5eSChao Gao };
213c3e70c5eSChao Gao 
21459783353SChao Gao static struct faux_device *fdev;
21559783353SChao Gao 
tdx_host_init(void)21659783353SChao Gao static int __init tdx_host_init(void)
21759783353SChao Gao {
21859783353SChao Gao 	if (!x86_match_cpu(tdx_host_ids) || !tdx_get_sysinfo())
21959783353SChao Gao 		return -ENODEV;
22059783353SChao Gao 
221c3e70c5eSChao Gao 	fdev = faux_device_create_with_groups(KBUILD_MODNAME, NULL,
222c3e70c5eSChao Gao 					      &tdx_host_ops,
223c3e70c5eSChao Gao 					      tdx_host_groups);
22459783353SChao Gao 	if (!fdev)
22559783353SChao Gao 		return -ENODEV;
22659783353SChao Gao 
22759783353SChao Gao 	return 0;
22859783353SChao Gao }
22959783353SChao Gao module_init(tdx_host_init);
23059783353SChao Gao 
tdx_host_exit(void)23159783353SChao Gao static void __exit tdx_host_exit(void)
23259783353SChao Gao {
23359783353SChao Gao 	faux_device_destroy(fdev);
23459783353SChao Gao }
23559783353SChao Gao module_exit(tdx_host_exit);
23659783353SChao Gao 
23759783353SChao Gao MODULE_DESCRIPTION("TDX Host Services");
23859783353SChao Gao MODULE_LICENSE("GPL");
239