+ mutex_lock(&tdev->spdm_mutex);
+ while (1) {
+ ret = hsubsys->ops->dev_disconnect(tdev);
+ if (ret <= 0)
+ break;
+
+ ret = tdev->tsm_bus->ops->spdm_forward(&tdev->spdm,
ret);
+ if (ret < 0)
+ break;
+ }
+ mutex_unlock(&tdev->spdm_mutex);
+
+ if (!ret)
+ tdev->connected = false;
+
+ return ret;
+}
+
+static int tsm_dev_status(struct tsm_dev *tdev, struct
tsm_dev_status *s) +{
+ struct tsm_host_subsys *hsubsys = (struct tsm_host_subsys *)
tdev->tsm; +
+ if (WARN_ON(!hsubsys->ops->dev_status))
+ return -EPERM;
+
+ return hsubsys->ops->dev_status(tdev, s);
+}
+
+static int tsm_tdi_measurements_locked(struct tsm_dev *tdev)
+{
+ struct tsm_host_subsys *hsubsys = (struct tsm_host_subsys *)
tdev->tsm;
+ int ret;
+
+ while (1) {
+ ret = hsubsys->ops->dev_measurements(tdev);
+ if (ret <= 0)
+ break;
+
+ ret = tdev->tsm_bus->ops->spdm_forward(&tdev->spdm,
ret);
+ if (ret < 0)
+ break;
+ }
+
+ return ret;
+}
+
+static void tsm_tdi_reclaim(struct tsm_tdi *tdi)
+{
+ struct tsm_dev *tdev = tdi->tdev;
+ struct tsm_host_subsys *hsubsys = (struct tsm_host_subsys *)
tdev->tsm;
+ int ret;
+
+ if (WARN_ON(!hsubsys->ops->tdi_unbind))
+ return;
+
+ mutex_lock(&tdi->tdev->spdm_mutex);
+ while (1) {
+ ret = hsubsys->ops->tdi_unbind(tdi);
+ if (ret <= 0)
+ break;
+
+ ret =
tdi->tdev->tsm_bus->ops->spdm_forward(&tdi->tdev->spdm, ret);
+ if (ret < 0)
+ break;
+ }
+ mutex_unlock(&tdi->tdev->spdm_mutex);
+}
+
+static int tsm_tdi_status(struct tsm_tdi *tdi, void *private_data,
struct tsm_tdi_status *ts) +{
+ struct tsm_tdi_status tstmp = { 0 };
+ struct tsm_dev *tdev = tdi->tdev;
+ struct tsm_host_subsys *hsubsys = (struct tsm_host_subsys *)
tdev->tsm;
+ int ret;
+
+ mutex_lock(&tdi->tdev->spdm_mutex);
+ while (1) {
+ ret = hsubsys->ops->tdi_status(tdi, &tstmp);
+ if (ret <= 0)
+ break;
+
+ ret =
tdi->tdev->tsm_bus->ops->spdm_forward(&tdi->tdev->spdm, ret);
+ if (ret < 0)
+ break;
+ }
+ mutex_unlock(&tdi->tdev->spdm_mutex);
+
+ if (!ret)
+ *ts = tstmp;
+
+ return ret;
+}
+
+static ssize_t tsm_cert_slot_store(struct device *dev, struct
device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct tsm_dev *tdev = container_of(dev, struct tsm_dev,
dev);
+ ssize_t ret = count;
+ unsigned long val;
+
+ if (kstrtoul(buf, 0, &val) < 0)
+ ret = -EINVAL;
+ else
+ tdev->cert_slot = val;
+
+ return ret;
+}
+
+static ssize_t tsm_cert_slot_show(struct device *dev, struct
device_attribute *attr, char *buf) +{
+ struct tsm_dev *tdev = container_of(dev, struct tsm_dev,
dev);
+ ssize_t ret = sysfs_emit(buf, "%u\n", tdev->cert_slot);
+
+ return ret;
+}
+
+static DEVICE_ATTR_RW(tsm_cert_slot);
+
+static ssize_t tsm_dev_connect_store(struct device *dev, struct
device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct tsm_dev *tdev = container_of(dev, struct tsm_dev,
dev);
+ unsigned long val;
+ ssize_t ret = -EIO;
+
+ if (kstrtoul(buf, 0, &val) < 0)
+ ret = -EINVAL;
+ else if (val && !tdev->connected)
+ ret = tsm_dev_connect(tdev);
+ else if (!val && tdev->connected)
+ ret = tsm_dev_reclaim(tdev);
+
+ if (!ret)
+ ret = count;
+
+ return ret;
+}
+
+static ssize_t tsm_dev_connect_show(struct device *dev, struct
device_attribute *attr, char *buf) +{
+ struct tsm_dev *tdev = container_of(dev, struct tsm_dev,
dev);
+ ssize_t ret = sysfs_emit(buf, "%u\n", tdev->connected);
+
+ return ret;
+}
+
+static DEVICE_ATTR_RW(tsm_dev_connect);
+
+static ssize_t tsm_dev_status_show(struct device *dev, struct
device_attribute *attr, char *buf) +{
+ struct tsm_dev *tdev = container_of(dev, struct tsm_dev,
dev);
+ struct tsm_dev_status s = { 0 };
+ int ret = tsm_dev_status(tdev, &s);
+ ssize_t ret1;
+
+ ret1 = sysfs_emit(buf, "ret=%d\n"
+ "ctx_state=%x\n"
+ "tc_mask=%x\n"
+ "certs_slot=%x\n"
+ "device_id=%x:%x.%d\n"
+ "segment_id=%x\n"
+ "no_fw_update=%x\n",
+ ret,
+ s.ctx_state,
+ s.tc_mask,
+ s.certs_slot,
+ (s.device_id >> 8) & 0xff,
+ (s.device_id >> 3) & 0x1f,
+ s.device_id & 0x07,
+ s.segment_id,
+ s.no_fw_update);
+
+ tsm_dev_put(tdev);
+ return ret1;
+}
+
+static DEVICE_ATTR_RO(tsm_dev_status);
+
+static struct attribute *host_dev_attrs[] = {
+ &dev_attr_tsm_cert_slot.attr,
+ &dev_attr_tsm_dev_connect.attr,
+ &dev_attr_tsm_dev_status.attr,
+ NULL,
+};
+static const struct attribute_group host_dev_group = {
+ .attrs = host_dev_attrs,
+};
+
+static ssize_t tsm_tdi_bind_show(struct device *dev, struct
device_attribute *attr, char *buf) +{
+ struct tsm_tdi *tdi = container_of(dev, struct tsm_tdi, dev);
+
+ if (!tdi->kvm)
+ return sysfs_emit(buf, "not bound\n");
+
+ return sysfs_emit(buf, "VM=%p BDFn=%x:%x.%d\n",
+ tdi->kvm,
+ (tdi->guest_rid >> 8) & 0xff,
+ (tdi->guest_rid >> 3) & 0x1f,
+ tdi->guest_rid & 0x07);
+}
+
+static DEVICE_ATTR_RO(tsm_tdi_bind);
+
+static char *spdm_algos_to_str(u64 algos, char *buf, size_t len)
+{
+ size_t n = 0;
+
+ buf[0] = 0;
+#define __ALGO(x) do {
\
+ if ((n < len) && (algos & (1ULL <<
(TSM_TDI_SPDM_ALGOS_##x)))) \
+ n += snprintf(buf + n, len - n, #x"
"); \
+ } while (0)
+
+ __ALGO(DHE_SECP256R1);
+ __ALGO(DHE_SECP384R1);
+ __ALGO(AEAD_AES_128_GCM);
+ __ALGO(AEAD_AES_256_GCM);
+ __ALGO(ASYM_TPM_ALG_RSASSA_3072);
+ __ALGO(ASYM_TPM_ALG_ECDSA_ECC_NIST_P256);
+ __ALGO(ASYM_TPM_ALG_ECDSA_ECC_NIST_P384);
+ __ALGO(HASH_TPM_ALG_SHA_256);
+ __ALGO(HASH_TPM_ALG_SHA_384);
+ __ALGO(KEY_SCHED_SPDM_KEY_SCHEDULE);
+#undef __ALGO
+ return buf;
+}
+
+static const char *tdisp_state_to_str(enum tsm_tdisp_state state)
+{
+ switch (state) {
+#define __ST(x) case TDISP_STATE_##x: return #x
+ case TDISP_STATE_UNAVAIL: return "TDISP state unavailable";
+ __ST(CONFIG_UNLOCKED);
+ __ST(CONFIG_LOCKED);
+ __ST(RUN);
+ __ST(ERROR);
+#undef __ST
+ default: return "unknown";
+ }
+}
+
+static ssize_t tsm_tdi_status_user_show(struct device *dev,
+ struct device_attribute
*attr,
+ char *buf)
+{
+ struct tsm_tdi *tdi = container_of(dev, struct tsm_tdi, dev);
+ struct tsm_dev *tdev = tdi->tdev;
+ struct tsm_host_subsys *hsubsys = (struct tsm_host_subsys *)
tdev->tsm;
+ struct tsm_tdi_status ts = { 0 };
+ char algos[256] = "";
+ unsigned int n, m;
+ int ret;
+
+ ret = tsm_tdi_status(tdi, hsubsys->private_data, &ts);
+ if (ret < 0)
+ return sysfs_emit(buf, "ret=%d\n\n", ret);
+
+ if (!ts.valid)
+ return sysfs_emit(buf, "ret=%d\nstate=%d:%s\n",
+ ret, ts.state,
tdisp_state_to_str(ts.state)); +
+ n = snprintf(buf, PAGE_SIZE,
+ "ret=%d\n"
+ "state=%d:%s\n"
+ "meas_digest_fresh=%x\n"
+ "meas_digest_valid=%x\n"
+ "all_request_redirect=%x\n"
+ "bind_p2p=%x\n"
+ "lock_msix=%x\n"
+ "no_fw_update=%x\n"
+ "cache_line_size=%d\n"
+ "algos=%#llx:%s\n"
+ "report_counter=%lld\n"
+ ,
+ ret,
+ ts.state, tdisp_state_to_str(ts.state),
+ ts.meas_digest_fresh,
+ ts.meas_digest_valid,
+ ts.all_request_redirect,
+ ts.bind_p2p,
+ ts.lock_msix,
+ ts.no_fw_update,
+ ts.cache_line_size,
+ ts.spdm_algos, spdm_algos_to_str(ts.spdm_algos,
algos, sizeof(algos) - 1),
+ ts.intf_report_counter);
+
+ n += snprintf(buf + n, PAGE_SIZE - n, "Certs digest: ");
+ m = hex_dump_to_buffer(ts.certs_digest,
sizeof(ts.certs_digest), 32, 1,
+ buf + n, PAGE_SIZE - n, false);
+ n += min(PAGE_SIZE - n, m);
+ n += snprintf(buf + n, PAGE_SIZE - n, "...\nMeasurements
digest: ");
+ m = hex_dump_to_buffer(ts.meas_digest,
sizeof(ts.meas_digest), 32, 1,
+ buf + n, PAGE_SIZE - n, false);
+ n += min(PAGE_SIZE - n, m);
+ n += snprintf(buf + n, PAGE_SIZE - n, "...\nInterface report
digest: ");
+ m = hex_dump_to_buffer(ts.interface_report_digest,
sizeof(ts.interface_report_digest),
+ 32, 1, buf + n, PAGE_SIZE - n, false);
+ n += min(PAGE_SIZE - n, m);
+ n += snprintf(buf + n, PAGE_SIZE - n, "...\n");
+
+ return n;
+}
+
+static DEVICE_ATTR_RO(tsm_tdi_status_user);
+
+static ssize_t tsm_tdi_status_show(struct device *dev, struct
device_attribute *attr, char *buf) +{
+ struct tsm_tdi *tdi = container_of(dev, struct tsm_tdi, dev);
+ struct tsm_dev *tdev = tdi->tdev;
+ struct tsm_host_subsys *hsubsys = (struct tsm_host_subsys *)
tdev->tsm;
+ struct tsm_tdi_status ts = { 0 };
+ u8 state;
+ int ret;
+
+ ret = tsm_tdi_status(tdi, hsubsys->private_data, &ts);
+ if (ret)
+ return ret;
+
+ state = ts.state;
+ memcpy(buf, &state, sizeof(state));
+
+ return sizeof(state);
+}
+
+static DEVICE_ATTR_RO(tsm_tdi_status);
+
+static struct attribute *host_tdi_attrs[] = {
+ &dev_attr_tsm_tdi_bind.attr,
+ &dev_attr_tsm_tdi_status_user.attr,
+ &dev_attr_tsm_tdi_status.attr,
+ NULL,
+};
+
+static const struct attribute_group host_tdi_group = {
+ .attrs = host_tdi_attrs,
+};
+
+int tsm_tdi_bind(struct tsm_tdi *tdi, u32 guest_rid, int kvmfd)
+{
+ struct tsm_dev *tdev = tdi->tdev;
+ struct tsm_host_subsys *hsubsys = (struct tsm_host_subsys *)
tdev->tsm;
+ struct fd f = fdget(kvmfd);
+ struct kvm *kvm;
+ u64 vmid;
+ int ret;
+
+ if (!fd_file(f))
+ return -EBADF;
+
+ if (!file_is_kvm(fd_file(f))) {
+ ret = -EBADF;
+ goto out_fput;
+ }
+
+ kvm = fd_file(f)->private_data;
+ if (!kvm || !kvm_get_kvm_safe(kvm)) {
+ ret = -EFAULT;
+ goto out_fput;
+ }
+
+ vmid = kvm_arch_tsm_get_vmid(kvm);
+ if (!vmid) {
+ ret = -EFAULT;
+ goto out_kvm_put;
+ }
+
+ if (WARN_ON(!hsubsys->ops->tdi_bind)) {
+ ret = -EPERM;
+ goto out_kvm_put;
+ }
+
+ if (!tdev->connected) {
+ ret = -EIO;
+ goto out_kvm_put;
+ }
+
+ mutex_lock(&tdi->tdev->spdm_mutex);
+ while (1) {
+ ret = hsubsys->ops->tdi_bind(tdi, guest_rid, vmid);
+ if (ret < 0)
+ break;
+
+ if (!ret)
+ break;
+
+ ret =
tdi->tdev->tsm_bus->ops->spdm_forward(&tdi->tdev->spdm, ret);
+ if (ret < 0)
+ break;
+ }
+ mutex_unlock(&tdi->tdev->spdm_mutex);
+
+ if (ret) {
+ tsm_tdi_unbind(tdi);
+ goto out_kvm_put;
+ }
+
+ tdi->guest_rid = guest_rid;
+ tdi->kvm = kvm;
+ ++tdi->tdev->bound;
+ goto out_fput;
+
+out_kvm_put:
+ kvm_put_kvm(kvm);
+out_fput:
+ fdput(f);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(tsm_tdi_bind);
+
+void tsm_tdi_unbind(struct tsm_tdi *tdi)
+{
+ if (tdi->kvm) {
+ tsm_tdi_reclaim(tdi);
+ --tdi->tdev->bound;
+ kvm_put_kvm(tdi->kvm);
+ tdi->kvm = NULL;
+ }
+
+ tdi->guest_rid = 0;
+ tdi->dev.parent->tdi_enabled = false;
+}
+EXPORT_SYMBOL_GPL(tsm_tdi_unbind);
+
+int tsm_guest_request(struct tsm_tdi *tdi, u8 __user *req, size_t
reqlen,
+ u8 __user *res, size_t reslen, int *fw_err)
+{
+ struct tsm_dev *tdev = tdi->tdev;
+ struct tsm_host_subsys *hsubsys = (struct tsm_host_subsys *)
tdev->tsm;
+ int ret;
+
+ if (!hsubsys->ops->guest_request)
+ return -EPERM;
+
+ mutex_lock(&tdi->tdev->spdm_mutex);
+ while (1) {
+ ret = hsubsys->ops->guest_request(tdi, req, reqlen,
+ res, reslen,
fw_err);
+ if (ret <= 0)
+ break;
+
+ ret =
tdi->tdev->tsm_bus->ops->spdm_forward(&tdi->tdev->spdm,
+ ret);
+ if (ret < 0)
+ break;
+ }
+
+ mutex_unlock(&tdi->tdev->spdm_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(tsm_guest_request);
+
+struct tsm_host_subsys *tsm_host_register(struct device *parent,
+ struct tsm_hv_ops *hvops,
+ void *private_data)
+{
+ struct tsm_subsys *subsys = tsm_register(parent,
sizeof(struct tsm_host_subsys),
+ &host_dev_group,
&host_tdi_group,
+
tsm_tdi_measurements_locked);
+ struct tsm_host_subsys *hsubsys;
+
+ hsubsys = (struct tsm_host_subsys *) subsys;
+
+ if (IS_ERR(hsubsys))
+ return hsubsys;
+
+ hsubsys->ops = hvops;
+ hsubsys->private_data = private_data;
+
+ return hsubsys;
+}
+EXPORT_SYMBOL_GPL(tsm_host_register);
+
+static int __init tsm_init(void)
+{
+ int ret = 0;
+
+ pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n");
+
+ return ret;
+}
+
+static void __exit tsm_exit(void)
+{
+ pr_info(DRIVER_DESC " version: " DRIVER_VERSION "
shutdown\n"); +}
+
+module_init(tsm_init);
+module_exit(tsm_exit);
+
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
diff --git a/drivers/virt/coco/tsm.c b/drivers/virt/coco/tsm.c
new file mode 100644
index 000000000000..b6235d1210ca
--- /dev/null
+++ b/drivers/virt/coco/tsm.c
@@ -0,0 +1,636 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/module.h>
+#include <linux/tsm.h>
+
+#define DRIVER_VERSION "0.1"
+#define DRIVER_AUTHOR "aik@xxxxxxx"
+#define DRIVER_DESC "TSM library"
+
+static struct class *tsm_class, *tdev_class, *tdi_class;
+
+/* snprintf does not check for the size, hence this wrapper */
+static int tsmprint(char *buf, size_t size, const char *fmt, ...)
+{
+ va_list args;
+ size_t i;
+
+ if (!size)
+ return 0;
+
+ va_start(args, fmt);
+ i = vsnprintf(buf, size, fmt, args);
+ va_end(args);
+
+ return min(i, size);
+}
+
+struct tsm_blob *tsm_blob_new(void *data, size_t len)
+{
+ struct tsm_blob *b;
+
+ if (!len || !data)
+ return NULL;
+
+ b = kzalloc(sizeof(*b) + len, GFP_KERNEL);
+ if (!b)
+ return NULL;
+
+ b->data = (void *)b + sizeof(*b);
+ b->len = len;
+ memcpy(b->data, data, len);
+
+ return b;
+}
+EXPORT_SYMBOL_GPL(tsm_blob_new);
+
+static int match_class(struct device *dev, const void *data)
+{
+ return dev->class == data;
+}
+
+struct tsm_dev *tsm_dev_get(struct device *parent)
+{
+ struct device *dev = device_find_child(parent, tdev_class,
match_class); +
+ if (!dev) {
+ dev = device_find_child(parent, tdi_class,
match_class);
+ if (dev) {
+ struct tsm_tdi *tdi = container_of(dev,
struct tsm_tdi, dev); +
+ dev = &tdi->tdev->dev;
+ }
+ }
+
+ if (!dev)
+ return NULL;
+
+ /* device_find_child() does get_device() */
+ return container_of(dev, struct tsm_dev, dev);
+}
+EXPORT_SYMBOL_GPL(tsm_dev_get);
+
+void tsm_dev_put(struct tsm_dev *tdev)
+{
+ put_device(&tdev->dev);
+}
+EXPORT_SYMBOL_GPL(tsm_dev_put);
+
+struct tsm_tdi *tsm_tdi_get(struct device *parent)
+{
+ struct device *dev = device_find_child(parent, tdi_class,
match_class); +
+ if (!dev)
+ return NULL;
+
+ /* device_find_child() does get_device() */
+ return container_of(dev, struct tsm_tdi, dev);
+}
+EXPORT_SYMBOL_GPL(tsm_tdi_get);
+
+void tsm_tdi_put(struct tsm_tdi *tdi)
+{
+ put_device(&tdi->dev);
+}
+EXPORT_SYMBOL_GPL(tsm_tdi_put);
+
+static ssize_t blob_show(struct tsm_blob *blob, char *buf)
+{
+ unsigned int n, m;
+ size_t sz = PAGE_SIZE - 1;
+
+ if (!blob)
+ return sysfs_emit(buf, "none\n");
+
+ n = tsmprint(buf, sz, "%lu %u\n", blob->len);
+ m = hex_dump_to_buffer(blob->data, blob->len, 32, 1,
+ buf + n, sz - n, false);
+ n += min(sz - n, m);
+ n += tsmprint(buf + n, sz - n, "...\n");
+ return n;
+}
+
+static ssize_t tsm_certs_gen(struct tsm_blob *certs, char *buf,
size_t len) +{
+ struct spdm_certchain_block_header *h;
+ unsigned int n = 0, m, i, off, o2;
+ u8 *p;
+
+ for (i = 0, off = 0; off < certs->len; ++i) {
+ h = (struct spdm_certchain_block_header *) ((u8
*)certs->data + off);
+ if (WARN_ON_ONCE(h->length > certs->len - off))
+ return 0;
+
+ n += tsmprint(buf + n, len - n, "[%d] len=%d:\n", i,
h->length); +
+ for (o2 = 0, p = (u8 *)&h[1]; o2 < h->length; o2 +=
32) {
+ m = hex_dump_to_buffer(p + o2, h->length -
o2, 32, 1,
+ buf + n, len - n,
true);
+ n += min(len - n, m);
+ n += tsmprint(buf + n, len - n, "\n");
+ }
+
+ off += h->length; /* Includes the header */
+ }
+
+ return n;
+}
+
+
+void tsm_dev_free(struct tsm_dev *tdev)
+{
+ dev_notice(&tdev->dev, "Freeing tdevice\n");
+ device_unregister(&tdev->dev);
+}
+EXPORT_SYMBOL_GPL(tsm_dev_free);
+
+int tsm_create_link(struct tsm_subsys *tsm, struct device *dev,
const char *name) +{
+ return sysfs_create_link(&tsm->dev.kobj, &dev->kobj, name);
+}
+EXPORT_SYMBOL_GPL(tsm_create_link);
+
+void tsm_remove_link(struct tsm_subsys *tsm, const char *name)
+{
+ sysfs_remove_link(&tsm->dev.kobj, name);
+}
+EXPORT_SYMBOL_GPL(tsm_remove_link);
+
+static struct tsm_subsys *alloc_tsm_subsys(struct device *parent,
size_t size) +{
+ struct tsm_subsys *subsys;
+ struct device *dev;
+
+ if (WARN_ON_ONCE(size < sizeof(*subsys)))
+ return ERR_PTR(-EINVAL);
+
+ subsys = kzalloc(size, GFP_KERNEL);
+ if (!subsys)
+ return ERR_PTR(-ENOMEM);
+
+ dev = &subsys->dev;
+ dev->parent = parent;
+ dev->class = tsm_class;
+ device_initialize(dev);
+ return subsys;
+}
+
+struct tsm_subsys *tsm_register(struct device *parent, size_t size,
+ const struct attribute_group
*tdev_ag,
+ const struct attribute_group *tdi_ag,
+ int (*update_measurements)(struct
tsm_dev *tdev)) +{
+ struct tsm_subsys *subsys = alloc_tsm_subsys(parent, size);
+ struct device *dev;
+ int rc;
+
+ if (IS_ERR(subsys))
+ return subsys;
+
+ dev = &subsys->dev;
+ rc = dev_set_name(dev, "tsm0");
+ if (rc)
+ return ERR_PTR(rc);
+
+ rc = device_add(dev);
+ if (rc)
+ return ERR_PTR(rc);
+
+ subsys->tdev_groups[0] = &dev_group;
+ subsys->tdev_groups[1] = tdev_ag;
+ subsys->tdi_groups[0] = &tdi_group;
+ subsys->tdi_groups[1] = tdi_ag;
+ subsys->update_measurements = update_measurements;
+
+ return subsys;
+}
+EXPORT_SYMBOL_GPL(tsm_register);
+
+void tsm_unregister(struct tsm_subsys *subsys)
+{
+ device_unregister(&subsys->dev);
+}
+EXPORT_SYMBOL_GPL(tsm_unregister);
+
+static void tsm_release(struct device *dev)
+{
+ struct tsm_subsys *tsm = container_of(dev, typeof(*tsm),
dev); +
+ dev_info(&tsm->dev, "Releasing TSM\n");
+ kfree(tsm);
+}
+
+static void tdev_release(struct device *dev)
+{
+ struct tsm_dev *tdev = container_of(dev, typeof(*tdev), dev);
+
+ dev_info(&tdev->dev, "Releasing %s TDEV\n",
+ tdev->connected ? "connected":"disconnected");
+ kfree(tdev);
+}
+
+static void tdi_release(struct device *dev)
+{
+ struct tsm_tdi *tdi = container_of(dev, typeof(*tdi), dev);
+
+ dev_info(&tdi->dev, "Releasing %s TDI\n", tdi->kvm ? "bound"
: "unbound");
+ sysfs_remove_link(&tdi->dev.parent->kobj, "tsm_dev");
+ kfree(tdi);
+}
+
+static int __init tsm_init(void)
+{
+ int ret = 0;
+
+ pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n");
+
+ tsm_class = class_create("tsm");
+ if (IS_ERR(tsm_class))
+ return PTR_ERR(tsm_class);
+ tsm_class->dev_release = tsm_release;
+
+ tdev_class = class_create("tsm-dev");
+ if (IS_ERR(tdev_class))
+ return PTR_ERR(tdev_class);
+ tdev_class->dev_release = tdev_release;
+
+ tdi_class = class_create("tsm-tdi");
+ if (IS_ERR(tdi_class))
+ return PTR_ERR(tdi_class);
+ tdi_class->dev_release = tdi_release;
+
+ return ret;
+}
+
+static void __exit tsm_exit(void)
+{
+ pr_info(DRIVER_DESC " version: " DRIVER_VERSION "
shutdown\n");
+ class_destroy(tdi_class);
+ class_destroy(tdev_class);
+ class_destroy(tsm_class);
+}
+
+module_init(tsm_init);
+module_exit(tsm_exit);
+
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
diff --git a/Documentation/virt/coco/tsm.rst
b/Documentation/virt/coco/tsm.rst new file mode 100644
index 000000000000..7cb5f1862492
--- /dev/null
+++ b/Documentation/virt/coco/tsm.rst
@@ -0,0 +1,99 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+What it is
+==========
+
+This is for PCI passthrough in confidential computing (CoCo:
SEV-SNP, TDX, CoVE). +Currently passing through PCI devices to a CoCo
VM uses SWIOTLB to pre-shared +memory buffers.
+
+PCIe IDE (Integrity and Data Encryption) and TDISP (TEE Device
Interface Security +Protocol) are protocols to enable encryption over
PCIe link and DMA to encrypted +memory. This doc is focused to DMAing
to encrypted VM, the encrypted host memory is +out of scope.
+
+
+Protocols
+=========
+
+PCIe r6 DOE is a mailbox protocol to read/write object from/to
device. +Objects are of plain SPDM or secure SPDM type. SPDM is
responsible for authenticating +devices, creating a secure link
between a device and TSM. +IDE_KM manages PCIe link encryption keys,
it works on top of secure SPDM. +TDISP manages a passed through PCI
function state, also works on top on secure SPDM. +Additionally, PCIe
defines IDE capability which provides the host OS a way +to enable
streams on the PCIe link. +
+
+TSM modules
+===========
+
+TSM is a library, shared among hosts and guests.
+
+TSM-HOST contains host-specific bits, controls IDE and TDISP
bindings. +
+TSM-GUEST contains guest-specific bits, controls enablement of
encrypted DMA and +MMIO.
+
+TSM-PCI is PCI binding for TSM, calls the above libraries for
setting up +sysfs nodes and corresponding data structures.
+
+
+Flow
+====
+
+At the boot time the tsm.ko scans the PCI bus to find and setup
TDISP-cabable +devices; it also listens to hotplug events. If setup
was successful, tsm-prefixed +nodes will appear in sysfs.
+
+Then, the user enables IDE by writing to
/sys/bus/pci/devices/0000:e1:00.0/tsm_dev_connect +and this is how
PCIe encryption is enabled. +
+To pass the device through, a modifined VMM is required.
+
+In the VM, the same tsm.ko loads. In addition to the host's setup,
the VM wants +to receive the report and enable secure DMA or/and
secure MMIO, via some VM<->HV +protocol (such as AMD GHCB). Once this
is done, a VM can access validated MMIO +with the Cbit set and the
device can DMA to encrypted memory. +
+The sysfs example from a host with a TDISP capable device:
+
+~> find /sys -iname "*tsm*"
+/sys/class/tsm-tdi
+/sys/class/tsm
+/sys/class/tsm/tsm0
+/sys/class/tsm-dev
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.1/tsm_dev
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.1/tsm-tdi
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.1/tsm-tdi/tdi:0000:e1:00.1/tsm_tdi_bind
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.1/tsm-tdi/tdi:0000:e1:00.1/tsm_tdi_status
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.1/tsm-tdi/tdi:0000:e1:00.1/tsm_tdi_status_user
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.1/tsm-tdi/tdi:0000:e1:00.1/tsm_report_user
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.1/tsm-tdi/tdi:0000:e1:00.1/tsm_report
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm_dev
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm-tdi
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm-tdi/tdi:0000:e1:00.0/tsm_tdi_bind
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm-tdi/tdi:0000:e1:00.0/tsm_tdi_status
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm-tdi/tdi:0000:e1:00.0/tsm_tdi_status_user
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm-tdi/tdi:0000:e1:00.0/tsm_report_user
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm-tdi/tdi:0000:e1:00.0/tsm_report
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm-dev
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm-dev/tdev:0000:e1:00.0/tsm_certs
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm-dev/tdev:0000:e1:00.0/tsm_nonce
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm-dev/tdev:0000:e1:00.0/tsm_meas_user
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm-dev/tdev:0000:e1:00.0/tsm_certs_user
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm-dev/tdev:0000:e1:00.0/tsm_dev_status
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm-dev/tdev:0000:e1:00.0/tsm_cert_slot
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm-dev/tdev:0000:e1:00.0/tsm_dev_connect
+/sys/devices/pci0000:e0/0000:e0:01.1/0000:e1:00.0/tsm-dev/tdev:0000:e1:00.0/tsm_meas
+/sys/devices/pci0000:a0/0000:a0:07.1/0000:a9:00.5/tsm
+/sys/devices/pci0000:a0/0000:a0:07.1/0000:a9:00.5/tsm/tsm0
+
+
+References
+==========
+
+[1] TEE Device Interface Security Protocol - TDISP - v2022-07-27
+https://members.pcisig.com/wg/PCI-SIG/document/18268?downloadRevision=21500
+[2] Security Protocol and Data Model (SPDM)
+https://www.dmtf.org/sites/default/files/standards/documents/DSP0274_1.2.1.pdf
diff --git a/drivers/virt/coco/Kconfig b/drivers/virt/coco/Kconfig
index 819a97e8ba99..e4385247440b 100644
--- a/drivers/virt/coco/Kconfig
+++ b/drivers/virt/coco/Kconfig
@@ -3,6 +3,18 @@
# Confidential computing related collateral
#
+config TSM
+ tristate "Platform support for TEE Device Interface Security
Protocol (TDISP)"
+ default m
+ depends on AMD_MEM_ENCRYPT
+ select PCI_DOE
+ select PCI_IDE
+ help
+ Add a common place for user visible platform support for
PCIe TDISP.
+ TEE Device Interface Security Protocol (TDISP) from
PCI-SIG,
+
https://pcisig.com/tee-device-interface-security-protocol-tdisp
+ This is prerequisite for host and guest support.
+
source "drivers/virt/coco/efi_secret/Kconfig"
source "drivers/virt/coco/pkvm-guest/Kconfig"
@@ -14,3 +26,5 @@ source "drivers/virt/coco/tdx-guest/Kconfig"
source "drivers/virt/coco/arm-cca-guest/Kconfig"
source "drivers/virt/coco/guest/Kconfig"
+
+source "drivers/virt/coco/host/Kconfig"
diff --git a/drivers/virt/coco/host/Kconfig
b/drivers/virt/coco/host/Kconfig new file mode 100644
index 000000000000..3bde38b91fd4
--- /dev/null
+++ b/drivers/virt/coco/host/Kconfig
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# TSM (TEE Security Manager) Common infrastructure and host drivers
+#
+config TSM_HOST
+ tristate