Re: [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts
flat view
From: Dave Jiang <dave.jiang@intel.com>
Date: 2026-10-28 22:56:48
Also in:
linux-patches
On 10/28/26 8:26 AM, Aneesh Kumar K.V (Arm) wrote: < -- snip -->
quoted hunk ↗ jump to hunk
diff --git a/drivers/pci/tsm/attestation.c b/drivers/pci/tsm/attestation.c new file mode 100644 index 000000000000..993c26664498 --- /dev/null +++ b/drivers/pci/tsm/attestation.c@@ -0,0 +1,525 @@ +// SPDX-License-Identifier: GPL-2.0 +/* PCI lifecycle and cached SPDM/TDISP evidence through CTLV attestation. */ +#include <crypto/hash_info.h> +#include <linux/attestation.h> +#include <linux/ctlv.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/pci-tsm.h> +#include <linux/tsm.h> + +#include "../pci.h" +#include "ctlv_attestation_pci_gen.h" +#include "internal.h" + +struct pci_tsm_attestation { + struct pci_dev *pdev; + struct ctlv_device *ctlvdev; + struct list_head retired; + bool attached; + /* Protected by pci_tsm_rwsem. */ + bool active; + bool removing; +}; + +static void pci_attestation_update_locked(struct pci_tsm_attestation *pa, + bool att_locked) +{ + struct pci_dev *pdev = pa->pdev; + struct pci_tsm *tsm = pdev->tsm; + const struct pci_tsm_ops *ops = tsm ? tsm->tsm_dev->pci_ops : NULL; + bool link = tsm && tsm->dsm_dev; + bool devsec = tsm && !link; + bool accepted = device_cc_accepted(&pdev->dev); + bool present = !pa->removing && !pci_dev_is_disconnected(pdev); + unsigned int features = 0; + + lockdep_assert_held_write(&pci_tsm_rwsem); + + if (devsec) { + ctlv_device_op_state_set(pa->ctlvdev, CTLV_ATTESTATION_PCI_OP_EVIDENCE_REFRESH, + !present || !tsm || tsm->error || + accepted || !ops->refresh_evidence); + } + + if (present) { + if (tsm && !tsm->error) { + features |= ATTESTATION_FEATURE_EVIDENCE; + if ((devsec && ops->accept) || (link && is_pci_tsm_pf0(pdev))) + features |= ATTESTATION_FEATURE_EVIDENCE_ACCEPT; + } + + if (devsec) + features |= ATTESTATION_FEATURE_TDISP_STATE; + } + + if (att_locked) + attestation_features_set_locked(ctlv_priv_parent(pa), features); + else + attestation_features_set(ctlv_priv_parent(pa), features); +} + +void pci_tsm_attestation_update(struct pci_dev *pdev) +{ + lockdep_assert_held_write(&pci_tsm_rwsem); + if (pdev->tsm_attestation && !pdev->tsm_attestation->removing) + pci_attestation_update_locked(pdev->tsm_attestation, false); +} + +void pci_tsm_attestation_notify_state_change(struct pci_dev *pdev) +{ + struct pci_tsm_attestation *pa = pdev->tsm_attestation; + struct attestation *att; + + lockdep_assert_held_write(&pci_tsm_rwsem); + if (!pa || pa->removing) + return; + + att = ctlv_priv_parent(pa); + /* The active CTLV operation already holds this instance's mutex. */ + if (pa->active) { + attestation_state_changed_locked(att); + pci_attestation_update_locked(pa, true); + return; + } + + /* Native notifications outside dispatch must acquire the mutex. */ + attestation_lock(att); + attestation_state_changed_locked(att); + pci_attestation_update_locked(pa, true); + attestation_unlock(att); +} + +void pci_tsm_attestation_remove(struct pci_dev *pdev, struct list_head *retired) +{ + struct pci_tsm_attestation *pa = pdev->tsm_attestation; + + lockdep_assert_held_write(&pci_tsm_rwsem); + if (!pa || pa->removing) + return; + + /* Revoke old descriptors before another session can be installed. */ + pa->removing = true; + pdev->tsm_attestation_removing = true; + list_add_tail(&pa->retired, retired); +} + +void pci_tsm_attestation_destroy(struct list_head *retired) +{ + struct pci_tsm_attestation *pa, *next; + + /* ctlv_destroy() drains callbacks that may be waiting for pci_tsm_rwsem. */ + list_for_each_entry_safe(pa, next, retired, retired) { + struct pci_dev *pdev = pci_dev_get(pa->pdev); + + list_del(&pa->retired); + ctlv_destroy(pa->ctlvdev); + + /* The common registry has now retired this parent as well. */ + scoped_guard(rwsem_write, &pci_tsm_rwsem) + pdev->tsm_attestation_removing = false; + + pci_dev_put(pdev); + } +} + +int ctlv_attestation_pci_op_pre(void *priv, struct ctlv_op_ctx *ctx) +{ + struct pci_tsm_attestation *pa = priv; + int ret; + + /* All native paths, including notifications, use PCI -> attestation. */ + ret = down_write_killable(&pci_tsm_rwsem); + if (ret) + return ret; + + if (pa->removing || !pa->pdev->tsm || pci_dev_is_disconnected(pa->pdev)) { + up_write(&pci_tsm_rwsem); + return -ENODEV; + } + + ret = ctlv_attestation_op_pre(ctlv_priv_parent(pa), ctx); + if (ret) { + up_write(&pci_tsm_rwsem); + return ret; + } + + pa->active = true; + pci_attestation_update_locked(pa, true); + return 0; +} + +void ctlv_attestation_pci_op_post(void *priv, struct ctlv_op_ctx *ctx) +{ + struct pci_tsm_attestation *pa = priv; + + pci_attestation_update_locked(pa, true); + pa->active = false; + ctlv_attestation_op_post(ctlv_priv_parent(pa), ctx); + up_write(&pci_tsm_rwsem); +} + +static int pci_attestation_prepare(struct pci_tsm_attestation *pa, u64 generation) +{ + struct attestation *att = ctlv_priv_parent(pa); + + if (generation != attestation_get_device_generation(att)) + return -ESTALE; + + return attestation_state_change_prepare(att); +} + +static int pci_attestation_reply(struct pci_tsm_attestation *pa, + struct ctlv_cursor *reply) +{ + struct attestation *att = ctlv_priv_parent(pa); + + if (!ctlv_attestation_pci_pci_generation_put(reply, + attestation_get_device_generation(att))) + return -EMSGSIZE; + + return 0; +} + +int ctlv_attestation_pci_evidence_refresh_action(void *priv, struct ctlv_op_ctx *ctx, + const struct ctlv_cursor *request, struct ctlv_cursor *reply) +{ + struct pci_tsm_attestation *pa = priv; + struct pci_tsm *tsm = pa->pdev->tsm; + const struct pci_tsm_ops *ops; + const void *challenge; + enum pci_tsm_evidence_type type; + u64 object; + u32 length = 0; + int ret; + + object = ctlv_attestation_pci_evidence_refresh_object_get(request); + switch (object) { + case CTLV_ATTESTATION_EVIDENCE_BLOB_CLASS_SPDM_MEASUREMENTS: + type = PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS; + break; + case CTLV_ATTESTATION_EVIDENCE_BLOB_CLASS_TDISP_REPORT: + type = PCI_TSM_EVIDENCE_TYPE_REPORT; + break; + default: + return -EOPNOTSUPP; + } + + challenge = ctlv_attestation_pci_evidence_refresh_challenge_get(request, &length); + if ((type == PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS && length != 32) || + (type == PCI_TSM_EVIDENCE_TYPE_REPORT && length)) + return -EINVAL; + + ret = pci_attestation_prepare(pa, + ctlv_attestation_pci_evidence_refresh_generation_get(request)); + if (ret) + return ret; + + if (!tsm) + return -ENOTCONN; + + if (tsm->error || device_cc_accepted(&pa->pdev->dev)) + return -EBUSY; + + ops = tsm->tsm_dev->pci_ops; + if (!ops->refresh_evidence) + return -EOPNOTSUPP; + + ret = ops->refresh_evidence(tsm, type, 0, (void *)challenge, length); + /* Even a failed refresh may have changed device state. Retire all verdicts. */ + if (ret) + tsm->error = true;
Does this make any refresh failure fatal until unlock? The CCA refresh in 11/11 says "failure preserve the old cache", but with tsm->error set I don't think the cache can be read or accepted?
The cached evidence is still the lock's evidence, collected after the lock. And per the cover letter a failed refresh already invalidates the earlier generations. Should the backend decide instead, and set tsm->error itself when it can't tell what happened to the device?
Also, this builds on struct pci_tsm_evidence from Dan's March posting [1]. The TDX guest RFC [2] and AMD's SEV-TIO guest RFC [3] both use
device_evidence_create() from his July posting [4]. Is the plan to go with pci_tsm_evidence and drop device_evidence? Would help to know which one to build the TDX guest side on.
[1] https://lore.kernel.org/all/20260303000207.1836586-1-dan.j.williams@intel.com/ (local)
[2] https://lore.kernel.org/all/20260924041032.1096569-1-zhenzhong.duan@intel.com/ (local)
[3] https://lore.kernel.org/all/20260916115159.1938195-16-aik@amd.com/ (local)
[4] https://lore.kernel.org/all/20260705220819.2472765-1-djbw@kernel.org/ (local)
DJ