[RFC PATCH 07/21] pci/tdisp: Introduce tsm module

Alexey Kardashevskiy
2024-08-23T13:29:30Z

The module responsibilities are:
1. detect TEE support in a device and create nodes in the device's sysfs
entry;
2. allow binding a PCI device to a VM for passing it through in a trusted
manner;
3. store measurements/certificates/reports and provide access to those for
the userspace via sysfs.

This relies on the platform to register a set of callbacks,
for both host and guest.

And tdi_enabled in the device struct.

Signed-off-by: Alexey Kardashevskiy <aik@amd.com>
---
drivers/virt/coco/Makefile | 1 +
include/linux/device.h | 5 +
include/linux/tsm.h | 263 ++++
drivers/virt/coco/tsm.c | 1336 ++++++++++++++++++++
Documentation/virt/coco/tsm.rst | 62 +
drivers/virt/coco/Kconfig | 11 +
6 files changed, 1678 insertions(+)

diff --git a/drivers/virt/coco/Makefile b/drivers/virt/coco/Makefile
index 75defec514f8..5d1aefb62714 100644
--- a/drivers/virt/coco/Makefile
+++ b/drivers/virt/coco/Makefile
@@ -3,6 +3,7 @@
# Confidential computing related collateral
#
obj-$(CONFIG_TSM_REPORTS) += tsm-report.o
+obj-$(CONFIG_TSM) += tsm.o
obj-$(CONFIG_EFI_SECRET) += efi_secret/
obj-$(CONFIG_SEV_GUEST) += sev-guest/
obj-$(CONFIG_INTEL_TDX_GUEST) += tdx-guest/
diff --git a/include/linux/device.h b/include/linux/device.h
index 34eb20f5966f..bb58ed1fb8da 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -45,6 +45,7 @@ struct fwnode_handle;
struct iommu_group;
struct dev_pin_info;
struct dev_iommu;
+struct tsm_tdi;
struct msi_device_data;

/**
@@ -801,6 +802,7 @@ struct device {
void (*release)(struct device *dev);
struct iommu_group *iommu_group;
struct dev_iommu *iommu;
+ struct tsm_tdi *tdi;

struct device_physical_location *physical_location;

@@ -822,6 +824,9 @@ struct device {
#ifdef CONFIG_DMA_NEED_SYNC
bool dma_skip_sync:1;
#endif
+#if defined(CONFIG_TSM) || defined(CONFIG_TSM_MODULE)
+ bool tdi_enabled:1;
+#endif
};

/**
diff --git a/include/linux/tsm.h b/include/linux/tsm.h
new file mode 100644
index 000000000000..d48eceaf5bc0
--- /dev/null
+++ b/include/linux/tsm.h
@@ -0,0 +1,263 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef LINUX_TSM_H
+#define LINUX_TSM_H
+
+#include <linux/cdev.h>
+
+/* SPDM control structure for DOE */
+struct tsm_spdm {
+ unsigned long req_len;
+ void *req;
+ unsigned long rsp_len;
+ void *rsp;
+
+ struct pci_doe_mb *doe_mb;
+ struct pci_doe_mb *doe_mb_secured;
+};
+
+/* Data object for measurements/certificates/attestationreport */
+struct tsm_blob {
+ void *data;
+ size_t len;
+ struct kref kref;
+ void (*release)(struct tsm_blob *b);
+};
+
+struct tsm_blob *tsm_blob_new(void *data, size_t len, void (*release)(struct tsm_blob *b));
+struct tsm_blob *tsm_blob_get(struct tsm_blob *b);
+void tsm_blob_put(struct tsm_blob *b);
+
+/**
+ * struct tdisp_interface_id - TDISP INTERFACE_ID Definition
+ *
+ * @function_id: Identifies the function of the device hosting the TDI
+ * 15:0: @rid: Requester ID
+ * 23:16: @rseg: Requester Segment (Reserved if Requester Segment Valid is Clear)
+ * 24: @rseg_valid: Requester Segment Valid
+ * 31:25 – Reserved
+ * 8B - Reserved
+ */
+struct tdisp_interface_id {
+ union {
+ struct {
+ u32 function_id;
+ u8 reserved[8];
+ };
+ struct {
+ u16 rid;
+ u8 rseg;
+ u8 rseg_valid:1;
+ };
+ };
+} __packed;
+
+/*
+ * Measurement block as defined in SPDM DSP0274.
+ */
+struct spdm_measurement_block_header {
+ u8 index;
+ u8 spec; /* MeasurementSpecification */
+ u16 size;
+} __packed;
+
+struct dmtf_measurement_block_header {
+ u8 type; /* DMTFSpecMeasurementValueType */
+ u16 size; /* DMTFSpecMeasurementValueSize */
+} __packed;
+
+struct dmtf_measurement_block_device_mode {
+ u32 opmode_cap; /* OperationalModeCapabilties */
+ u32 opmode_sta; /* OperationalModeState */
+ u32 devmode_cap; /* DeviceModeCapabilties */
+ u32 devmode_sta; /* DeviceModeState */
+} __packed;
+
+struct spdm_certchain_block_header {
+ u16 length;
+ u16 reserved;
+} __packed;
+
+/*
+ * TDI Report Structure as defined in TDISP.
+ */
+struct tdi_report_header {
+ union {
+ u16 interface_info;
+ struct {
+ u16 no_fw_update:1; /* fw updates not permitted in CONFIG_LOCKED or RUN */
+ u16 dma_no_pasid:1; /* TDI generates DMA requests without PASID */
+ u16 dma_pasid:1; /* TDI generates DMA requests with PASID */
+ u16 ats:1; /* ATS supported and enabled for the TDI */
+ u16 prs:1; /* PRS supported and enabled for the TDI */
+ u16 reserved1:11;
+ };
+ };
+ u16 reserved2;
+ u16 msi_x_message_control;
+ u16 lnr_control;
+ u32 tph_control;
+ u32 mmio_range_count;
+} __packed;
+
+/*
+ * Each MMIO Range of the TDI is reported with the MMIO reporting offset added.
+ * Base and size in units of 4K pages
+ */
+struct tdi_report_mmio_range {
+ u64 first_page; /* First 4K page with offset added */
+ u32 num; /* Number of 4K pages in this range */
+ union {
+ u32 range_attributes;
+ struct {
+ u32 msix_table:1;
+ u32 msix_pba:1;
+ u32 is_non_tee_mem:1;
+ u32 is_mem_attr_updatable:1;
+ u32 reserved:12;
+ u32 range_id:16;
+ };
+ };
+} __packed;
+
+struct tdi_report_footer {
+ u32 device_specific_info_len;
+ u8 device_specific_info[];
+} __packed;
+
+#define TDI_REPORT_HDR(rep) ((struct tdi_report_header *) ((rep)->data))
+#define TDI_REPORT_MR_NUM(rep) (TDI_REPORT_HDR(rep)->mmio_range_count)
+#define TDI_REPORT_MR_OFF(rep) ((struct tdi_report_mmio_range *) (TDI_REPORT_HDR(rep) + 1))
+#define TDI_REPORT_MR(rep, rangeid) TDI_REPORT_MR_OFF(rep)[rangeid]
+#define TDI_REPORT_FTR(rep) ((struct tdi_report_footer *) &TDI_REPORT_MR((rep), \
+ TDI_REPORT_MR_NUM(rep)))
+
+/* Physical device descriptor responsible for IDE/TDISP setup */
+struct tsm_dev {
+ struct kref kref;
+ const struct attribute_group *ag;
+ struct pci_dev *pdev; /* Physical PCI function #0 */
+ struct tsm_spdm spdm;
+ struct mutex spdm_mutex;
+
+ u8 tc_mask;
+ u8 cert_slot;
+ u8 connected;
+ struct {
+ u8 enabled:1;
+ u8 enable:1;
+ u8 def:1;
+ u8 dev_ide_cfg:1;
+ u8 dev_tee_limited:1;
+ u8 rootport_ide_cfg:1;
+ u8 rootport_tee_limited:1;
+ u8 id;
+ } selective_ide[256];
+ bool ide_pre;
+
+ struct tsm_blob *meas;
+ struct tsm_blob *certs;
+
+ void *data; /* Platform specific data */
+};
+
+/* PCI function for passing through, can be the same as tsm_dev::pdev */
+struct tsm_tdi {
+ const struct attribute_group *ag;
+ struct pci_dev *pdev;
+ struct tsm_dev *tdev;
+
+ u8 rseg;
+ u8 rseg_valid;
+ bool validated;
+
+ struct tsm_blob *report;
+
+ void *data; /* Platform specific data */
+
+ u64 vmid;
+ u32 asid;
+ u16 guest_rid; /* BDFn of PCI Fn in the VM */
+};
+
+struct tsm_dev_status {
+ u8 ctx_state;
+ u8 tc_mask;
+ u8 certs_slot;
+ u16 device_id;
+ u16 segment_id;
+ u8 no_fw_update;
+ u16 ide_stream_id[8];
+};
+
+enum tsm_spdm_algos {
+ TSM_TDI_SPDM_ALGOS_DHE_SECP256R1,
+ TSM_TDI_SPDM_ALGOS_DHE_SECP384R1,
+ TSM_TDI_SPDM_ALGOS_AEAD_AES_128_GCM,
+ TSM_TDI_SPDM_ALGOS_AEAD_AES_256_GCM,
+ TSM_TDI_SPDM_ALGOS_ASYM_TPM_ALG_RSASSA_3072,
+ TSM_TDI_SPDM_ALGOS_ASYM_TPM_ALG_ECDSA_ECC_NIST_P256,
+ TSM_TDI_SPDM_ALGOS_ASYM_TPM_ALG_ECDSA_ECC_NIST_P384,
+ TSM_TDI_SPDM_ALGOS_HASH_TPM_ALG_SHA_256,
+ TSM_TDI_SPDM_ALGOS_HASH_TPM_ALG_SHA_384,
+ TSM_TDI_SPDM_ALGOS_KEY_SCHED_SPDM_KEY_SCHEDULE,
+};
+
+enum tsm_tdisp_state {
+ TDISP_STATE_UNAVAIL,
+ TDISP_STATE_CONFIG_UNLOCKED,
+ TDISP_STATE_CONFIG_LOCKED,
+ TDISP_STATE_RUN,
+ TDISP_STATE_ERROR,
+};
+
+struct tsm_tdi_status {
+ bool valid;
+ u8 meas_digest_fresh:1;
+ u8 meas_digest_valid:1;
+ u8 all_request_redirect:1;
+ u8 bind_p2p:1;
+ u8 lock_msix:1;
+ u8 no_fw_update:1;
+ u16 cache_line_size;
+ u64 spdm_algos; /* Bitmask of tsm_spdm_algos */
+ u8 certs_digest[48];
+ u8 meas_digest[48];
+ u8 interface_report_digest[48];
+
+ /* HV only */
+ struct tdisp_interface_id id;
+ u8 guest_report_id[16];
+ enum tsm_tdisp_state state;
+};
+
+struct tsm_ops {
+ /* HV hooks */
+ int (*dev_connect)(struct tsm_dev *tdev, void *private_data);
+ int (*dev_reclaim)(struct tsm_dev *tdev, void *private_data);
+ int (*dev_status)(struct tsm_dev *tdev, void *private_data, struct tsm_dev_status *s);
+ int (*ide_refresh)(struct tsm_dev *tdev, void *private_data);
+ int (*tdi_bind)(struct tsm_tdi *tdi, u32 bdfn, u64 vmid, u32 asid, void *private_data);
+ int (*tdi_reclaim)(struct tsm_tdi *tdi, void *private_data);
+
+ int (*guest_request)(struct tsm_tdi *tdi, u32 guest_rid, u64 vmid, void *req_data,
+ enum tsm_tdisp_state *state, void *private_data);
+
+ /* VM hooks */
+ int (*tdi_validate)(struct tsm_tdi *tdi, bool invalidate, void *private_data);
+
+ /* HV and VM hooks */
+ int (*tdi_status)(struct tsm_tdi *tdi, void *private_data, struct tsm_tdi_status *ts);
+};
+
+void tsm_set_ops(struct tsm_ops *ops, void *private_data);
+struct tsm_tdi *tsm_tdi_get(struct device *dev);
+int tsm_tdi_bind(struct tsm_tdi *tdi, u32 guest_rid, u64 vmid, u32 asid);
+void tsm_tdi_unbind(struct tsm_tdi *tdi);
+int tsm_guest_request(struct tsm_tdi *tdi, enum tsm_tdisp_state *state, void *req_data);
+struct tsm_tdi *tsm_tdi_find(u32 guest_rid, u64 vmid);
+
+int pci_dev_tdi_validate(struct pci_dev *pdev);
+ssize_t tsm_report_gen(struct tsm_blob *report, char *b, size_t len);
+
+#endif /* LINUX_TSM_H */
diff --git a/drivers/virt/coco/tsm.c b/drivers/virt/coco/tsm.c
new file mode 100644
index 000000000000..e90455a0267f
--- /dev/null
+++ b/drivers/virt/coco/tsm.c
@@ -0,0 +1,1336 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pci-doe.h>
+#include <linux/pci-ide.h>
+#include <linux/file.h>
+#include <linux/fdtable.h>
+#include <linux/tsm.h>
+#include <linux/kvm_host.h>
+
+#define DRIVER_VERSION "0.1"
+#define DRIVER_AUTHOR "aik@amd.com"
+#define DRIVER_DESC "TSM TDISP driver"
+
+static struct {
+ struct tsm_ops *ops;
+ void *private_data;
+
+ uint tc_mask;
+ uint cert_slot;
+ bool physfn;
+} tsm;
+
+module_param_named(tc_mask, tsm.tc_mask, uint, 0644);
+MODULE_PARM_DESC(tc_mask, "Mask of traffic classes enabled in the device");
+
+module_param_named(cert_slot, tsm.cert_slot, uint, 0644);
+MODULE_PARM_DESC(cert_slot, "Slot number of the certificate requested for constructing the SPDM session");
+
+module_param_named(physfn, tsm.physfn, bool, 0644);
+MODULE_PARM_DESC(physfn, "Allow TDI on SR IOV of a physical function");
+
+struct tsm_blob *tsm_blob_new(void *data, size_t len, void (*release)(struct tsm_blob *b))
+{
+ 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;
+ b->release = release;
+ memcpy(b->data, data, len);
+ kref_init(&b->kref);
+
+ return b;
+}
+EXPORT_SYMBOL_GPL(tsm_blob_new);
+
+static void tsm_blob_release(struct kref *kref)
+{
+ struct tsm_blob *b = container_of(kref, struct tsm_blob, kref);
+
+ b->release(b);
+ kfree(b);
+}
+
+struct tsm_blob *tsm_blob_get(struct tsm_blob *b)
+{
+ if (!b)
+ return NULL;
+
+ if (!kref_get_unless_zero(&b->kref))
+ return NULL;
+
+ return b;
+}
+EXPORT_SYMBOL_GPL(tsm_blob_get);
+
+void tsm_blob_put(struct tsm_blob *b)
+{
+ if (!b)
+ return;
+
+ kref_put(&b->kref, tsm_blob_release);
+}
+EXPORT_SYMBOL_GPL(tsm_blob_put);
+
+static struct tsm_dev *tsm_dev_get(struct device *dev)
+{
+ struct tsm_tdi *tdi = dev->tdi;
+
+ if (!tdi || !tdi->tdev || !kref_get_unless_zero(&tdi->tdev->kref))
+ return NULL;
+
+ return tdi->tdev;
+}
+
+static void tsm_dev_free(struct kref *kref);
+static void tsm_dev_put(struct tsm_dev *tdev)
+{
+ kref_put(&tdev->kref, tsm_dev_free);
+}
+
+struct tsm_tdi *tsm_tdi_get(struct device *dev)
+{
+ struct tsm_tdi *tdi = dev->tdi;
+
+ return tdi;
+}
+EXPORT_SYMBOL_GPL(tsm_tdi_get);
+
+static int spdm_forward(struct tsm_spdm *spdm, u8 type)
+{
+ struct pci_doe_mb *doe_mb;
+ int rc;
+
+ if (type == PCI_DOE_PROTOCOL_SECURED_CMA_SPDM)
+ doe_mb = spdm->doe_mb_secured;
+ else if (type == PCI_DOE_PROTOCOL_CMA_SPDM)
+ doe_mb = spdm->doe_mb;
+ else
+ return -EINVAL;
+
+ if (!doe_mb)
+ return -EFAULT;
+
+ rc = pci_doe(doe_mb, PCI_VENDOR_ID_PCI_SIG, type,
+ spdm->req, spdm->req_len, spdm->rsp, spdm->rsp_len);
+ if (rc >= 0)
+ spdm->rsp_len = rc;
+
+ return rc;
+}
+
+/*
+ * Enables IDE between the RC and the device.
+ * TEE Limited, IDE Cfg space and other bits are hardcoded
+ * as this is a sketch.
+ */
+static int tsm_set_sel_ide(struct tsm_dev *tdev)
+{
+ struct pci_dev *rootport;
+ bool printed = false;
+ unsigned int i;
+ int ret = 0;
+
+ rootport = tdev->pdev->bus->self;
+ for (i = 0; i < ARRAY_SIZE(tdev->selective_ide); ++i) {
+ if (!tdev->selective_ide[i].enable)
+ continue;
+
+ if (!printed) {
+ pci_info(rootport, "Configuring IDE with %s\n",
+ pci_name(tdev->pdev));
+ printed = true;
+ }
+ WARN_ON_ONCE(tdev->selective_ide[i].enabled);
+
+ ret = pci_ide_set_sel_rid_assoc(tdev->pdev, i, true, 0, 0, 0xFFFF);
+ if (ret)
+ pci_warn(tdev->pdev,
+ "Failed configuring SelectiveIDE#%d rid1 with %d\n",
+ i, ret);
+ ret = pci_ide_set_sel_addr_assoc(tdev->pdev, i, 0/* RID# */, true,
+ 0, 0xFFFFFFFFFFF00000ULL);
+ if (ret)
+ pci_warn(tdev->pdev,
+ "Failed configuring SelectiveIDE#%d RID#0 with %d\n",
+ i, ret);
+
+ ret = pci_ide_set_sel(tdev->pdev, i,
+ tdev->selective_ide[i].id,
+ tdev->selective_ide[i].enable,
+ tdev->selective_ide[i].def,
+ tdev->selective_ide[i].dev_tee_limited,
+ tdev->selective_ide[i].dev_ide_cfg);
+ if (ret) {
+ pci_warn(tdev->pdev,
+ "Failed configuring SelectiveIDE#%d with %d\n",
+ i, ret);
+ break;
+ }
+
+ ret = pci_ide_set_sel_rid_assoc(rootport, i, true, 0, 0, 0xFFFF);
+ if (ret)
+ pci_warn(rootport,
+ "Failed configuring SelectiveIDE#%d rid1 with %d\n",
+ i, ret);
+
+ ret = pci_ide_set_sel(rootport, i,
+ tdev->selective_ide[i].id,
+ tdev->selective_ide[i].enable,
+ tdev->selective_ide[i].def,
+ tdev->selective_ide[i].rootport_tee_limited,
+ tdev->selective_ide[i].rootport_ide_cfg);
+ if (ret)
+ pci_warn(rootport,
+ "Failed configuring SelectiveIDE#%d with %d\n",
+ i, ret);
+
+ tdev->selective_ide[i].enabled = 1;
+ }
+
+ return ret;
+}
+
+static void tsm_unset_sel_ide(struct tsm_dev *tdev)
+{
+ struct pci_dev *rootport = tdev->pdev->bus->self;
+ bool printed = false;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(tdev->selective_ide); ++i) {
+ if (!tdev->selective_ide[i].enabled)
+ continue;
+
+ if (!printed) {
+ pci_info(rootport, "Deconfiguring IDE with %s\n", pci_name(tdev->pdev));
+ printed = true;
+ }
+
+ pci_ide_set_sel(rootport, i, 0, 0, 0, false, false);
+ pci_ide_set_sel(tdev->pdev, i, 0, 0, 0, false, false);
+ tdev->selective_ide[i].enabled = 0;
+ }
+}
+
+static int tsm_dev_connect(struct tsm_dev *tdev, void *private_data, unsigned int val)
+{
+ int ret;
+
+ if (WARN_ON(!tsm.ops->dev_connect))
+ return -EPERM;
+
+ tdev->ide_pre = val == 2;
+ if (tdev->ide_pre)
+ tsm_set_sel_ide(tdev);
+
+ mutex_lock(&tdev->spdm_mutex);
+ while (1) {
+ ret = tsm.ops->dev_connect(tdev, tsm.private_data);
+ if (ret <= 0)
+ break;
+
+ ret = spdm_forward(&tdev->spdm, ret);
+ if (ret < 0)
+ break;
+ }
+ mutex_unlock(&tdev->spdm_mutex);
+
+ if (!tdev->ide_pre)
+ ret = tsm_set_sel_ide(tdev);
+
+ tdev->connected = (ret == 0);
+
+ return ret;
+}
+
+static int tsm_dev_reclaim(struct tsm_dev *tdev, void *private_data)
+{
+ struct pci_dev *pdev = NULL;
+ int ret;
+
+ if (WARN_ON(!tsm.ops->dev_reclaim))
+ return -EPERM;
+
+ /* Do not disconnect with active TDIs */
+ for_each_pci_dev(pdev) {
+ struct tsm_tdi *tdi = tsm_tdi_get(&pdev->dev);
+
+ if (tdi && tdi->tdev == tdev && tdi->data)
+ return -EBUSY;
+ }
+
+ if (!tdev->ide_pre)
+ tsm_unset_sel_ide(tdev);
+
+ mutex_lock(&tdev->spdm_mutex);
+ while (1) {
+ ret = tsm.ops->dev_reclaim(tdev, private_data);
+ if (ret <= 0)
+ break;
+
+ ret = spdm_forward(&tdev->spdm, ret);
+ if (ret < 0)
+ break;
+ }
+ mutex_unlock(&tdev->spdm_mutex);
+
+ if (tdev->ide_pre)
+ tsm_unset_sel_ide(tdev);
+
+ if (!ret)
+ tdev->connected = false;
+
+ return ret;
+}
+
+static int tsm_dev_status(struct tsm_dev *tdev, void *private_data, struct tsm_dev_status *s)
+{
+ if (WARN_ON(!tsm.ops->dev_status))
+ return -EPERM;
+
+ return tsm.ops->dev_status(tdev, private_data, s);
+}
+
+static int tsm_ide_refresh(struct tsm_dev *tdev, void *private_data)
+{
+ int ret;
+
+ if (!tsm.ops->ide_refresh)
+ return -EPERM;
+
+ mutex_lock(&tdev->spdm_mutex);
+ while (1) {
+ ret = tsm.ops->ide_refresh(tdev, private_data);
+ if (ret <= 0)
+ break;
+
+ ret = spdm_forward(&tdev->spdm, ret);
+ if (ret < 0)
+ break;
+ }
+ mutex_unlock(&tdev->spdm_mutex);
+
+ return ret;
+}
+
+static void tsm_tdi_reclaim(struct tsm_tdi *tdi, void *private_data)
+{
+ int ret;
+
+ if (WARN_ON(!tsm.ops->tdi_reclaim))
+ return;
+
+ mutex_lock(&tdi->tdev->spdm_mutex);
+ while (1) {
+ ret = tsm.ops->tdi_reclaim(tdi, private_data);
+ if (ret <= 0)
+ break;
+
+ ret = spdm_forward(&tdi->tdev->spdm, ret);
+ if (ret < 0)
+ break;
+ }
+ mutex_unlock(&tdi->tdev->spdm_mutex);
+}
+
+static int tsm_tdi_validate(struct tsm_tdi *tdi, bool invalidate, void *private_data)
+{
+ int ret;
+
+ if (!tdi || !tsm.ops->tdi_validate)
+ return -EPERM;
+
+ ret = tsm.ops->tdi_validate(tdi, invalidate, private_data);
+ if (ret) {
+ pci_err(tdi->pdev, "Validation failed, ret=%d", ret);
+ tdi->pdev->dev.tdi_enabled = false;
+ }
+
+ return ret;
+}
+
+/* In case BUS_NOTIFY_PCI_BUS_MASTER is no good, a driver can call pci_dev_tdi_validate() */
+int pci_dev_tdi_validate(struct pci_dev *pdev)
+{
+ struct tsm_tdi *tdi = tsm_tdi_get(&pdev->dev);
+
+ return tsm_tdi_validate(tdi, false, tsm.private_data);
+}
+EXPORT_SYMBOL_GPL(pci_dev_tdi_validate);
+
+static int tsm_tdi_status(struct tsm_tdi *tdi, void *private_data, struct tsm_tdi_status *ts)
+{
+ struct tsm_tdi_status tstmp = { 0 };
+ int ret;
+
+ if (WARN_ON(!tsm.ops->tdi_status))
+ return -EPERM;
+
+ mutex_lock(&tdi->tdev->spdm_mutex);
+ while (1) {
+ ret = tsm.ops->tdi_status(tdi, private_data, &tstmp);
+ if (ret <= 0)
+ break;
+
+ ret = spdm_forward(&tdi->tdev->spdm, ret);
+ if (ret < 0)
+ break;
+ }
+ mutex_unlock(&tdi->tdev->spdm_mutex);
+
+ *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 = tsm_dev_get(dev);
+ ssize_t ret = count;
+ unsigned long val;
+
+ if (kstrtoul(buf, 0, &val) < 0)
+ ret = -EINVAL;
+ else
+ tdev->cert_slot = val;
+
+ tsm_dev_put(tdev);
+
+ return ret;
+}
+
+static ssize_t tsm_cert_slot_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct tsm_dev *tdev = tsm_dev_get(dev);
+ ssize_t ret = sysfs_emit(buf, "%u\n", tdev->cert_slot);
+
+ tsm_dev_put(tdev);
+ return ret;
+}
+
+static DEVICE_ATTR_RW(tsm_cert_slot);
+
+static ssize_t tsm_tc_mask_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct tsm_dev *tdev = tsm_dev_get(dev);
+ ssize_t ret = count;
+ unsigned long val;
+
+ if (kstrtoul(buf, 0, &val) < 0)
+ ret = -EINVAL;
+ else
+ tdev->tc_mask = val;
+ tsm_dev_put(tdev);
+
+ return ret;
+}
+
+static ssize_t tsm_tc_mask_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct tsm_dev *tdev = tsm_dev_get(dev);
+ ssize_t ret = sysfs_emit(buf, "%#x\n", tdev->tc_mask);
+
+ tsm_dev_put(tdev);
+ return ret;
+}
+
+static DEVICE_ATTR_RW(tsm_tc_mask);
+
+static ssize_t tsm_dev_connect_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct tsm_dev *tdev = tsm_dev_get(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, tsm.private_data, val);
+ else if (!val && tdev->connected)
+ ret = tsm_dev_reclaim(tdev, tsm.private_data);
+
+ if (!ret)
+ ret = count;
+
+ tsm_dev_put(tdev);
+
+ return ret;
+}
+
+static ssize_t tsm_dev_connect_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct tsm_dev *tdev = tsm_dev_get(dev);
+ ssize_t ret = sysfs_emit(buf, "%u\n", tdev->connected);
+
+ tsm_dev_put(tdev);
+ return ret;
+}
+
+static DEVICE_ATTR_RW(tsm_dev_connect);
+
+static ssize_t tsm_sel_stream_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned int ide_dev = false, tee_dev = true, ide_rp = true, tee_rp = false;
+ unsigned int sel_index, id, def, en;
+ struct tsm_dev *tdev;
+
+ if (sscanf(buf, "%u %u %u %u %u %u %u %u", &sel_index, &id, &def, &en,
+ &ide_dev, &tee_dev, &ide_rp, &tee_rp) != 8) {
+ if (sscanf(buf, "%u %u %u %u", &sel_index, &id, &def, &en) != 4)
+ return -EINVAL;
+ }
+
+ if (sel_index >= ARRAY_SIZE(tdev->selective_ide) || id > 0x100)
+ return -EINVAL;
+
+ tdev = tsm_dev_get(dev);
+ if (en) {
+ tdev->selective_ide[sel_index].id = id;
+ tdev->selective_ide[sel_index].def = def;
+ tdev->selective_ide[sel_index].enable = 1;
+ tdev->selective_ide[sel_index].enabled = 0;
+ tdev->selective_ide[sel_index].dev_ide_cfg = ide_dev;
+ tdev->selective_ide[sel_index].dev_tee_limited = tee_dev;
+ tdev->selective_ide[sel_index].rootport_ide_cfg = ide_rp;
+ tdev->selective_ide[sel_index].rootport_tee_limited = tee_rp;
+ } else {
+ memset(&tdev->selective_ide[sel_index], 0, sizeof(tdev->selective_ide[0]));
+ }
+
+ tsm_dev_put(tdev);
+ return count;
+}
+
+static ssize_t tsm_sel_stream_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct tsm_dev *tdev = tsm_dev_get(dev);
+ struct pci_dev *rootport = tdev->pdev->bus->self;
+ unsigned int i;
+ char *buf1;
+ ssize_t ret = 0, sz = PAGE_SIZE;
+
+ buf1 = kmalloc(sz, GFP_KERNEL);
+ if (!buf1)
+ return -ENOMEM;
+
+ buf1[0] = 0;
+ for (i = 0; i < ARRAY_SIZE(tdev->selective_ide); ++i) {
+ if (!tdev->selective_ide[i].enable)
+ continue;
+
+ ret += snprintf(buf1 + ret, sz - ret - 1, "%u: %d%s",
+ i,
+ tdev->selective_ide[i].id,
+ tdev->selective_ide[i].def ? " DEF" : "");
+ if (tdev->selective_ide[i].enabled) {
+ u32 devst = 0, rcst = 0;
+
+ pci_ide_get_sel_sta(tdev->pdev, i, &devst);
+ pci_ide_get_sel_sta(rootport, i, &rcst);
+ ret += snprintf(buf1 + ret, sz - ret - 1,
+ " %x%s %s%s<-> %x%s %s%s rootport:%s",
+ devst,
+ PCI_IDE_SEL_STS_STATUS(devst) == 2 ? "=SECURE" : "",
+ tdev->selective_ide[i].dev_ide_cfg ? "IDECfg " : "",
+ tdev->selective_ide[i].dev_tee_limited ? "TeeLim " : "",
+ rcst,
+ PCI_IDE_SEL_STS_STATUS(rcst) == 2 ? "=SECURE" : "",
+ tdev->selective_ide[i].rootport_ide_cfg ? "IDECfg " : "",
+ tdev->selective_ide[i].rootport_tee_limited ? "TeeLim " : "",
+ pci_name(rootport)
+ );
+ }
+ ret += snprintf(buf1 + ret, sz - ret - 1, "\n");
+ }
+ tsm_dev_put(tdev);
+
+ ret = sysfs_emit(buf, buf1);
+ kfree(buf1);
+
+ return ret;
+}
+
+static DEVICE_ATTR_RW(tsm_sel_stream);
+
+static ssize_t tsm_ide_refresh_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct tsm_dev *tdev = tsm_dev_get(dev);
+ int ret;
+
+ ret = tsm_ide_refresh(tdev, tsm.private_data);
+ tsm_dev_put(tdev);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR_WO(tsm_ide_refresh);
+
+static ssize_t blob_show(struct tsm_blob *blob, char *buf)
+{
+ unsigned int n, m;
+
+ if (!blob)
+ return sysfs_emit(buf, "none\n");
+
+ n = snprintf(buf, PAGE_SIZE, "%lu %u\n", blob->len,
+ kref_read(&blob->kref));
+ m = hex_dump_to_buffer(blob->data, blob->len, 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 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 += snprintf(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 += snprintf(buf + n, len - n, "\n");
+ }
+
+ off += h->length; /* Includes the header */
+ }
+
+ return n;
+}
+
+static ssize_t tsm_certs_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct tsm_dev *tdev = tsm_dev_get(dev);
+ ssize_t n = 0;
+
+ if (!tdev->certs) {
+ n = sysfs_emit(buf, "none\n");
+ } else {
+ n = tsm_certs_gen(tdev->certs, buf, PAGE_SIZE);
+ if (!n)
+ n = blob_show(tdev->certs, buf);
+ }
+
+ tsm_dev_put(tdev);
+ return n;
+}
+
+static DEVICE_ATTR_RO(tsm_certs);
+
+static ssize_t tsm_meas_gen(struct tsm_blob *meas, char *buf, size_t len)
+{
+ static const char * const whats[] = {
+ "ImmuROM", "MutFW", "HWCfg", "FWCfg",
+ "MeasMft", "DevDbg", "MutFWVer", "MutFWVerSec"
+ };
+ struct dmtf_measurement_block_device_mode *dm;
+ struct spdm_measurement_block_header *mb;
+ struct dmtf_measurement_block_header *h;
+ unsigned int n = 0, m, off, what;
+ bool dmtf;
+
+ for (off = 0; off < meas->len; ) {
+ mb = (struct spdm_measurement_block_header *)(((u8 *) meas->data) + off);
+ dmtf = mb->spec & 1;
+
+ n += snprintf(buf + n, len - n, "#%d (%d) ", mb->index, mb->size);
+ if (dmtf) {
+ h = (void *) &mb[1];
+
+ if (WARN_ON_ONCE(mb->size != (sizeof(*h) + h->size)))
+ return -EINVAL;
+
+ what = h->type & 0x7F;
+ n += snprintf(buf + n, len - n, "%x=[%s %s]: ",
+ h->type,
+ h->type & 0x80 ? "digest" : "raw",
+ what < ARRAY_SIZE(whats) ? whats[what] : "reserved");
+
+ if (what == 5) {
+ dm = (struct dmtf_measurement_block_device_mode *) &h[1];
+ n += snprintf(buf + n, len - n, " %x %x %x %x",
+ dm->opmode_cap, dm->opmode_sta,
+ dm->devmode_cap, dm->devmode_sta);
+ } else {
+ m = hex_dump_to_buffer(&h[1], h->size, 32, 1,
+ buf + n, len - n, false);
+ n += min(PAGE_SIZE - n, m);
+ }
+ } else {
+ n += snprintf(buf + n, len - n, "spec=%x: ", mb->spec);
+ m = hex_dump_to_buffer(&mb[1], min(len - off, mb->size),
+ 32, 1, buf + n, len - n, false);
+ n += min(PAGE_SIZE - n, m);
+ }
+
+ off += sizeof(*mb) + mb->size;
+ n += snprintf(buf + n, PAGE_SIZE - n, "...\n");
+ }
+
+ return n;
+}
+
+static ssize_t tsm_meas_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct tsm_dev *tdev = tsm_dev_get(dev);
+ ssize_t n = 0;
+
+ if (!tdev->meas) {
+ n = sysfs_emit(buf, "none\n");
+ } else {
+ if (!n)
+ n = tsm_meas_gen(tdev->meas, buf, PAGE_SIZE);
+ if (!n)
+ n = blob_show(tdev->meas, buf);
+ }
+
+ tsm_dev_put(tdev);
+ return n;
+}
+
+static DEVICE_ATTR_RO(tsm_meas);
+
+static ssize_t tsm_dev_status_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct tsm_dev *tdev = tsm_dev_get(dev);
+ struct tsm_dev_status s = { 0 };
+ int ret = tsm_dev_status(tdev, tsm.private_data, &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\n"
+ "segment_id=%x\n"
+ "no_fw_update=%x\n",
+ ret,
+ s.ctx_state,
+ s.tc_mask,
+ s.certs_slot,
+ s.device_id,
+ 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_tc_mask.attr,
+ &dev_attr_tsm_dev_connect.attr,
+ &dev_attr_tsm_sel_stream.attr,
+ &dev_attr_tsm_ide_refresh.attr,
+ &dev_attr_tsm_certs.attr,
+ &dev_attr_tsm_meas.attr,
+ &dev_attr_tsm_dev_status.attr,
+ NULL,
+};
+static const struct attribute_group host_dev_group = {
+ .attrs = host_dev_attrs,
+};
+
+static struct attribute *guest_dev_attrs[] = {
+ &dev_attr_tsm_certs.attr,
+ &dev_attr_tsm_meas.attr,
+ NULL,
+};
+static const struct attribute_group guest_dev_group = {
+ .attrs = guest_dev_attrs,
+};
+
+static ssize_t tsm_tdi_bind_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct tsm_tdi *tdi = tsm_tdi_get(dev);
+
+ if (!tdi->vmid)
+ return sysfs_emit(buf, "not bound\n");
+
+ return sysfs_emit(buf, "VM=%#llx ASID=%d BDFn=%x:%x.%d\n",
+ tdi->vmid, tdi->asid,
+ PCI_BUS_NUM(tdi->guest_rid), PCI_SLOT(tdi->guest_rid),
+ PCI_FUNC(tdi->guest_rid));
+}
+
+static DEVICE_ATTR_RO(tsm_tdi_bind);
+
+static ssize_t tsm_tdi_validate_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct tsm_tdi *tdi = tsm_tdi_get(dev);
+ unsigned long val;
+ ssize_t ret;
+
+ if (kstrtoul(buf, 0, &val) < 0)
+ return -EINVAL;
+
+ if (val) {
+ ret = tsm_tdi_validate(tdi, false, tsm.private_data);
+ if (ret)
+ return ret;
+ } else {
+ tsm_tdi_validate(tdi, true, tsm.private_data);
+ }
+
+ tdi->validated = val;
+
+ return count;
+}
+
+static ssize_t tsm_tdi_validate_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct tsm_tdi *tdi = tsm_tdi_get(dev);
+
+ return sysfs_emit(buf, "%u\n", tdi->validated);
+}
+
+static DEVICE_ATTR_RW(tsm_tdi_validate);
+
+ssize_t tsm_report_gen(struct tsm_blob *report, char *buf, size_t len)
+{
+ struct tdi_report_header *h = TDI_REPORT_HDR(report);
+ struct tdi_report_mmio_range *mr = TDI_REPORT_MR_OFF(report);
+ struct tdi_report_footer *f = TDI_REPORT_FTR(report);
+ unsigned int n, m, i;
+
+ n = snprintf(buf, len,
+ "no_fw_update=%u\ndma_no_pasid=%u\ndma_pasid=%u\nats=%u\nprs=%u\n",
+ h->no_fw_update, h->dma_no_pasid, h->dma_pasid, h->ats, h->prs);
+ n += snprintf(buf + n, len - n,
+ "msi_x_message_control=%#04x\nlnr_control=%#04x\n",
+ h->msi_x_message_control, h->lnr_control);
+ n += snprintf(buf + n, len - n, "tph_control=%#08x\n", h->tph_control);
+
+ for (i = 0; i < h->mmio_range_count; ++i) {
+ n += snprintf(buf + n, len - n,
+ "[%i] #%u %#016llx +%#lx MSIX%c PBA%c NonTEE%c Upd%c\n",
+ i, mr[i].range_id, mr[i].first_page << PAGE_SHIFT,
+ (unsigned long) mr[i].num << PAGE_SHIFT,
+ mr[i].msix_table ? '+':'-',
+ mr[i].msix_pba ? '+':'-',
+ mr[i].is_non_tee_mem ? '+':'-',
+ mr[i].is_mem_attr_updatable ? '+':'-');
+ if (mr[i].reserved)
+ n += snprintf(buf + n, len - n,
+ "[%i] WARN: reserved=%#x\n", i, mr[i].range_attributes);
+ }
+
+ if (f->device_specific_info_len) {
+ unsigned int num = report->len - ((u8 *)f->device_specific_info - (u8 *)h);
+
+ num = min(num, f->device_specific_info_len);
+ n += snprintf(buf + n, len - n, "DevSp len=%d%s",
+ f->device_specific_info_len, num ? ": " : "");
+ m = hex_dump_to_buffer(f->device_specific_info, num, 32, 1,
+ buf + n, len - n, false);
+ n += min(len - n, m);
+ n += snprintf(buf + n, len - n, m ? "\n" : "...\n");
+ }
+
+ return n;
+}
+EXPORT_SYMBOL_GPL(tsm_report_gen);
+
+static ssize_t tsm_report_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct tsm_tdi *tdi = tsm_tdi_get(dev);
+ ssize_t n = 0;
+
+ if (!tdi->report) {
+ n = sysfs_emit(buf, "none\n");
+ } else {
+ if (!n)
+ n = tsm_report_gen(tdi->report, buf, PAGE_SIZE);
+ if (!n)
+ n = blob_show(tdi->report, buf);
+ }
+
+ return n;
+}
+
+static DEVICE_ATTR_RO(tsm_report);
+
+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_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct tsm_tdi *tdi = tsm_tdi_get(dev);
+ struct tsm_tdi_status ts = { 0 };
+ char algos[256] = "";
+ unsigned int n, m;
+ int ret;
+
+ ret = tsm_tdi_status(tdi, tsm.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"
+ ,
+ 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));
+
+ 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);
+
+static struct attribute *host_tdi_attrs[] = {
+ &dev_attr_tsm_tdi_bind.attr,
+ &dev_attr_tsm_report.attr,
+ &dev_attr_tsm_tdi_status.attr,
+ NULL,
+};
+
+static const struct attribute_group host_tdi_group = {
+ .attrs = host_tdi_attrs,
+};
+
+static struct attribute *guest_tdi_attrs[] = {
+ &dev_attr_tsm_tdi_validate.attr,
+ &dev_attr_tsm_report.attr,
+ &dev_attr_tsm_tdi_status.attr,
+ NULL,
+};
+
+static const struct attribute_group guest_tdi_group = {
+ .attrs = guest_tdi_attrs,
+};
+
+static int tsm_tdi_init(struct tsm_dev *tdev, struct pci_dev *pdev)
+{
+ struct tsm_tdi *tdi;
+ int ret = 0;
+
+ dev_info(&pdev->dev, "Initializing tdi\n");
+ if (!tdev)
+ return -ENODEV;
+
+ tdi = kzalloc(sizeof(*tdi), GFP_KERNEL);
+ if (!tdi)
+ return -ENOMEM;
+
+ /* tsm_dev_get() requires pdev->dev.tdi which is set later */
+ if (!kref_get_unless_zero(&tdev->kref)) {
+ ret = -EPERM;
+ goto free_exit;
+ }
+
+ if (tsm.ops->dev_connect)
+ tdi->ag = &host_tdi_group;
+ else
+ tdi->ag = &guest_tdi_group;
+
+ ret = sysfs_create_link(&pdev->dev.kobj, &tdev->pdev->dev.kobj, "tsm_dev");
+ if (ret)
+ goto free_exit;
+
+ ret = device_add_group(&pdev->dev, tdi->ag);
+ if (ret)
+ goto sysfs_unlink_exit;
+
+ tdi->tdev = tdev;
+ tdi->pdev = pci_dev_get(pdev);
+
+ pdev->dev.tdi_enabled = !pdev->is_physfn || tsm.physfn;
+ pdev->dev.tdi = tdi;
+ pci_info(pdev, "TDI enabled=%d\n", pdev->dev.tdi_enabled);
+
+ return 0;
+
+sysfs_unlink_exit:
+ sysfs_remove_link(&pdev->dev.kobj, "tsm_dev");
+free_exit:
+ kfree(tdi);
+
+ return ret;
+}
+
+static void tsm_tdi_free(struct tsm_tdi *tdi)
+{
+ tsm_dev_put(tdi->tdev);
+
+ pci_dev_put(tdi->pdev);
+
+ device_remove_group(&tdi->pdev->dev, tdi->ag);
+ sysfs_remove_link(&tdi->pdev->dev.kobj, "tsm_dev");
+ tdi->pdev->dev.tdi = NULL;
+ tdi->pdev->dev.tdi_enabled = false;
+ kfree(tdi);
+}
+
+static int tsm_dev_init(struct pci_dev *pdev, struct tsm_dev **ptdev)
+{
+ struct tsm_dev *tdev;
+ int ret = 0;
+
+ dev_info(&pdev->dev, "Initializing tdev\n");
+ tdev = kzalloc(sizeof(*tdev), GFP_KERNEL);
+ if (!tdev)
+ return -ENOMEM;
+
+ kref_init(&tdev->kref);
+ tdev->tc_mask = tsm.tc_mask;
+ tdev->cert_slot = tsm.cert_slot;
+ tdev->pdev = pci_dev_get(pdev);
+ mutex_init(&tdev->spdm_mutex);
+
+ if (tsm.ops->dev_connect)
+ tdev->ag = &host_dev_group;
+ else
+ tdev->ag = &guest_dev_group;
+
+ ret = device_add_group(&pdev->dev, tdev->ag);
+ if (ret)
+ goto free_exit;
+
+ if (tsm.ops->dev_connect) {
+ ret = -EPERM;
+ tdev->pdev = pci_dev_get(pdev);
+ tdev->spdm.doe_mb = pci_find_doe_mailbox(tdev->pdev,
+ PCI_VENDOR_ID_PCI_SIG,
+ PCI_DOE_PROTOCOL_CMA_SPDM);
+ if (!tdev->spdm.doe_mb)
+ goto pci_dev_put_exit;
+
+ tdev->spdm.doe_mb_secured = pci_find_doe_mailbox(tdev->pdev,
+ PCI_VENDOR_ID_PCI_SIG,
+ PCI_DOE_PROTOCOL_SECURED_CMA_SPDM);
+ if (!tdev->spdm.doe_mb_secured)
+ goto pci_dev_put_exit;
+ }
+
+ *ptdev = tdev;
+ return 0;
+
+pci_dev_put_exit:
+ pci_dev_put(pdev);
+free_exit:
+ kfree(tdev);
+
+ return ret;
+}
+
+static void tsm_dev_free(struct kref *kref)
+{
+ struct tsm_dev *tdev = container_of(kref, struct tsm_dev, kref);
+
+ device_remove_group(&tdev->pdev->dev, tdev->ag);
+
+ if (tdev->connected)
+ tsm_dev_reclaim(tdev, tsm.private_data);
+
+ dev_info(&tdev->pdev->dev, "Freeing TDEV\n");
+ pci_dev_put(tdev->pdev);
+ kfree(tdev);
+}
+
+static int tsm_alloc_device(struct pci_dev *pdev)
+{
+ int ret = 0;
+
+ /* It is guest VM == TVM */
+ if (!tsm.ops->dev_connect) {
+ if (pdev->devcap & PCI_EXP_DEVCAP_TEE_IO) {
+ struct tsm_dev *tdev = NULL;
+
+ ret = tsm_dev_init(pdev, &tdev);
+ if (ret)
+ return ret;
+
+ ret = tsm_tdi_init(tdev, pdev);
+ tsm_dev_put(tdev);
+ return ret;
+ }
+ return 0;
+ }
+
+ if (pdev->is_physfn && (PCI_FUNC(pdev->devfn) == 0) &&
+ (pdev->devcap & PCI_EXP_DEVCAP_TEE_IO)) {
+ struct tsm_dev *tdev = NULL;
+
+
+ ret = tsm_dev_init(pdev, &tdev);
+ if (ret)
+ return ret;
+
+ ret = tsm_tdi_init(tdev, pdev);
+ tsm_dev_put(tdev);
+ return ret;
+ }
+
+ if (pdev->is_virtfn) {
+ struct pci_dev *pf0 = pci_get_slot(pdev->physfn->bus,
+ pdev->physfn->devfn & ~7);
+
+ if (pf0 && (pf0->devcap & PCI_EXP_DEVCAP_TEE_IO)) {
+ struct tsm_dev *tdev = tsm_dev_get(&pf0->dev);
+
+ ret = tsm_tdi_init(tdev, pdev);
+ tsm_dev_put(tdev);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void tsm_dev_freeice(struct device *dev)
+{
+ struct tsm_tdi *tdi = tsm_tdi_get(dev);
+
+ if (!tdi)
+ return;
+
+ tsm_tdi_free(tdi);
+}
+
+static int tsm_pci_bus_notifier(struct notifier_block *nb, unsigned long action, void *data)
+{
+ switch (action) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ tsm_alloc_device(to_pci_dev(data));
+ break;
+ case BUS_NOTIFY_DEL_DEVICE:
+ tsm_dev_freeice(data);
+ break;
+ case BUS_NOTIFY_UNBOUND_DRIVER:
+ tsm_tdi_validate(tsm_tdi_get(data), true, tsm.private_data);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block tsm_pci_bus_nb = {
+ .notifier_call = tsm_pci_bus_notifier,
+};
+
+static int __init tsm_init(void)
+{
+ int ret = 0;
+
+ pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n");
+ return ret;
+}
+
+static void __exit tsm_cleanup(void)
+{
+}
+
+void tsm_set_ops(struct tsm_ops *ops, void *private_data)
+{
+ struct pci_dev *pdev = NULL;
+ int ret;
+
+ if (!tsm.ops && ops) {
+ tsm.ops = ops;
+ tsm.private_data = private_data;
+
+ for_each_pci_dev(pdev) {
+ ret = tsm_alloc_device(pdev);
+ if (ret)
+ break;
+ }
+ bus_register_notifier(&pci_bus_type, &tsm_pci_bus_nb);
+ } else {
+ bus_unregister_notifier(&pci_bus_type, &tsm_pci_bus_nb);
+ for_each_pci_dev(pdev)
+ tsm_dev_freeice(&pdev->dev);
+ tsm.ops = ops;
+ }
+}
+EXPORT_SYMBOL_GPL(tsm_set_ops);
+
+int tsm_tdi_bind(struct tsm_tdi *tdi, u32 guest_rid, u64 vmid, u32 asid)
+{
+ int ret;
+
+ if (WARN_ON(!tsm.ops->tdi_bind))
+ return -EPERM;
+
+ tdi->guest_rid = guest_rid;
+ tdi->vmid = vmid;
+ tdi->asid = asid;
+
+ mutex_lock(&tdi->tdev->spdm_mutex);
+ while (1) {
+ ret = tsm.ops->tdi_bind(tdi, guest_rid, vmid, asid, tsm.private_data);
+ if (ret < 0)
+ break;
+
+ if (!ret)
+ break;
+
+ ret = spdm_forward(&tdi->tdev->spdm, ret);
+ if (ret < 0)
+ break;
+ }
+ mutex_unlock(&tdi->tdev->spdm_mutex);
+
+ if (ret) {
+ tsm_tdi_unbind(tdi);
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(tsm_tdi_bind);
+
+void tsm_tdi_unbind(struct tsm_tdi *tdi)
+{
+ tsm_tdi_reclaim(tdi, tsm.private_data);
+ tdi->vmid = 0;
+ tdi->asid = 0;
+ tdi->guest_rid = 0;
+}
+EXPORT_SYMBOL_GPL(tsm_tdi_unbind);
+
+int tsm_guest_request(struct tsm_tdi *tdi, enum tsm_tdisp_state *state, void *req_data)
+{
+ int ret;
+
+ if (!tsm.ops->guest_request)
+ return -EPERM;
+
+ mutex_lock(&tdi->tdev->spdm_mutex);
+ while (1) {
+ ret = tsm.ops->guest_request(tdi, tdi->guest_rid, tdi->vmid, req_data,
+ state, tsm.private_data);
+ if (ret <= 0)
+ break;
+
+ ret = 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_tdi *tsm_tdi_find(u32 guest_rid, u64 vmid)
+{
+ struct pci_dev *pdev = NULL;
+ struct tsm_tdi *tdi;
+
+ for_each_pci_dev(pdev) {
+ tdi = tsm_tdi_get(&pdev->dev);
+ if (!tdi)
+ continue;
+
+ if (tdi->vmid == vmid && tdi->guest_rid == guest_rid)
+ return tdi;
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(tsm_tdi_find);
+
+module_init(tsm_init);
+module_exit(tsm_cleanup);
+
+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..3be6e8491e42
--- /dev/null
+++ b/Documentation/virt/coco/tsm.rst
@@ -0,0 +1,62 @@
+.. 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 module
+==========
+
+This is common place to trigger device authentication and keys management.
+It exposes certificates/measurenets/reports/status via sysfs and provides control
+over the link (limited though by the TSM capabilities).
+A platform is expected to register a specific set of hooks. The same module works
+in host and guest OS, the set of requires platform hooks is quite different.
+
+
+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.
+
+
+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 87d142c1f932..67a9c9daf96d 100644
--- a/drivers/virt/coco/Kconfig
+++ b/drivers/virt/coco/Kconfig
@@ -7,6 +7,17 @@ config TSM_REPORTS
select CONFIGFS_FS
tristate

+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
+
source "drivers/virt/coco/efi_secret/Kconfig"

source "drivers/virt/coco/sev-guest/Kconfig"
--
2.45.2