All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH v1 00/11] PCI/TSM: Add CTLV attestation
@ 2026-10-28 15:26 Aneesh Kumar K.V (Arm)
  2026-10-28 15:26 ` [RFC PATCH v1 01/11] attestation: Restrict inherited operations per instance Aneesh Kumar K.V (Arm)
                   ` (10 more replies)
  0 siblings, 11 replies; 17+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-10-28 15:26 UTC (permalink / raw)
  To: linux-coco
  Cc: Aneesh Kumar K.V (Arm), Jiri Pirko, Kevin Tian,
	Alexey Kardashevskiy, Xu Yilun, Catalin Marinas, Suzuki K Poulose,
	Steven Price, Kiryl Shutsemau, Rick Edgecombe, Dave Hansen,
	Jonathan Cameron, Jason Gunthorpe, Bjorn Helgaas, Ashish Kalra,
	Tom Lendacky, John Allen, Will Deacon, Mark Rutland, patches

This series connects PCI/TSM to the common CTLV attestation interface.
Userspace can discover a device's evidence objects, retrieve them, and
accept the evidence before the kernel establishes a secure link or moves
a guest device into TDISP RUN. It replaces the PCI/TSM netlink evidence
interface while retaining connect, disconnect, lock and unlock in sysfs.

This series builds on the CTLV attestation series [1] and Arm CCA
device-assignment support. It covers PCI/TSM attestation through
certificate slot-zero.

The development tree is available at:

  https://gitlab.arm.com/linux-arm/linux-cca.git cca/topics/cca-da-attestation

That branch also includes the follow-up Rust SPDM certificate-slot
discovery work, generic attestation fixes, and CTLV tooling fixes.

[1] https://lore.kernel.org/all/20261015145053.3664509-1-jiri@resnulli.us

The host flow separates identity collection from session and IDE setup:

  sysfs connect
    -> collect and cache the certificate chain
  DEVICE_GET / EVIDENCE_GET
    -> discover available objects and retrieve the chain for verification
  EVIDENCE_ACCEPT
    -> establish the secure session and IDE streams
  sysfs disconnect
    -> release the identity context and any established streams

Both AMD SEV-TIO and Arm CCA host drivers implement this flow using their
existing firmware interfaces. AMD pauses the firmware SPDM exchange after
collecting the certificate chain, before sending authentication or session
establishment requests. CCA creates the PDEV and collects its certificate
chain during connect, then completes session and stream setup on acceptance.
After both conversions, every link driver must supply an evidence_accept
callback; connect collects identity evidence without establishing streams.

This is the slot-zero baseline: the AMD and CCA host drivers expose their
slot-zero certificate chains. A follow-up series adds shared SPDM identity
discovery and selection among discovered certificate slots. The common
interface supports any certificate slot advertised by the driver, and the
same evidence-get and evidence-accept commands apply to both series.

For a guest device, sysfs lock collects the initial evidence and creates
the CTLV instance. Userspace can optionally refresh supported objects,
retrieve the cached evidence, verify it, and issue EVIDENCE_ACCEPT to enter
RUN. The CCA guest driver supports refreshing measurements with a 32-byte
challenge or an interface report without a challenge. It checks the new
bytes against trusted digests and device state before replacing the
selected cached object. Refresh is disabled after the device enters RUN.

EVIDENCE_GET reads the cached snapshot, so its challenge attribute becomes
optional. EVIDENCE_REFRESH requests new evidence and invalidates previous
evidence generations and acceptance. A failed backend refresh also
invalidates them and requires recovery.

EVIDENCE_ACCEPT replaces the separate DEVSEC_RUN operation. Clearing
acceptance leaves the hardware state unchanged: a guest device can remain
in RUN, and host IDE streams remain established. DEVSEC_STATE_GET therefore
continues to report acceptance and device state separately. Disconnect and
unlock perform teardown.

The common attestation changes let each instance expose the operations and
evidence objects its device supports. This allows host links and guest
devices to share the PCI attestation implementation while advertising
different capabilities. Additional locking and generation helpers let
PCI/TSM coordinate state changes, evidence invalidation and notifications
with CTLV operations.

The PCI child family adds STATE_GET and EVIDENCE_REFRESH to the inherited
attestation operations. An instance appears only after a successful connect
or lock and is removed on disconnect or unlock. DEVICE_GET includes the
PCI name so userspace can associate an instance with its device.

The first five patches prepare the common attestation interface. The next
three define and implement the PCI integration, including its locking
helpers. The final three convert AMD and CCA host link setup and add CCA
guest evidence refresh. The series builds on the existing CTLV attestation
and PCI/TSM device-assignment support.

Notes:
* I used Codex to help implement, review and organize the series.

Cc: Jiri Pirko <jiri@resnulli.us>
Cc: Kevin Tian <kevin.tian@intel.com>
Cc: Alexey Kardashevskiy <aik@amd.com>
Cc: Xu Yilun <yilun.xu@linux.intel.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Suzuki K Poulose <suzuki.poulose@arm.com>
Cc: Steven Price <steven.price@arm.com>
Cc: Kiryl Shutsemau <kas@kernel.org>
Cc: Rick Edgecombe <rick.p.edgecombe@intel.com>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Jonathan Cameron <jic23@kernel.org>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Ashish Kalra <ashish.kalra@amd.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Cc: John Allen <john.allen@amd.com>
Cc: Will Deacon <will@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: patches@lists.linux.dev

Aneesh Kumar K.V (Arm) (11):
  attestation: Restrict inherited operations per instance
  attestation: Filter evidence objects per instance
  attestation: Enter secure operation when evidence is accepted
  attestation: Bind object selection to accepted evidence snapshots
  attestation: Make the evidence-get challenge attribute optional
  PCI/TSM: Describe the PCI attestation control family
  attestation: Add attestation helpers for PCI/TSM integration
  PCI/TSM: Bind CTLV attestation to connected and locked contexts
  crypto/ccp: Defer SEV-TIO link setup until evidence acceptance
  virt: coco: arm-cca-host: Publish slot zero and establish streams
    after acceptance
  virt: coco: arm-cca-guest: Refresh selected evidence against trusted
    digests

 Documentation/ABI/testing/sysfs-bus-pci       |  108 +-
 Documentation/ABI/testing/sysfs-class-tsm     |    9 +-
 .../ctlv/abi/ctlv-attestation-pci.yaml        |   43 +
 Documentation/ctlv/abi/ctlv-attestation.yaml  |   59 +-
 Documentation/ctlv/abi/families.registry.yaml |    1 +
 Documentation/ctlv/families.yaml              |    1 +
 Documentation/ctlv/generated-files.yaml       |   11 +
 .../ctlv/specs/ctlv-attestation-pci.yaml      |   79 +
 .../ctlv/specs/ctlv-attestation.yaml          |   40 +-
 Documentation/driver-api/attestation.rst      |   56 +
 Documentation/driver-api/pci/tsm.rst          |  160 +-
 Documentation/netlink/specs/pci-tsm.yaml      |  151 --
 drivers/attestation/core/core.c               |  123 +-
 drivers/attestation/core/ctlv.c               |  156 +-
 drivers/attestation/core/internal.h           |    3 +
 .../generated/ctlv_attestation_gen.c          |   45 +-
 .../generated/ctlv_attestation_gen.h          |   76 +-
 drivers/attestation/providers/sim.c           |   40 +-
 drivers/attestation/tests/core_kunit.c        |   72 +-
 drivers/crypto/ccp/sev-dev-tio.c              |   17 +-
 drivers/crypto/ccp/sev-dev-tio.h              |    5 +
 drivers/crypto/ccp/sev-dev-tsm.c              |  188 +-
 drivers/pci/Kconfig                           |    3 +
 drivers/pci/tsm/Makefile                      |    6 +-
 drivers/pci/tsm/attestation.c                 |  525 +++++
 drivers/pci/tsm/core.c                        |  431 +++--
 drivers/pci/tsm/ctlv_attestation_pci_gen.c    |  111 ++
 drivers/pci/tsm/ctlv_attestation_pci_gen.h    |  100 +
 drivers/pci/tsm/evidence.c                    |  274 ---
 drivers/pci/tsm/internal.h                    |   26 +
 drivers/pci/tsm/netlink.c                     |   43 -
 drivers/pci/tsm/netlink.h                     |   23 -
 drivers/virt/coco/arm-cca-guest/main.c        |  168 +-
 drivers/virt/coco/arm-cca-guest/rsi-da.c      |   16 +-
 drivers/virt/coco/arm-cca-host/main.c         |  143 +-
 drivers/virt/coco/arm-cca-host/rmi-da.c       |    7 +-
 drivers/virt/coco/arm-cca-host/rmi-da.h       |    2 +
 include/linux/attestation.h                   |   93 +-
 include/linux/pci-tsm.h                       |   86 +-
 include/linux/pci.h                           |    2 +
 include/uapi/linux/ctlv_attestation_gen.h     |   25 +-
 include/uapi/linux/ctlv_attestation_pci_gen.h |   74 +
 include/uapi/linux/ctlv_families_gen.h        |    1 +
 include/uapi/linux/pci-tsm-netlink.h          |  101 -
 samples/devsec/common.c                       |    1 -
 samples/devsec/link_tsm.c                     |   17 +-
 samples/devsec/tsm.c                          |   32 +-
 tools/ctlv/tests/support.py                   |    3 +-
 tools/ctlv/tests/test_outputs.py              |    6 +-
 tools/ctlv/tests/test_semantics.py            |   38 +
 tools/ctlv/tests/test_spec.py                 |    1 +
 .../selftests/attestation/attestation_test.c  |   32 +-
 .../ctlv_attestation_pci_user_gen.c           | 1697 +++++++++++++++++
 .../ctlv_attestation_pci_user_gen.h           |  230 +++
 .../ctlv_attestation_sim_user_gen.c           |  161 +-
 .../ctlv_attestation_tdx_user_gen.c           |  161 +-
 .../attestation/ctlv_attestation_user_gen.c   |  161 +-
 .../attestation/ctlv_attestation_user_gen.h   |   70 +-
 .../attestation/tdx_attestation_test.c        |    9 +-
 59 files changed, 4678 insertions(+), 1644 deletions(-)
 create mode 100644 Documentation/ctlv/abi/ctlv-attestation-pci.yaml
 create mode 100644 Documentation/ctlv/specs/ctlv-attestation-pci.yaml
 delete mode 100644 Documentation/netlink/specs/pci-tsm.yaml
 create mode 100644 drivers/pci/tsm/attestation.c
 create mode 100644 drivers/pci/tsm/ctlv_attestation_pci_gen.c
 create mode 100644 drivers/pci/tsm/ctlv_attestation_pci_gen.h
 delete mode 100644 drivers/pci/tsm/evidence.c
 create mode 100644 drivers/pci/tsm/internal.h
 delete mode 100644 drivers/pci/tsm/netlink.c
 delete mode 100644 drivers/pci/tsm/netlink.h
 create mode 100644 include/uapi/linux/ctlv_attestation_pci_gen.h
 delete mode 100644 include/uapi/linux/pci-tsm-netlink.h
 create mode 100644 tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.c
 create mode 100644 tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.h

-- 
2.43.0


^ permalink raw reply	[flat|nested] 17+ messages in thread

* [RFC PATCH v1 01/11] attestation: Restrict inherited operations per instance
  2026-10-28 15:26 [RFC PATCH v1 00/11] PCI/TSM: Add CTLV attestation Aneesh Kumar K.V (Arm)
@ 2026-10-28 15:26 ` Aneesh Kumar K.V (Arm)
  2026-10-28 15:26 ` [RFC PATCH v1 02/11] attestation: Filter evidence objects " Aneesh Kumar K.V (Arm)
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-10-28 15:26 UTC (permalink / raw)
  To: linux-coco
  Cc: Aneesh Kumar K.V (Arm), Jiri Pirko, Kevin Tian,
	Alexey Kardashevskiy, Xu Yilun, Catalin Marinas, Suzuki K Poulose,
	Steven Price, Kiryl Shutsemau, Rick Edgecombe, Dave Hansen,
	Jonathan Cameron, Jason Gunthorpe, Bjorn Helgaas, Ashish Kalra,
	Tom Lendacky, John Allen, Will Deacon, Mark Rutland, patches

Allow an attestation instance to enable only the operations supported by
its device. Update the operations and DEVICE_GET capabilities together
under the instance mutex.

The following PCI/TSM patch uses one attestation implementation for host
links and guest devices. Each instance must expose only the operations
supported by that device:

  Instance                Evidence  Evidence acceptance  TDISP state
  ----------------------  --------  -------------------  -----------
  Host link, PF0          Yes       Yes                  No
  Host link, non-PF0      Yes       No                   No
  Guest device           Yes       If supported         Yes

For a guest device, evidence acceptance requires the TSM driver's accept
callback. A device error also requires evidence retrieval and acceptance
to be disabled for that instance.

Currently, DEVSEC_STATE_GET calls the TDISP callback whenever that callback
is defined, even for a host link. The callback returns -EOPNOTSUPP for the
host link, causing the whole query to fail and hiding its evidence
acceptance verdict.

Track the enabled features separately for each instance. Use them both to
enable operations and to choose which state callbacks DEVSEC_STATE_GET
calls. A host link can then report its acceptance verdict without trying
to read TDISP state. Keep DEVICE_GET capabilities consistent with the
enabled operations.

Cc: Jiri Pirko <jiri@resnulli.us>
Cc: Kiryl Shutsemau <kas@kernel.org>
Cc: Rick Edgecombe <rick.p.edgecombe@intel.com>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Jonathan Cameron <jic23@kernel.org>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 Documentation/ctlv/abi/ctlv-attestation.yaml  |  7 +-
 .../ctlv/specs/ctlv-attestation.yaml          |  3 +
 Documentation/driver-api/attestation.rst      | 14 ++++
 drivers/attestation/core/core.c               | 79 +++++++++++++++++-
 drivers/attestation/core/ctlv.c               | 10 +--
 drivers/attestation/core/internal.h           |  1 +
 .../generated/ctlv_attestation_gen.c          |  6 +-
 .../generated/ctlv_attestation_gen.h          |  6 ++
 drivers/attestation/tests/core_kunit.c        | 83 ++++++++++++++++++-
 include/linux/attestation.h                   | 33 ++++++++
 .../ctlv_attestation_sim_user_gen.c           |  6 +-
 .../ctlv_attestation_tdx_user_gen.c           |  6 +-
 .../attestation/ctlv_attestation_user_gen.c   |  6 +-
 13 files changed, 237 insertions(+), 23 deletions(-)

diff --git a/Documentation/ctlv/abi/ctlv-attestation.yaml b/Documentation/ctlv/abi/ctlv-attestation.yaml
index 48f7454ae6c0..31dbffa59416 100644
--- a/Documentation/ctlv/abi/ctlv-attestation.yaml
+++ b/Documentation/ctlv/abi/ctlv-attestation.yaml
@@ -245,12 +245,13 @@ ops:
                   attrs: [log.template-name, log.template-data, log.violation]
               - record.item: {attrs: [record.format, record.data]}
   evidence-accept:
-    abi: {id: 9, type: action, optional: true}
+    abi: {id: 9, type: action, availability: dynamic, optional: true}
     request: {attrs: [device.generation, evidence.generation]}
   evidence-accept-clear:
-    abi: {id: 10, type: action, optional: true}
+    abi: {id: 10, type: action, availability: dynamic, optional: true}
   devsec-state-get:
-    abi: {id: 11, type: query, fd-access: read, optional: true}
+    abi: {id: 11, type: query, fd-access: read, availability: dynamic,
+          optional: true}
     reply:
       attrs: [device.generation]
       optional-attrs: [evidence.generation, evidence.accepted-generation,
diff --git a/Documentation/ctlv/specs/ctlv-attestation.yaml b/Documentation/ctlv/specs/ctlv-attestation.yaml
index 5a8da657cc8b..570a97b7538f 100644
--- a/Documentation/ctlv/specs/ctlv-attestation.yaml
+++ b/Documentation/ctlv/specs/ctlv-attestation.yaml
@@ -551,6 +551,7 @@ ops:
   - name: evidence-accept
     type: action
     fd-access: read-write
+    availability: dynamic
     optional: true
     request:
       attrs: evidence-generation-request
@@ -558,11 +559,13 @@ ops:
   - name: evidence-accept-clear
     type: action
     fd-access: read-write
+    availability: dynamic
     optional: true
 
   - name: devsec-state-get
     type: query
     fd-access: read
+    availability: dynamic
     optional: true
     reply:
       attrs: devsec-state-reply
diff --git a/Documentation/driver-api/attestation.rst b/Documentation/driver-api/attestation.rst
index baf188aafe54..06af8df23a46 100644
--- a/Documentation/driver-api/attestation.rst
+++ b/Documentation/driver-api/attestation.rst
@@ -29,6 +29,20 @@ dynamic topology retains handles returned by ``attestation_mr_create()`` and
 passes each handle exactly once to ``attestation_mr_destroy()``.  Property
 storage remains valid for the handle's lifetime.
 
+Per-instance features
+=====================
+
+A provider descriptor describes the maximum capabilities of its instances.
+A child family may restrict evidence retrieval, verifier verdicts, TDISP or
+trust-state reporting and RUN through ``attestation_features_set_locked()``.
+The features must be a subset of the descriptor. Verdicts require evidence;
+RUN additionally requires verdicts and at least one native state reporter.
+Providers that do not restrict their features keep the descriptor's defaults.
+
+The setter updates dynamic CTLV operation availability and the capabilities
+reported by DEVICE_GET under the instance mutex. DEVSEC_STATE_GET omits native
+state reporters that the instance has disabled.
+
 State changes
 =============
 
diff --git a/drivers/attestation/core/core.c b/drivers/attestation/core/core.c
index 7d8430bda7ec..41fe1084c989 100644
--- a/drivers/attestation/core/core.c
+++ b/drivers/attestation/core/core.c
@@ -458,6 +458,78 @@ void attestation_state_changed(struct attestation *att)
 }
 EXPORT_SYMBOL_NS_GPL(attestation_state_changed, "ATTESTATION");
 
+static unsigned int attestation_provider_features(const struct attestation_provider *provider)
+{
+	unsigned int features = 0;
+
+	if (provider->evidence_caps)
+		features |= ATTESTATION_FEATURE_EVIDENCE;
+
+	if (provider->evidence_accept)
+		features |= ATTESTATION_FEATURE_EVIDENCE_ACCEPT;
+
+	if (provider->tdisp_state_get)
+		features |= ATTESTATION_FEATURE_TDISP_STATE;
+
+	if (provider->device_trust_get)
+		features |= ATTESTATION_FEATURE_DEVICE_TRUST;
+
+	if (provider->devsec_run)
+		features |= ATTESTATION_FEATURE_DEVSEC_RUN;
+
+	return features;
+}
+
+int attestation_features_set_locked(struct attestation *att, unsigned int features)
+{
+	unsigned int supported = attestation_provider_features(att->provider);
+	unsigned int state = ATTESTATION_FEATURE_TDISP_STATE |
+			     ATTESTATION_FEATURE_DEVICE_TRUST;
+
+	lockdep_assert_held(&att->lock);
+	if (features & ~supported)
+		return -EINVAL;
+
+	if ((features & ATTESTATION_FEATURE_EVIDENCE_ACCEPT) &&
+	    !(features & ATTESTATION_FEATURE_EVIDENCE))
+		return -EINVAL;
+
+	if ((features & ATTESTATION_FEATURE_DEVSEC_RUN) &&
+	    (!(features & ATTESTATION_FEATURE_EVIDENCE_ACCEPT) || !(features & state)))
+		return -EINVAL;
+
+	att->features = features;
+	if (supported & ATTESTATION_FEATURE_EVIDENCE)
+		ctlv_device_op_state_set(att->ctlvdev, CTLV_ATTESTATION_OP_EVIDENCE_GET,
+					 !(features & ATTESTATION_FEATURE_EVIDENCE));
+
+	if (supported & ATTESTATION_FEATURE_EVIDENCE_ACCEPT) {
+		ctlv_device_op_state_set(att->ctlvdev, CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT,
+					 !(features & ATTESTATION_FEATURE_EVIDENCE_ACCEPT));
+		ctlv_device_op_state_set(att->ctlvdev, CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR,
+					 !(features & ATTESTATION_FEATURE_EVIDENCE_ACCEPT));
+	}
+
+	state |= ATTESTATION_FEATURE_EVIDENCE_ACCEPT;
+
+	if (supported & state)
+		ctlv_device_op_state_set(att->ctlvdev, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET,
+					 !(features & state));
+
+	if (supported & ATTESTATION_FEATURE_DEVSEC_RUN)
+		ctlv_device_op_state_set(att->ctlvdev, CTLV_ATTESTATION_OP_DEVSEC_RUN,
+					 !(features & ATTESTATION_FEATURE_DEVSEC_RUN));
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(attestation_features_set_locked, "ATTESTATION");
+
+int attestation_features_set(struct attestation *att, unsigned int features)
+{
+	guard(mutex)(&att->lock);
+	return attestation_features_set_locked(att, features);
+}
+EXPORT_SYMBOL_NS_GPL(attestation_features_set, "ATTESTATION");
+
 void attestation_provide_ops(struct attestation *att)
 {
 	const struct attestation_provider *provider = att->provider;
@@ -469,6 +541,8 @@ void attestation_provide_ops(struct attestation *att)
 		if (provider->evidence_accept) {
 			ctlv_attestation_evidence_accept_provide(ctlvdev);
 			ctlv_attestation_evidence_accept_clear_provide(ctlvdev);
+			ctlv_attestation_evidence_accept_enable(ctlvdev);
+			ctlv_attestation_evidence_accept_clear_enable(ctlvdev);
 		}
 	}
 	if (provider->mr_caps) {
@@ -480,8 +554,10 @@ void attestation_provide_ops(struct attestation *att)
 		if (provider->mr_extend)
 			ctlv_attestation_mr_extend_provide(ctlvdev);
 	}
-	if (provider->tdisp_state_get || provider->device_trust_get || provider->evidence_accept)
+	if (provider->tdisp_state_get || provider->device_trust_get || provider->evidence_accept) {
 		ctlv_attestation_devsec_state_get_provide(ctlvdev);
+		ctlv_attestation_devsec_state_get_enable(ctlvdev);
+	}
 	if (provider->devsec_run) {
 		ctlv_attestation_devsec_run_provide(ctlvdev);
 		ctlv_attestation_devsec_run_enable(ctlvdev);
@@ -502,6 +578,7 @@ int attestation_core_init(struct attestation *att, struct ctlv_device *ctlvdev,
 	xa_init(&att->mrs);
 	att->ctlvdev = ctlvdev;
 	att->provider = provider;
+	att->features = attestation_provider_features(provider);
 	for (index = 0; index < provider->mrs_count; index++) {
 		struct attestation_mr *mr;
 
diff --git a/drivers/attestation/core/ctlv.c b/drivers/attestation/core/ctlv.c
index b08356c3c027..8f4fa48ffdc5 100644
--- a/drivers/attestation/core/ctlv.c
+++ b/drivers/attestation/core/ctlv.c
@@ -60,7 +60,7 @@ static int attestation_device_caps_put(struct attestation *att, struct ctlv_curs
 
 	if (!ctlv_attestation_device_caps_start(reply, &caps_nest))
 		return -EMSGSIZE;
-	if (provider->evidence_caps) {
+	if (att->features & ATTESTATION_FEATURE_EVIDENCE) {
 		const struct attestation_evidence_caps *caps = provider->evidence_caps;
 
 		if (!ctlv_attestation_cap_evidence_start(&caps_nest, &group))
@@ -72,7 +72,7 @@ static int attestation_device_caps_put(struct attestation *att, struct ctlv_curs
 		ok &= ctlv_attestation_cap_max_total_output_length_put(&group,
 								       caps->max_total_output_length);
 		flags = 0;
-		if (provider->evidence_accept)
+		if (att->features & ATTESTATION_FEATURE_EVIDENCE_ACCEPT)
 			flags |= CTLV_ATTESTATION_EVIDENCE_CAPS_EVIDENCE_ACCEPT;
 		if (caps->supports_mr_selection)
 			flags |= CTLV_ATTESTATION_EVIDENCE_CAPS_MR_SELECTION;
@@ -119,7 +119,7 @@ static int attestation_device_caps_put(struct attestation *att, struct ctlv_curs
 		ok &= ctlv_attestation_cap_mr_flags_put(&group, flags);
 		ctlv_nest_end(&group);
 	}
-	if (provider->devsec_run) {
+	if (att->features & ATTESTATION_FEATURE_DEVSEC_RUN) {
 		if (!ctlv_attestation_cap_devsec_start(&caps_nest, &group))
 			return -EMSGSIZE;
 		ok &= ctlv_attestation_cap_devsec_flags_put(&group,
@@ -935,13 +935,13 @@ int ctlv_attestation_devsec_state_get_query(void *priv, struct ctlv_op_ctx *ctx,
 	if (att->verdict_recorded)
 		ok &= ctlv_attestation_evidence_accepted_generation_put(reply,
 									att->accepted_evidence_generation);
-	if (att->provider->tdisp_state_get) {
+	if (att->features & ATTESTATION_FEATURE_TDISP_STATE) {
 		ret = att->provider->tdisp_state_get(att, &state);
 		if (ret)
 			return ret;
 		ok &= ctlv_attestation_devsec_tdisp_state_put(reply, state);
 	}
-	if (att->provider->device_trust_get) {
+	if (att->features & ATTESTATION_FEATURE_DEVICE_TRUST) {
 		ret = att->provider->device_trust_get(att, &trust);
 		if (ret)
 			return ret;
diff --git a/drivers/attestation/core/internal.h b/drivers/attestation/core/internal.h
index bd84efb5abb8..9e531e8483f7 100644
--- a/drivers/attestation/core/internal.h
+++ b/drivers/attestation/core/internal.h
@@ -70,6 +70,7 @@ struct attestation_mr_log {
 struct attestation {
 	struct ctlv_device *ctlvdev;
 	const struct attestation_provider *provider;
+	unsigned int features;
 	unsigned int index;
 
 	/* Mutable instance, MR, verdict, and journal state. */
diff --git a/drivers/attestation/generated/ctlv_attestation_gen.c b/drivers/attestation/generated/ctlv_attestation_gen.c
index 911b32255dd5..5882dd2c93a7 100644
--- a/drivers/attestation/generated/ctlv_attestation_gen.c
+++ b/drivers/attestation/generated/ctlv_attestation_gen.c
@@ -300,15 +300,15 @@ const struct ctlv_op_schema ctlv_attestation_op_schemas[] = {
 	CTLV_OP_SCHEMA_EVENT(CTLV_ATTESTATION_OP_MR_LOG_ENTRY_APPENDED, 73416,
 			     CTLV_SCHEMA_TREE(mr_log_entry_appended_reply)),
 	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT, CTLV_OP_FD_ACCESS_READ_WRITE,
-			      CTLV_OP_AVAILABILITY_FIXED, 64, 0,
+			      CTLV_OP_AVAILABILITY_DYNAMIC, 64, 0,
 			      CTLV_SCHEMA_TREE(evidence_accept_request), CTLV_SCHEMA_TREE_NONE(),
 			      .optional = true),
 	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR,
-			      CTLV_OP_FD_ACCESS_READ_WRITE, CTLV_OP_AVAILABILITY_FIXED, 16, 0,
+			      CTLV_OP_FD_ACCESS_READ_WRITE, CTLV_OP_AVAILABILITY_DYNAMIC, 16, 0,
 			      CTLV_SCHEMA_TREE(evidence_accept_clear_request),
 			      CTLV_SCHEMA_TREE_NONE(), .optional = true),
 	CTLV_OP_SCHEMA_QUERY(CTLV_ATTESTATION_OP_DEVSEC_STATE_GET, CTLV_OP_FD_ACCESS_READ,
-			     CTLV_OP_AVAILABILITY_FIXED, 16, 40, 1024,
+			     CTLV_OP_AVAILABILITY_DYNAMIC, 16, 40, 1024,
 			     CTLV_SCHEMA_TREE(devsec_state_get_request),
 			     CTLV_SCHEMA_TREE(devsec_state_get_reply), .optional = true),
 	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_DEVSEC_RUN, CTLV_OP_FD_ACCESS_READ_WRITE,
diff --git a/drivers/attestation/generated/ctlv_attestation_gen.h b/drivers/attestation/generated/ctlv_attestation_gen.h
index 028026a82e8a..a1f125690005 100644
--- a/drivers/attestation/generated/ctlv_attestation_gen.h
+++ b/drivers/attestation/generated/ctlv_attestation_gen.h
@@ -73,8 +73,14 @@ CTLV_OP_PROVIDE(ctlv_attestation_mr_dump, CTLV_ATTESTATION_OP_MR_DUMP)
 CTLV_OP_PROVIDE(ctlv_attestation_mr_read, CTLV_ATTESTATION_OP_MR_READ)
 CTLV_OP_PROVIDE(ctlv_attestation_mr_extend, CTLV_ATTESTATION_OP_MR_EXTEND)
 CTLV_OP_PROVIDE(ctlv_attestation_evidence_accept, CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT)
+CTLV_OP_ENABLE(ctlv_attestation_evidence_accept, CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT)
+CTLV_OP_DISABLE(ctlv_attestation_evidence_accept, CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT)
 CTLV_OP_PROVIDE(ctlv_attestation_evidence_accept_clear, CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR)
+CTLV_OP_ENABLE(ctlv_attestation_evidence_accept_clear, CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR)
+CTLV_OP_DISABLE(ctlv_attestation_evidence_accept_clear, CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR)
 CTLV_OP_PROVIDE(ctlv_attestation_devsec_state_get, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET)
+CTLV_OP_ENABLE(ctlv_attestation_devsec_state_get, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET)
+CTLV_OP_DISABLE(ctlv_attestation_devsec_state_get, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET)
 CTLV_OP_PROVIDE(ctlv_attestation_devsec_run, CTLV_ATTESTATION_OP_DEVSEC_RUN)
 CTLV_OP_ENABLE(ctlv_attestation_devsec_run, CTLV_ATTESTATION_OP_DEVSEC_RUN)
 CTLV_OP_DISABLE(ctlv_attestation_devsec_run, CTLV_ATTESTATION_OP_DEVSEC_RUN)
diff --git a/drivers/attestation/tests/core_kunit.c b/drivers/attestation/tests/core_kunit.c
index 290c83009991..7e4710370cfe 100644
--- a/drivers/attestation/tests/core_kunit.c
+++ b/drivers/attestation/tests/core_kunit.c
@@ -142,11 +142,10 @@ static void attestation_test_provider_init(struct attestation_test *fixture)
 	};
 }
 
-static int attestation_test_create(struct kunit *test,
+static int attestation_test_create_provider(struct kunit *test,
 				   struct attestation_test *fixture,
 				   const char *name)
 {
-	attestation_test_provider_init(fixture);
 	fixture->parent = kunit_device_register(test, name);
 	if (IS_ERR(fixture->parent))
 		return PTR_ERR(fixture->parent);
@@ -168,6 +167,14 @@ static int attestation_test_create(struct kunit *test,
 		test, attestation_test_destroy, fixture);
 }
 
+static int attestation_test_create(struct kunit *test,
+				   struct attestation_test *fixture,
+				   const char *name)
+{
+	attestation_test_provider_init(fixture);
+	return attestation_test_create_provider(test, fixture, name);
+}
+
 static int attestation_test_init(struct kunit *test)
 {
 	struct attestation_test *fixture;
@@ -210,6 +217,76 @@ static void attestation_test_provider_contracts(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, PTR_ERR(created), -EINVAL);
 }
 
+static void attestation_test_instance_features(struct kunit *test)
+{
+	struct attestation_test *fixture = test->priv;
+	unsigned int features = ATTESTATION_FEATURE_EVIDENCE |
+				ATTESTATION_FEATURE_EVIDENCE_ACCEPT;
+
+	KUNIT_ASSERT_EQ(test, attestation_test_create(
+				test, fixture, "attestation-features"), 0);
+	KUNIT_EXPECT_EQ(test, fixture->att->features, features);
+	KUNIT_EXPECT_EQ(test, attestation_features_set(fixture->att,
+			ATTESTATION_FEATURE_EVIDENCE_ACCEPT), -EINVAL);
+	KUNIT_EXPECT_EQ(test, fixture->att->features, features);
+	/* A child cannot enable a callback that its provider did not supply. */
+	KUNIT_EXPECT_EQ(test, attestation_features_set(fixture->att,
+			features | ATTESTATION_FEATURE_DEVSEC_RUN), -EINVAL);
+	KUNIT_EXPECT_EQ(test, fixture->att->features, features);
+	KUNIT_ASSERT_EQ(test, attestation_features_set(fixture->att, 0), 0);
+	KUNIT_EXPECT_EQ(test, fixture->att->features, 0U);
+	KUNIT_ASSERT_EQ(test, attestation_features_set(fixture->att,
+			ATTESTATION_FEATURE_EVIDENCE), 0);
+	KUNIT_EXPECT_EQ(test, fixture->att->features,
+			(unsigned int)ATTESTATION_FEATURE_EVIDENCE);
+	KUNIT_ASSERT_EQ(test, attestation_features_set(fixture->att, features), 0);
+	KUNIT_EXPECT_EQ(test, fixture->att->features, features);
+}
+
+static int attestation_test_tdisp_state(struct attestation *att,
+					enum attestation_tdisp_state *state)
+{
+	*state = ATTESTATION_TDISP_STATE_LOCKED;
+	return 0;
+}
+
+static int attestation_test_run(struct attestation *att)
+{
+	return 0;
+}
+
+static void attestation_test_secure_features(struct kunit *test)
+{
+	struct attestation_test *fixture = test->priv;
+	unsigned int evidence = ATTESTATION_FEATURE_EVIDENCE |
+				ATTESTATION_FEATURE_EVIDENCE_ACCEPT;
+	unsigned int state = ATTESTATION_FEATURE_TDISP_STATE;
+	unsigned int run = ATTESTATION_FEATURE_DEVSEC_RUN;
+
+	attestation_test_provider_init(fixture);
+	fixture->provider.tdisp_state_get = attestation_test_tdisp_state;
+	fixture->provider.devsec_run = attestation_test_run;
+	KUNIT_ASSERT_EQ(test, attestation_test_create_provider(
+				test, fixture, "attestation-secure-features"), 0);
+	KUNIT_EXPECT_EQ(test, fixture->att->features, evidence | state | run);
+	KUNIT_EXPECT_EQ(test, attestation_features_set(fixture->att, run), -EINVAL);
+	KUNIT_EXPECT_EQ(test, attestation_features_set(fixture->att,
+			evidence | run), -EINVAL);
+	KUNIT_EXPECT_EQ(test, attestation_features_set(fixture->att,
+			state | run), -EINVAL);
+	/* Unlocked, locked, RUN, and host verdict-only capability sets. */
+	KUNIT_ASSERT_EQ(test, attestation_features_set(fixture->att, state), 0);
+	KUNIT_EXPECT_EQ(test, fixture->att->features, state);
+	KUNIT_ASSERT_EQ(test, attestation_features_set(fixture->att,
+			evidence | state | run), 0);
+	KUNIT_EXPECT_EQ(test, fixture->att->features, evidence | state | run);
+	KUNIT_ASSERT_EQ(test, attestation_features_set(fixture->att,
+			evidence | state), 0);
+	KUNIT_EXPECT_EQ(test, fixture->att->features, evidence | state);
+	KUNIT_ASSERT_EQ(test, attestation_features_set(fixture->att, evidence), 0);
+	KUNIT_EXPECT_EQ(test, fixture->att->features, evidence);
+}
+
 static void attestation_test_initial_journal(struct kunit *test)
 {
 	struct attestation_test *fixture = test->priv;
@@ -594,6 +671,8 @@ static void attestation_test_kexec_ima_roundtrip(struct kunit *test)
 static struct kunit_case attestation_test_cases[] = {
 	KUNIT_CASE(attestation_test_provider_contracts),
 	KUNIT_CASE(attestation_test_initial_journal),
+	KUNIT_CASE(attestation_test_instance_features),
+	KUNIT_CASE(attestation_test_secure_features),
 	KUNIT_CASE(attestation_test_target_extension),
 	KUNIT_CASE(attestation_test_failed_extension),
 	KUNIT_CASE(attestation_test_state_change),
diff --git a/include/linux/attestation.h b/include/linux/attestation.h
index ae6ca4024b2a..dede1b53f67a 100644
--- a/include/linux/attestation.h
+++ b/include/linux/attestation.h
@@ -14,6 +14,15 @@ struct attestation_mr;
 struct ctlv_blob;
 struct ctlv_cursor;
 
+/* Per-instance subsets of the immutable provider's capabilities. */
+enum attestation_feature {
+	ATTESTATION_FEATURE_EVIDENCE = 1U << 0,
+	ATTESTATION_FEATURE_EVIDENCE_ACCEPT = 1U << 1,
+	ATTESTATION_FEATURE_TDISP_STATE = 1U << 2,
+	ATTESTATION_FEATURE_DEVICE_TRUST = 1U << 3,
+	ATTESTATION_FEATURE_DEVSEC_RUN = 1U << 4,
+};
+
 enum attestation_evidence_representation {
 	ATTESTATION_EVIDENCE_REPRESENTATION_RAW =
 		CTLV_ATTESTATION_EVIDENCE_REPRESENTATION_RAW,
@@ -285,4 +294,28 @@ void attestation_mr_destroy(struct attestation_mr *mr);
  */
 void attestation_state_changed(struct attestation *att);
 
+/**
+ * attestation_features_set_locked - Set enabled features with the mutex held.
+ * @att: attestation instance
+ * @features: features to enable
+ *
+ * The caller must hold the instance mutex.
+ * Update operation availability and DEVICE_GET capabilities together.
+ *
+ * Return: 0 on success, or -EINVAL if the requested features are unsupported
+ * or their dependencies are not satisfied.
+ */
+int attestation_features_set_locked(struct attestation *att, unsigned int features);
+
+/**
+ * attestation_features_set - Set enabled features.
+ * @att: attestation instance
+ * @features: features to enable
+ *
+ * Takes the instance mutex. Follow the same lock order as the CTLV pre-hook.
+ *
+ * Return: 0 on success, or -EINVAL for unsupported features or unmet dependencies.
+ */
+int attestation_features_set(struct attestation *att, unsigned int features);
+
 #endif /* _LINUX_ATTESTATION_H */
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c b/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c
index dd558431324e..bccac87ea4fc 100644
--- a/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c
+++ b/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c
@@ -1552,7 +1552,7 @@ static const struct ctlv_op_spec ctlv_attestation_sim_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT,
 		.type = CTLV_OP_TYPE_ACTION,
 		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 64,
 		.request = &evidence_accept_request_node_schema,
@@ -1561,7 +1561,7 @@ static const struct ctlv_op_spec ctlv_attestation_sim_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR,
 		.type = CTLV_OP_TYPE_ACTION,
 		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 16,
 		.request = &evidence_accept_clear_request_node_schema,
@@ -1570,7 +1570,7 @@ static const struct ctlv_op_spec ctlv_attestation_sim_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_DEVSEC_STATE_GET,
 		.type = CTLV_OP_TYPE_QUERY,
 		.fd_access = CTLV_OP_FD_ACCESS_READ,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 16,
 		.query_reply_min_len = 40,
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c b/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c
index 50bd181d0d15..3395c0ffc3e5 100644
--- a/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c
+++ b/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c
@@ -1568,7 +1568,7 @@ static const struct ctlv_op_spec ctlv_attestation_tdx_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT,
 		.type = CTLV_OP_TYPE_ACTION,
 		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 64,
 		.request = &evidence_accept_request_node_schema,
@@ -1577,7 +1577,7 @@ static const struct ctlv_op_spec ctlv_attestation_tdx_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR,
 		.type = CTLV_OP_TYPE_ACTION,
 		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 16,
 		.request = &evidence_accept_clear_request_node_schema,
@@ -1586,7 +1586,7 @@ static const struct ctlv_op_spec ctlv_attestation_tdx_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_DEVSEC_STATE_GET,
 		.type = CTLV_OP_TYPE_QUERY,
 		.fd_access = CTLV_OP_FD_ACCESS_READ,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 16,
 		.query_reply_min_len = 40,
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c b/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c
index d62affc3a491..6a363b29f80b 100644
--- a/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c
+++ b/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c
@@ -1538,7 +1538,7 @@ static const struct ctlv_op_spec ctlv_attestation_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT,
 		.type = CTLV_OP_TYPE_ACTION,
 		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 64,
 		.request = &evidence_accept_request_node_schema,
@@ -1547,7 +1547,7 @@ static const struct ctlv_op_spec ctlv_attestation_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR,
 		.type = CTLV_OP_TYPE_ACTION,
 		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 16,
 		.request = &evidence_accept_clear_request_node_schema,
@@ -1556,7 +1556,7 @@ static const struct ctlv_op_spec ctlv_attestation_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_DEVSEC_STATE_GET,
 		.type = CTLV_OP_TYPE_QUERY,
 		.fd_access = CTLV_OP_FD_ACCESS_READ,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 16,
 		.query_reply_min_len = 40,
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [RFC PATCH v1 02/11] attestation: Filter evidence objects per instance
  2026-10-28 15:26 [RFC PATCH v1 00/11] PCI/TSM: Add CTLV attestation Aneesh Kumar K.V (Arm)
  2026-10-28 15:26 ` [RFC PATCH v1 01/11] attestation: Restrict inherited operations per instance Aneesh Kumar K.V (Arm)
@ 2026-10-28 15:26 ` Aneesh Kumar K.V (Arm)
  2026-10-28 15:26 ` [RFC PATCH v1 03/11] attestation: Enter secure operation when evidence is accepted Aneesh Kumar K.V (Arm)
                   ` (8 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-10-28 15:26 UTC (permalink / raw)
  To: linux-coco
  Cc: Aneesh Kumar K.V (Arm), Jiri Pirko, Kevin Tian,
	Alexey Kardashevskiy, Xu Yilun, Catalin Marinas, Suzuki K Poulose,
	Steven Price, Kiryl Shutsemau, Rick Edgecombe, Dave Hansen,
	Jonathan Cameron, Jason Gunthorpe, Bjorn Helgaas, Ashish Kalra,
	Tom Lendacky, John Allen, Will Deacon, Mark Rutland, patches

Allow an attestation instance to expose only the evidence objects its
device supports. Add an optional evidence_object_supported() callback
and use it both when building DEVICE_GET capabilities and when checking
EVIDENCE_GET requests.

The following PCI/TSM patch uses one attestation implementation for
devices that support different evidence objects. Its list includes
certificate slots 0-7, SPDM VCA, measurements, and a TDISP interface
report. Each device must expose only the objects it supports, for
example:

  Instance                  Supported evidence objects
  ------------------------  ------------------------------------------
  Host link using slot zero Certificate slot 0
  CCA guest device          Certificate slot 0, VCA, measurements,
                            TDISP interface report
  Host PF0 after discovery  Certificate slots reported populated by SPDM

PCI/TSM checks the TSM driver's evidence_types bitmap or, after SPDM
discovery on a host PF0, the populated-slot bitmap. The preceding patch
controls whether EVIDENCE_GET is enabled; this patch controls which
objects that operation can retrieve.

Cc: Jiri Pirko <jiri@resnulli.us>
Cc: Jonathan Cameron <jic23@kernel.org>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 drivers/attestation/core/core.c |  8 ++++++--
 drivers/attestation/core/ctlv.c | 13 +++++++++++--
 include/linux/attestation.h     |  4 ++++
 3 files changed, 21 insertions(+), 4 deletions(-)

diff --git a/drivers/attestation/core/core.c b/drivers/attestation/core/core.c
index 41fe1084c989..37d5d3233cf0 100644
--- a/drivers/attestation/core/core.c
+++ b/drivers/attestation/core/core.c
@@ -54,8 +54,12 @@ static int attestation_evidence_caps_validate(const struct attestation_provider
 	const struct attestation_evidence_caps *caps = provider->evidence_caps;
 	size_t object_index;
 
-	if (!caps)
-		return provider->evidence_get ? -EINVAL : 0;
+	if (!caps) {
+		if (provider->evidence_get || provider->evidence_object_supported)
+			return -EINVAL;
+		return 0;
+	}
+
 	if (!provider->evidence_get || !caps->objects || !caps->objects_count ||
 	    caps->objects_count > 16 || caps->min_challenge_length > caps->max_challenge_length ||
 	    caps->max_challenge_length > 4096 || !caps->max_total_output_length)
diff --git a/drivers/attestation/core/ctlv.c b/drivers/attestation/core/ctlv.c
index 8f4fa48ffdc5..baf9c39f2658 100644
--- a/drivers/attestation/core/ctlv.c
+++ b/drivers/attestation/core/ctlv.c
@@ -33,15 +33,20 @@ attestation_representation_in(const struct attestation_evidence_supported_object
 }
 
 static const struct attestation_evidence_supported_object *
-attestation_evidence_object_find(const struct attestation_evidence_caps *caps,
+attestation_evidence_object_find(struct attestation *att,
 				 enum attestation_evidence_blob_class blob_class, bool has_instance,
 				 unsigned int instance, const char *format)
 {
+	const struct attestation_evidence_caps *caps = att->provider->evidence_caps;
 	size_t index;
 
 	for (index = 0; index < caps->objects_count; index++) {
 		const struct attestation_evidence_supported_object *object = &caps->objects[index];
 
+		if (att->provider->evidence_object_supported &&
+		    !att->provider->evidence_object_supported(att, object))
+			continue;
+
 		if (object->blob_class == blob_class && object->has_blob_instance == has_instance &&
 		    (!has_instance || object->blob_instance == instance) &&
 		    !strcmp(object->format, format))
@@ -85,6 +90,10 @@ static int attestation_device_caps_put(struct attestation *att, struct ctlv_curs
 			const struct attestation_evidence_supported_object *cap =
 				&caps->objects[index];
 
+			if (provider->evidence_object_supported &&
+			    !provider->evidence_object_supported(att, cap))
+				continue;
+
 			if (!ctlv_attestation_cap_object_start(&group, &object)) {
 				ok = false;
 				break;
@@ -184,7 +193,7 @@ attestation_evidence_decode_objects(struct attestation *att, struct ctlv_op_ctx
 		if (has_instance)
 			instance = ctlv_attestation_evidence_get_object_evidence_blob_instance_get(
 				&object);
-		supported = attestation_evidence_object_find(caps, blob_class, has_instance,
+		supported = attestation_evidence_object_find(att, blob_class, has_instance,
 							     instance, format);
 		if (!supported || !attestation_representation_in(supported, representation)) {
 			kfree(objects);
diff --git a/include/linux/attestation.h b/include/linux/attestation.h
index dede1b53f67a..364ab5029277 100644
--- a/include/linux/attestation.h
+++ b/include/linux/attestation.h
@@ -233,6 +233,8 @@ struct attestation_evidence_result {
 
 /**
  * struct attestation_provider - Immutable provider operations and properties.
+ * @evidence_object_supported: optionally restrict advertised and requested
+ *	evidence objects per instance; called under the instance lock
  * @evidence_get: produce requested evidence
  * @evidence_accept: record acceptance of the current evidence
  * @evidence_accept_clear: clear a recorded acceptance
@@ -247,6 +249,8 @@ struct attestation_evidence_result {
  * @mrs_count: number of fixed MRs
  */
 struct attestation_provider {
+	bool (*evidence_object_supported)(struct attestation *att,
+		const struct attestation_evidence_supported_object *object);
 	int (*evidence_get)(struct attestation *att,
 			    const struct attestation_evidence_request *request,
 			    struct attestation_evidence_result *result);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [RFC PATCH v1 03/11] attestation: Enter secure operation when evidence is accepted
  2026-10-28 15:26 [RFC PATCH v1 00/11] PCI/TSM: Add CTLV attestation Aneesh Kumar K.V (Arm)
  2026-10-28 15:26 ` [RFC PATCH v1 01/11] attestation: Restrict inherited operations per instance Aneesh Kumar K.V (Arm)
  2026-10-28 15:26 ` [RFC PATCH v1 02/11] attestation: Filter evidence objects " Aneesh Kumar K.V (Arm)
@ 2026-10-28 15:26 ` Aneesh Kumar K.V (Arm)
  2026-10-28 15:26 ` [RFC PATCH v1 04/11] attestation: Bind object selection to accepted evidence snapshots Aneesh Kumar K.V (Arm)
                   ` (7 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-10-28 15:26 UTC (permalink / raw)
  To: linux-coco
  Cc: Aneesh Kumar K.V (Arm), Jiri Pirko, Kevin Tian,
	Alexey Kardashevskiy, Xu Yilun, Catalin Marinas, Suzuki K Poulose,
	Steven Price, Kiryl Shutsemau, Rick Edgecombe, Dave Hansen,
	Jonathan Cameron, Jason Gunthorpe, Bjorn Helgaas, Ashish Kalra,
	Tom Lendacky, John Allen, Will Deacon, Mark Rutland, patches

Use EVIDENCE_ACCEPT to enter secure operation after userspace has
checked the evidence. Check the referenced device and evidence
generations before calling the driver, and record acceptance on success.

Remove the separate DEVSEC_RUN command and callback.

Keep DEVSEC_STATE_GET so userspace can read both evidence acceptance and
device state. Clearing acceptance does not undo the device state change.
For example, with the following PCI/TSM patches:

  Operation              Evidence acceptance  TDISP state
  ---------------------  -------------------  -----------
  EVIDENCE_ACCEPT        Recorded             RUN
  EVIDENCE_ACCEPT_CLEAR  Cleared              RUN

After EVIDENCE_ACCEPT_CLEAR, DEVSEC_STATE_GET omits the accepted
evidence generation but still reports TDISP RUN.

Cc: Jiri Pirko <jiri@resnulli.us>
Cc: Kiryl Shutsemau <kas@kernel.org>
Cc: Rick Edgecombe <rick.p.edgecombe@intel.com>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Jonathan Cameron <jic23@kernel.org>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 Documentation/ctlv/abi/ctlv-attestation.yaml  | 38 ++++-----
 .../ctlv/specs/ctlv-attestation.yaml          | 25 ------
 drivers/attestation/core/core.c               | 17 ----
 drivers/attestation/core/ctlv.c               | 45 +---------
 .../generated/ctlv_attestation_gen.c          | 29 ++-----
 .../generated/ctlv_attestation_gen.h          | 26 +-----
 drivers/attestation/providers/sim.c           | 35 ++------
 drivers/attestation/tests/core_kunit.c        | 19 +----
 include/linux/attestation.h                   |  5 +-
 include/uapi/linux/ctlv_attestation_gen.h     | 16 ----
 .../selftests/attestation/attestation_test.c  | 28 ++-----
 .../ctlv_attestation_sim_user_gen.c           | 83 +------------------
 .../ctlv_attestation_tdx_user_gen.c           | 83 +------------------
 .../attestation/ctlv_attestation_user_gen.c   | 83 +------------------
 .../attestation/ctlv_attestation_user_gen.h   | 52 +-----------
 15 files changed, 51 insertions(+), 533 deletions(-)

diff --git a/Documentation/ctlv/abi/ctlv-attestation.yaml b/Documentation/ctlv/abi/ctlv-attestation.yaml
index 31dbffa59416..0220c2e131d4 100644
--- a/Documentation/ctlv/abi/ctlv-attestation.yaml
+++ b/Documentation/ctlv/abi/ctlv-attestation.yaml
@@ -22,7 +22,6 @@ flags:
   mr-device-caps: {read: 0, extend: 1, dynamic-topology: 2}
   mr-caps: {read: 0, extend: 1, removable: 2}
   mr-state-flags: {ima-active: 0}
-  devsec-caps: {run: 0}
   evidence-caps: {evidence-accept: 0, mr-selection: 1, privlevel: 2, service: 3}
 attr-groups:
   device:
@@ -47,10 +46,8 @@ attr-groups:
                        max-occurrences: 2}
       max-length: {id: 11, type: uint}
       evidence-flags: {id: 12, flags: evidence-caps}
-      devsec: {id: 13, type: nested}
-      devsec-flags: {id: 14, flags: devsec-caps}
-      blob-instance: {id: 15, type: uint, max: 4294967295}
-      hash-algo: {id: 16, type: uint, max: 20, max-occurrences: 21}
+      blob-instance: {id: 13, type: uint, max: 4294967295}
+      hash-algo: {id: 14, type: uint, max: 20, max-occurrences: 21}
   evidence:
     id: 3
     attrs:
@@ -138,18 +135,16 @@ attr-sets:
   device-state-event: {id: 20}
   mr-log-event: {id: 21}
   mr-log-ima: {id: 22}
-  devsec-cap: {id: 23}
-  evidence-generation-request: {id: 24}
-  devsec-state-reply: {id: 25}
-  devsec-run-reply: {id: 26}
-  evidence-state-event: {id: 27}
-  evidence-options: {id: 28, stub: true}
-  mr-options: {id: 29, stub: true}
-  evidence-mr-selection: {id: 30}
-  record-data: {id: 31}
-  mr-log-selection: {id: 32}
-  mr-log-request: {id: 33}
-  mr-log-reply: {id: 34}
+  evidence-generation-request: {id: 23}
+  devsec-state-reply: {id: 24}
+  evidence-state-event: {id: 25}
+  evidence-options: {id: 26, stub: true}
+  mr-options: {id: 27, stub: true}
+  evidence-mr-selection: {id: 28}
+  record-data: {id: 29}
+  mr-log-selection: {id: 30}
+  mr-log-request: {id: 31}
+  mr-log-reply: {id: 32}
 ops:
   device-get:
     abi: {id: 1, type: action, fd-access: read}
@@ -169,7 +164,6 @@ ops:
                         optional-attrs: [cap.blob-instance, cap.hash-algo]
                   optional-attrs: [cap.evidence-flags]
               - cap.mr: {attrs: [cap.mr-flags]}
-              - cap.devsec: {attrs: [cap.devsec-flags]}
         - device.info: {attrs: [device-info]}
   evidence-get:
     abi: {id: 2, type: action, availability: dynamic, optional: true}
@@ -256,17 +250,13 @@ ops:
       attrs: [device.generation]
       optional-attrs: [evidence.generation, evidence.accepted-generation,
                        devsec.tdisp-state, devsec.device-trust]
-  devsec-run:
-    abi: {id: 12, type: action, availability: dynamic, optional: true}
-    request: {attrs: [device.generation, evidence.generation]}
-    reply: {attrs: [device.generation]}
   evidence-state-changed:
-    abi: {id: 13, type: event}
+    abi: {id: 12, type: event}
     reply:
       attrs: [device.generation]
       optional-attrs: [evidence.generation, evidence.accepted-generation]
   mr-log-get:
-    abi: {id: 14, type: query, fd-access: read, optional: true}
+    abi: {id: 13, type: query, fd-access: read, optional: true}
     request:
       optional-attrs:
         - mr.selection: {attrs: [mr.id]}
diff --git a/Documentation/ctlv/specs/ctlv-attestation.yaml b/Documentation/ctlv/specs/ctlv-attestation.yaml
index 570a97b7538f..8494f716f9e9 100644
--- a/Documentation/ctlv/specs/ctlv-attestation.yaml
+++ b/Documentation/ctlv/specs/ctlv-attestation.yaml
@@ -62,10 +62,6 @@ flags:
     values:
       - ima-active
 
-  - name: devsec-caps
-    values:
-      - run
-
   - name: evidence-caps
     values:
       - evidence-accept
@@ -115,10 +111,6 @@ attr-groups:
         type: uint
       - name: evidence-flags
         flags: evidence-caps
-      - name: devsec
-        attrs: devsec-cap
-      - name: devsec-flags
-        flags: devsec-caps
       - name: blob-instance
         type: uint
         max: 4294967295
@@ -306,7 +298,6 @@ attr-sets:
     optional-attrs:
       - cap.evidence
       - cap.mr
-      - cap.devsec
 
   - name: device-reply
     attrs:
@@ -429,9 +420,6 @@ attr-sets:
       - log.template-data
       - log.violation
 
-  - name: devsec-cap
-    attrs: cap.devsec-flags
-
   - name: evidence-generation-request
     attrs:
       - device.generation
@@ -445,9 +433,6 @@ attr-sets:
       - devsec.tdisp-state
       - devsec.device-trust
 
-  - name: devsec-run-reply
-    attrs: device.generation
-
   - name: evidence-state-event
     attrs: device.generation
     optional-attrs:
@@ -571,16 +556,6 @@ ops:
       attrs: devsec-state-reply
     suggested-reply-length: 1024
 
-  - name: devsec-run
-    type: action
-    fd-access: read-write
-    availability: dynamic
-    optional: true
-    request:
-      attrs: evidence-generation-request
-    reply:
-      attrs: devsec-run-reply
-
   - name: evidence-state-changed
     type: event
     reply:
diff --git a/drivers/attestation/core/core.c b/drivers/attestation/core/core.c
index 37d5d3233cf0..a1de19dcab7d 100644
--- a/drivers/attestation/core/core.c
+++ b/drivers/attestation/core/core.c
@@ -137,9 +137,6 @@ int attestation_provider_validate(const struct attestation_provider *provider)
 		if (ret)
 			return ret;
 	}
-	if (provider->devsec_run && (!provider->evidence_accept ||
-				     (!provider->tdisp_state_get && !provider->device_trust_get)))
-		return -EINVAL;
 	return 0;
 }
 
@@ -478,9 +475,6 @@ static unsigned int attestation_provider_features(const struct attestation_provi
 	if (provider->device_trust_get)
 		features |= ATTESTATION_FEATURE_DEVICE_TRUST;
 
-	if (provider->devsec_run)
-		features |= ATTESTATION_FEATURE_DEVSEC_RUN;
-
 	return features;
 }
 
@@ -498,10 +492,6 @@ int attestation_features_set_locked(struct attestation *att, unsigned int featur
 	    !(features & ATTESTATION_FEATURE_EVIDENCE))
 		return -EINVAL;
 
-	if ((features & ATTESTATION_FEATURE_DEVSEC_RUN) &&
-	    (!(features & ATTESTATION_FEATURE_EVIDENCE_ACCEPT) || !(features & state)))
-		return -EINVAL;
-
 	att->features = features;
 	if (supported & ATTESTATION_FEATURE_EVIDENCE)
 		ctlv_device_op_state_set(att->ctlvdev, CTLV_ATTESTATION_OP_EVIDENCE_GET,
@@ -520,9 +510,6 @@ int attestation_features_set_locked(struct attestation *att, unsigned int featur
 		ctlv_device_op_state_set(att->ctlvdev, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET,
 					 !(features & state));
 
-	if (supported & ATTESTATION_FEATURE_DEVSEC_RUN)
-		ctlv_device_op_state_set(att->ctlvdev, CTLV_ATTESTATION_OP_DEVSEC_RUN,
-					 !(features & ATTESTATION_FEATURE_DEVSEC_RUN));
 	return 0;
 }
 EXPORT_SYMBOL_NS_GPL(attestation_features_set_locked, "ATTESTATION");
@@ -562,10 +549,6 @@ void attestation_provide_ops(struct attestation *att)
 		ctlv_attestation_devsec_state_get_provide(ctlvdev);
 		ctlv_attestation_devsec_state_get_enable(ctlvdev);
 	}
-	if (provider->devsec_run) {
-		ctlv_attestation_devsec_run_provide(ctlvdev);
-		ctlv_attestation_devsec_run_enable(ctlvdev);
-	}
 }
 
 int attestation_core_init(struct attestation *att, struct ctlv_device *ctlvdev,
diff --git a/drivers/attestation/core/ctlv.c b/drivers/attestation/core/ctlv.c
index baf9c39f2658..7f755b7aaf51 100644
--- a/drivers/attestation/core/ctlv.c
+++ b/drivers/attestation/core/ctlv.c
@@ -128,13 +128,6 @@ static int attestation_device_caps_put(struct attestation *att, struct ctlv_curs
 		ok &= ctlv_attestation_cap_mr_flags_put(&group, flags);
 		ctlv_nest_end(&group);
 	}
-	if (att->features & ATTESTATION_FEATURE_DEVSEC_RUN) {
-		if (!ctlv_attestation_cap_devsec_start(&caps_nest, &group))
-			return -EMSGSIZE;
-		ok &= ctlv_attestation_cap_devsec_flags_put(&group,
-							    CTLV_ATTESTATION_DEVSEC_CAPS_RUN);
-		ctlv_nest_end(&group);
-	}
 	ctlv_nest_end(&caps_nest);
 	return ok ? 0 : -EMSGSIZE;
 }
@@ -584,6 +577,7 @@ int ctlv_attestation_evidence_accept_action(void *priv, struct ctlv_op_ctx *ctx,
 	ret = attestation_events_prepare(att, &events, false, true, false);
 	if (ret)
 		return ret;
+	/* Acceptance authorizes this snapshot without retiring its generation binding. */
 	ret = att->provider->evidence_accept(att);
 	if (!ret) {
 		att->verdict_recorded = true;
@@ -960,40 +954,3 @@ int ctlv_attestation_devsec_state_get_query(void *priv, struct ctlv_op_ctx *ctx,
 		return -EMSGSIZE;
 	return ctlv_op_query_done(ctx, version);
 }
-
-int ctlv_attestation_devsec_run_action(void *priv, struct ctlv_op_ctx *ctx,
-				       const struct ctlv_cursor *request, struct ctlv_cursor *reply)
-{
-	struct attestation_event_batch events = {};
-	struct attestation *att = priv;
-	u64 device, evidence;
-	int ret;
-
-	device = ctlv_attestation_devsec_run_device_generation_get(request);
-	evidence = ctlv_attestation_devsec_run_evidence_generation_get(request);
-	if (device > UINT_MAX || evidence > UINT_MAX)
-		return -ESTALE;
-	if (!att->verdict_recorded || att->accepted_device_generation != device ||
-	    att->accepted_evidence_generation != evidence)
-		return -ESTALE;
-	ret = attestation_evidence_validate(att, device, evidence);
-	if (ret)
-		return ret;
-	ret = attestation_preflight(att, true, false, false);
-	if (ret)
-		return ret;
-	ret = attestation_events_prepare(att, &events, true, true, false);
-	if (ret)
-		return ret;
-	ret = att->provider->devsec_run(att);
-	if (ret)
-		goto out;
-	attestation_device_changed(att);
-	attestation_device_event_emit(&events, att->device_generation);
-	attestation_evidence_event_emit(&events, att);
-	if (!ctlv_attestation_device_generation_put(reply, att->device_generation))
-		ret = -EMSGSIZE;
-out:
-	attestation_events_abort(&events);
-	return ret;
-}
diff --git a/drivers/attestation/generated/ctlv_attestation_gen.c b/drivers/attestation/generated/ctlv_attestation_gen.c
index 5882dd2c93a7..4afd0433e26f 100644
--- a/drivers/attestation/generated/ctlv_attestation_gen.c
+++ b/drivers/attestation/generated/ctlv_attestation_gen.c
@@ -23,21 +23,18 @@ static const struct ctlv_node_schema device_get_request_node_schemas[] = {
 static const struct ctlv_node_schema device_get_reply_node_schemas[] = {
 	CTLV_NODE_SCHEMA_ROOT(1, 3, 3),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 1, 0, U32_MAX),
-	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_DEVICE_CAPS, 1, 4, 3, 0),
+	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_DEVICE_CAPS, 1, 4, 2, 0),
 	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_DEVICE_INFO, 1, 0, 0, 0),
-	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_CAP_EVIDENCE, 1, 7, 5, 4),
-	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_CAP_MR, 1, 12, 1, 1),
-	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_CAP_DEVSEC, 1, 13, 1, 1),
+	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_CAP_EVIDENCE, 1, 6, 5, 4),
+	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_CAP_MR, 1, 11, 1, 1),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_CAP_MIN_CHALLENGE_LENGTH, 1, 0, U64_MAX),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_CAP_MAX_CHALLENGE_LENGTH, 1, 0, U64_MAX),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_CAP_MAX_TOTAL_OUTPUT_LENGTH, 1, 0, U64_MAX),
-	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_CAP_OBJECT, 16, 14, 6, 4),
+	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_CAP_OBJECT, 16, 12, 6, 4),
 	CTLV_NODE_SCHEMA_FLAGS(CTLV_ATTESTATION_ATTR_CAP_EVIDENCE_FLAGS, 1,
 			       CTLV_ATTESTATION_EVIDENCE_CAPS_MASK),
 	CTLV_NODE_SCHEMA_FLAGS(CTLV_ATTESTATION_ATTR_CAP_MR_FLAGS, 1,
 			       CTLV_ATTESTATION_MR_DEVICE_CAPS_MASK),
-	CTLV_NODE_SCHEMA_FLAGS(CTLV_ATTESTATION_ATTR_CAP_DEVSEC_FLAGS, 1,
-			       CTLV_ATTESTATION_DEVSEC_CAPS_MASK),
 	CTLV_NODE_SCHEMA_ENUM(CTLV_ATTESTATION_ATTR_CAP_BLOB_CLASS, 1, 9),
 	CTLV_NODE_SCHEMA_STRING(CTLV_ATTESTATION_ATTR_CAP_FORMAT, 1, 1, 128),
 	CTLV_NODE_SCHEMA_ENUM(CTLV_ATTESTATION_ATTR_CAP_REPRESENTATION, 2, 2),
@@ -218,17 +215,6 @@ static const struct ctlv_node_schema devsec_state_get_reply_node_schemas[] = {
 	CTLV_NODE_SCHEMA_ENUM(CTLV_ATTESTATION_ATTR_DEVSEC_DEVICE_TRUST, 1, 5),
 };
 
-static const struct ctlv_node_schema devsec_run_request_node_schemas[] = {
-	CTLV_NODE_SCHEMA_ROOT(1, 2, 2),
-	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 1, 0, U32_MAX),
-	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_EVIDENCE_GENERATION, 1, 0, U32_MAX),
-};
-
-static const struct ctlv_node_schema devsec_run_reply_node_schemas[] = {
-	CTLV_NODE_SCHEMA_ROOT(1, 1, 1),
-	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 1, 0, U32_MAX),
-};
-
 static const struct ctlv_node_schema evidence_state_changed_reply_node_schemas[] = {
 	CTLV_NODE_SCHEMA_ROOT(1, 3, 1),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 1, 0, U32_MAX),
@@ -273,7 +259,7 @@ static const struct ctlv_node_schema mr_log_get_reply_node_schemas[] = {
 /* The ops this family declares, in declaration order. */
 const struct ctlv_op_schema ctlv_attestation_op_schemas[] = {
 	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_DEVICE_GET, CTLV_OP_FD_ACCESS_READ,
-			      CTLV_OP_AVAILABILITY_FIXED, 16, 12936,
+			      CTLV_OP_AVAILABILITY_FIXED, 16, 12896,
 			      CTLV_SCHEMA_TREE(device_get_request),
 			      CTLV_SCHEMA_TREE_STUBS(device_get_reply)),
 	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_EVIDENCE_GET, CTLV_OP_FD_ACCESS_READ_WRITE,
@@ -311,10 +297,6 @@ const struct ctlv_op_schema ctlv_attestation_op_schemas[] = {
 			     CTLV_OP_AVAILABILITY_DYNAMIC, 16, 40, 1024,
 			     CTLV_SCHEMA_TREE(devsec_state_get_request),
 			     CTLV_SCHEMA_TREE(devsec_state_get_reply), .optional = true),
-	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_DEVSEC_RUN, CTLV_OP_FD_ACCESS_READ_WRITE,
-			      CTLV_OP_AVAILABILITY_DYNAMIC, 64, 40,
-			      CTLV_SCHEMA_TREE(devsec_run_request),
-			      CTLV_SCHEMA_TREE(devsec_run_reply), .optional = true),
 	CTLV_OP_SCHEMA_EVENT(CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED, 112,
 			     CTLV_SCHEMA_TREE(evidence_state_changed_reply)),
 	CTLV_OP_SCHEMA_QUERY(CTLV_ATTESTATION_OP_MR_LOG_GET, CTLV_OP_FD_ACCESS_READ,
@@ -351,7 +333,6 @@ static const struct ctlv_op ctlv_attestation_ops[] = {
 	CTLV_OP_ACTION(ctlv_attestation_evidence_accept),
 	CTLV_OP_ACTION(ctlv_attestation_evidence_accept_clear),
 	CTLV_OP_QUERY(ctlv_attestation_devsec_state_get),
-	CTLV_OP_ACTION(ctlv_attestation_devsec_run),
 	CTLV_OP_QUERY(ctlv_attestation_mr_log_get),
 };
 
diff --git a/drivers/attestation/generated/ctlv_attestation_gen.h b/drivers/attestation/generated/ctlv_attestation_gen.h
index a1f125690005..12716a7fa7af 100644
--- a/drivers/attestation/generated/ctlv_attestation_gen.h
+++ b/drivers/attestation/generated/ctlv_attestation_gen.h
@@ -43,9 +43,6 @@ int ctlv_attestation_evidence_accept_clear_action(void *priv, struct ctlv_op_ctx
 int ctlv_attestation_devsec_state_get_query(void *priv, struct ctlv_op_ctx *ctx,
 					    const struct ctlv_cursor *request,
 					    struct ctlv_cursor *reply);
-int ctlv_attestation_devsec_run_action(void *priv, struct ctlv_op_ctx *ctx,
-				       const struct ctlv_cursor *request,
-				       struct ctlv_cursor *reply);
 int ctlv_attestation_mr_log_get_query(void *priv, struct ctlv_op_ctx *ctx,
 				      const struct ctlv_cursor *request, struct ctlv_cursor *reply);
 
@@ -81,9 +78,6 @@ CTLV_OP_DISABLE(ctlv_attestation_evidence_accept_clear, CTLV_ATTESTATION_OP_EVID
 CTLV_OP_PROVIDE(ctlv_attestation_devsec_state_get, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET)
 CTLV_OP_ENABLE(ctlv_attestation_devsec_state_get, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET)
 CTLV_OP_DISABLE(ctlv_attestation_devsec_state_get, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET)
-CTLV_OP_PROVIDE(ctlv_attestation_devsec_run, CTLV_ATTESTATION_OP_DEVSEC_RUN)
-CTLV_OP_ENABLE(ctlv_attestation_devsec_run, CTLV_ATTESTATION_OP_DEVSEC_RUN)
-CTLV_OP_DISABLE(ctlv_attestation_devsec_run, CTLV_ATTESTATION_OP_DEVSEC_RUN)
 CTLV_OP_PROVIDE(ctlv_attestation_mr_log_get, CTLV_ATTESTATION_OP_MR_LOG_GET)
 
 /* Reading a validated message. A blob has a resolver instead of a getter. */
@@ -231,29 +225,20 @@ CTLV_IN_REF(ctlv_attestation_evidence_accept_evidence_generation,
 	    1, CTLV_SCHEMA_UINT)
 CTLV_IN_HAS(ctlv_attestation_evidence_accept_evidence_generation)
 CTLV_IN_U64(ctlv_attestation_evidence_accept_evidence_generation)
-CTLV_IN_REF(ctlv_attestation_devsec_run_device_generation, &ctlv_attestation_op_schemas[11].request,
-	    CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 0, 0, CTLV_SCHEMA_UINT)
-CTLV_IN_HAS(ctlv_attestation_devsec_run_device_generation)
-CTLV_IN_U64(ctlv_attestation_devsec_run_device_generation)
-CTLV_IN_REF(ctlv_attestation_devsec_run_evidence_generation,
-	    &ctlv_attestation_op_schemas[11].request, CTLV_ATTESTATION_ATTR_EVIDENCE_GENERATION, 0,
-	    1, CTLV_SCHEMA_UINT)
-CTLV_IN_HAS(ctlv_attestation_devsec_run_evidence_generation)
-CTLV_IN_U64(ctlv_attestation_devsec_run_evidence_generation)
-CTLV_IN_REF(ctlv_attestation_mr_log_get_mr_selection, &ctlv_attestation_op_schemas[13].request,
+CTLV_IN_REF(ctlv_attestation_mr_log_get_mr_selection, &ctlv_attestation_op_schemas[12].request,
 	    CTLV_ATTESTATION_ATTR_MR_SELECTION, 0, 0, CTLV_SCHEMA_NEST)
 #define ctlv_attestation_mr_log_get_mr_selection_for_each(_attr, _nest) \
 	CTLV_IN_FOR_EACH(ctlv_attestation_mr_log_get_mr_selection, _attr, _nest)
 CTLV_IN_REF(ctlv_attestation_mr_log_get_mr_start_generation,
-	    &ctlv_attestation_op_schemas[13].request, CTLV_ATTESTATION_ATTR_MR_START_GENERATION, 0,
+	    &ctlv_attestation_op_schemas[12].request, CTLV_ATTESTATION_ATTR_MR_START_GENERATION, 0,
 	    1, CTLV_SCHEMA_UINT)
 CTLV_IN_HAS(ctlv_attestation_mr_log_get_mr_start_generation)
 CTLV_IN_U64(ctlv_attestation_mr_log_get_mr_start_generation)
-CTLV_IN_REF(ctlv_attestation_mr_log_get_mr_end_generation, &ctlv_attestation_op_schemas[13].request,
+CTLV_IN_REF(ctlv_attestation_mr_log_get_mr_end_generation, &ctlv_attestation_op_schemas[12].request,
 	    CTLV_ATTESTATION_ATTR_MR_END_GENERATION, 0, 2, CTLV_SCHEMA_UINT)
 CTLV_IN_HAS(ctlv_attestation_mr_log_get_mr_end_generation)
 CTLV_IN_U64(ctlv_attestation_mr_log_get_mr_end_generation)
-CTLV_IN_REF(ctlv_attestation_mr_log_get_selection_mr_id, &ctlv_attestation_op_schemas[13].request,
+CTLV_IN_REF(ctlv_attestation_mr_log_get_selection_mr_id, &ctlv_attestation_op_schemas[12].request,
 	    CTLV_ATTESTATION_ATTR_MR_ID, 1, 0, CTLV_SCHEMA_UINT)
 CTLV_IN_HAS(ctlv_attestation_mr_log_get_selection_mr_id)
 CTLV_IN_U64(ctlv_attestation_mr_log_get_selection_mr_id)
@@ -282,9 +267,6 @@ CTLV_PUT_U64(ctlv_attestation_cap_max_length, CTLV_ATTESTATION_ATTR_CAP_MAX_LENG
 	     CTLV_SCHEMA_UINT)
 CTLV_PUT_U64(ctlv_attestation_cap_evidence_flags, CTLV_ATTESTATION_ATTR_CAP_EVIDENCE_FLAGS,
 	     CTLV_SCHEMA_FLAGS)
-CTLV_NEST_START(ctlv_attestation_cap_devsec, CTLV_ATTESTATION_ATTR_CAP_DEVSEC, CTLV_SCHEMA_NEST)
-CTLV_PUT_U64(ctlv_attestation_cap_devsec_flags, CTLV_ATTESTATION_ATTR_CAP_DEVSEC_FLAGS,
-	     CTLV_SCHEMA_FLAGS)
 CTLV_PUT_U64(ctlv_attestation_cap_blob_instance, CTLV_ATTESTATION_ATTR_CAP_BLOB_INSTANCE,
 	     CTLV_SCHEMA_UINT)
 CTLV_PUT_U64(ctlv_attestation_cap_hash_algo, CTLV_ATTESTATION_ATTR_CAP_HASH_ALGO, CTLV_SCHEMA_UINT)
diff --git a/drivers/attestation/providers/sim.c b/drivers/attestation/providers/sim.c
index 5cf312285e85..7c35b1611f10 100644
--- a/drivers/attestation/providers/sim.c
+++ b/drivers/attestation/providers/sim.c
@@ -33,7 +33,6 @@ enum attestation_sim_callback {
 	ATTESTATION_SIM_CB_MR_EXTEND,
 	ATTESTATION_SIM_CB_TDISP_STATE_GET,
 	ATTESTATION_SIM_CB_DEVICE_TRUST_GET,
-	ATTESTATION_SIM_CB_DEVSEC_RUN,
 	ATTESTATION_SIM_CB_COUNT,
 };
 
@@ -55,7 +54,6 @@ struct attestation_sim {
 	struct attestation_sim_mr fixed[2];
 	enum attestation_tdisp_state tdisp_state;
 	enum attestation_device_trust_state trust_state;
-	bool pending_verdict;
 	bool notification_pending;
 };
 
@@ -196,7 +194,6 @@ static const char * const attestation_sim_callback_names[] = {
 	[ATTESTATION_SIM_CB_MR_EXTEND] = "mr_extend",
 	[ATTESTATION_SIM_CB_TDISP_STATE_GET] = "tdisp_state_get",
 	[ATTESTATION_SIM_CB_DEVICE_TRUST_GET] = "device_trust_get",
-	[ATTESTATION_SIM_CB_DEVSEC_RUN] = "devsec_run",
 };
 
 static enum attestation_sim_callback
@@ -585,16 +582,16 @@ static int attestation_sim_evidence_accept(struct attestation *att)
 	guard(mutex)(&sim->hardware_lock);
 	if (sim->notification_pending)
 		return -EAGAIN;
-	sim->pending_verdict = true;
+	if (sim->tdisp_state != ATTESTATION_TDISP_STATE_LOCKED &&
+	    sim->tdisp_state != ATTESTATION_TDISP_STATE_RUN)
+		return -EACCES;
+	sim->tdisp_state = ATTESTATION_TDISP_STATE_RUN;
 	return 0;
 }
 
 static void attestation_sim_evidence_accept_clear(struct attestation *att)
 {
-	struct attestation_sim *sim = attestation_sim_from_att(att);
-
-	guard(mutex)(&sim->hardware_lock);
-	sim->pending_verdict = false;
+	/* The common core owns the verdict; clearing it does not leave RUN. */
 }
 
 static int
@@ -633,26 +630,6 @@ attestation_sim_device_trust_get(
 	return 0;
 }
 
-static int attestation_sim_devsec_run(struct attestation *att)
-{
-	struct attestation_sim *sim = attestation_sim_from_att(att);
-	int ret;
-
-	ret = attestation_sim_callback_enter(
-		sim, ATTESTATION_SIM_CB_DEVSEC_RUN);
-	if (ret)
-		return ret;
-	guard(mutex)(&sim->hardware_lock);
-	if (sim->notification_pending)
-		return -EAGAIN;
-	if (sim->tdisp_state != ATTESTATION_TDISP_STATE_LOCKED ||
-	    !sim->pending_verdict)
-		return -EACCES;
-	sim->tdisp_state = ATTESTATION_TDISP_STATE_RUN;
-	sim->pending_verdict = false;
-	return 0;
-}
-
 static const struct attestation_provider attestation_sim_provider = {
 	.evidence_get = attestation_sim_evidence_get,
 	.evidence_accept = attestation_sim_evidence_accept,
@@ -661,7 +638,6 @@ static const struct attestation_provider attestation_sim_provider = {
 	.mr_extend = attestation_sim_mr_extend,
 	.tdisp_state_get = attestation_sim_tdisp_state_get,
 	.device_trust_get = attestation_sim_device_trust_get,
-	.devsec_run = attestation_sim_devsec_run,
 	.evidence_caps = &attestation_sim_evidence_caps,
 	.mr_caps = &attestation_sim_mr_caps,
 	.mrs = attestation_sim_mrs,
@@ -937,7 +913,6 @@ static ssize_t attestation_sim_state_write(struct file *file,
 		fixture->sim->notification_pending = true;
 		fixture->sim->tdisp_state = tdisp;
 		fixture->sim->trust_state = trust;
-		fixture->sim->pending_verdict = false;
 	}
 	att = ctlv_priv_parent(fixture->sim);
 	attestation_state_changed(att);
diff --git a/drivers/attestation/tests/core_kunit.c b/drivers/attestation/tests/core_kunit.c
index 7e4710370cfe..08cb5eabee1b 100644
--- a/drivers/attestation/tests/core_kunit.c
+++ b/drivers/attestation/tests/core_kunit.c
@@ -231,7 +231,7 @@ static void attestation_test_instance_features(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, fixture->att->features, features);
 	/* A child cannot enable a callback that its provider did not supply. */
 	KUNIT_EXPECT_EQ(test, attestation_features_set(fixture->att,
-			features | ATTESTATION_FEATURE_DEVSEC_RUN), -EINVAL);
+			features | ATTESTATION_FEATURE_TDISP_STATE), -EINVAL);
 	KUNIT_EXPECT_EQ(test, fixture->att->features, features);
 	KUNIT_ASSERT_EQ(test, attestation_features_set(fixture->att, 0), 0);
 	KUNIT_EXPECT_EQ(test, fixture->att->features, 0U);
@@ -250,36 +250,21 @@ static int attestation_test_tdisp_state(struct attestation *att,
 	return 0;
 }
 
-static int attestation_test_run(struct attestation *att)
-{
-	return 0;
-}
-
 static void attestation_test_secure_features(struct kunit *test)
 {
 	struct attestation_test *fixture = test->priv;
 	unsigned int evidence = ATTESTATION_FEATURE_EVIDENCE |
 				ATTESTATION_FEATURE_EVIDENCE_ACCEPT;
 	unsigned int state = ATTESTATION_FEATURE_TDISP_STATE;
-	unsigned int run = ATTESTATION_FEATURE_DEVSEC_RUN;
 
 	attestation_test_provider_init(fixture);
 	fixture->provider.tdisp_state_get = attestation_test_tdisp_state;
-	fixture->provider.devsec_run = attestation_test_run;
 	KUNIT_ASSERT_EQ(test, attestation_test_create_provider(
 				test, fixture, "attestation-secure-features"), 0);
-	KUNIT_EXPECT_EQ(test, fixture->att->features, evidence | state | run);
-	KUNIT_EXPECT_EQ(test, attestation_features_set(fixture->att, run), -EINVAL);
-	KUNIT_EXPECT_EQ(test, attestation_features_set(fixture->att,
-			evidence | run), -EINVAL);
-	KUNIT_EXPECT_EQ(test, attestation_features_set(fixture->att,
-			state | run), -EINVAL);
+	KUNIT_EXPECT_EQ(test, fixture->att->features, evidence | state);
 	/* Unlocked, locked, RUN, and host verdict-only capability sets. */
 	KUNIT_ASSERT_EQ(test, attestation_features_set(fixture->att, state), 0);
 	KUNIT_EXPECT_EQ(test, fixture->att->features, state);
-	KUNIT_ASSERT_EQ(test, attestation_features_set(fixture->att,
-			evidence | state | run), 0);
-	KUNIT_EXPECT_EQ(test, fixture->att->features, evidence | state | run);
 	KUNIT_ASSERT_EQ(test, attestation_features_set(fixture->att,
 			evidence | state), 0);
 	KUNIT_EXPECT_EQ(test, fixture->att->features, evidence | state);
diff --git a/include/linux/attestation.h b/include/linux/attestation.h
index 364ab5029277..9813361ddd29 100644
--- a/include/linux/attestation.h
+++ b/include/linux/attestation.h
@@ -20,7 +20,6 @@ enum attestation_feature {
 	ATTESTATION_FEATURE_EVIDENCE_ACCEPT = 1U << 1,
 	ATTESTATION_FEATURE_TDISP_STATE = 1U << 2,
 	ATTESTATION_FEATURE_DEVICE_TRUST = 1U << 3,
-	ATTESTATION_FEATURE_DEVSEC_RUN = 1U << 4,
 };
 
 enum attestation_evidence_representation {
@@ -236,13 +235,12 @@ struct attestation_evidence_result {
  * @evidence_object_supported: optionally restrict advertised and requested
  *	evidence objects per instance; called under the instance lock
  * @evidence_get: produce requested evidence
- * @evidence_accept: record acceptance of the current evidence
+ * @evidence_accept: accept current evidence and enable secure operation
  * @evidence_accept_clear: clear a recorded acceptance
  * @mr_read: read one MR
  * @mr_extend: extend one MR
  * @tdisp_state_get: get the TDISP state
  * @device_trust_get: get the provider trust state
- * @devsec_run: enter the provider's secure state
  * @evidence_caps: evidence capabilities, or NULL
  * @mr_caps: MR capabilities, or NULL
  * @mrs: fixed MR properties
@@ -266,7 +264,6 @@ struct attestation_provider {
 			       enum attestation_tdisp_state *state);
 	int (*device_trust_get)(struct attestation *att,
 				enum attestation_device_trust_state *trust);
-	int (*devsec_run)(struct attestation *att);
 	const struct attestation_evidence_caps *evidence_caps;
 	const struct attestation_mr_caps *mr_caps;
 	const struct attestation_mr_props *mrs;
diff --git a/include/uapi/linux/ctlv_attestation_gen.h b/include/uapi/linux/ctlv_attestation_gen.h
index 2921bea50151..627c6f5f9377 100644
--- a/include/uapi/linux/ctlv_attestation_gen.h
+++ b/include/uapi/linux/ctlv_attestation_gen.h
@@ -100,14 +100,6 @@ enum _ctlv_attestation_mr_state_flags_bit {
 	_BITULL(CTLV_ATTESTATION_MR_STATE_FLAGS_IMA_ACTIVE_BIT)
 #define CTLV_ATTESTATION_MR_STATE_FLAGS_MASK CTLV_ATTESTATION_MR_STATE_FLAGS_IMA_ACTIVE
 
-/* flags devsec-caps */
-enum _ctlv_attestation_devsec_caps_bit {
-	CTLV_ATTESTATION_DEVSEC_CAPS_RUN_BIT,
-};
-
-#define CTLV_ATTESTATION_DEVSEC_CAPS_RUN _BITULL(CTLV_ATTESTATION_DEVSEC_CAPS_RUN_BIT)
-#define CTLV_ATTESTATION_DEVSEC_CAPS_MASK CTLV_ATTESTATION_DEVSEC_CAPS_RUN
-
 /* flags evidence-caps */
 enum _ctlv_attestation_evidence_caps_bit {
 	CTLV_ATTESTATION_EVIDENCE_CAPS_EVIDENCE_ACCEPT_BIT,
@@ -174,8 +166,6 @@ enum _ctlv_attestation_attr_cap {
 	_CTLV_ATTESTATION_ATTR_CAP_REPRESENTATION,
 	_CTLV_ATTESTATION_ATTR_CAP_MAX_LENGTH,
 	_CTLV_ATTESTATION_ATTR_CAP_EVIDENCE_FLAGS,
-	_CTLV_ATTESTATION_ATTR_CAP_DEVSEC,
-	_CTLV_ATTESTATION_ATTR_CAP_DEVSEC_FLAGS,
 	_CTLV_ATTESTATION_ATTR_CAP_BLOB_INSTANCE,
 	_CTLV_ATTESTATION_ATTR_CAP_HASH_ALGO,
 };
@@ -195,8 +185,6 @@ enum _ctlv_attestation_attr_cap {
 #define CTLV_ATTESTATION_ATTR_CAP_REPRESENTATION _CTLV_ATTESTATION_ATTR_CAP(REPRESENTATION)
 #define CTLV_ATTESTATION_ATTR_CAP_MAX_LENGTH _CTLV_ATTESTATION_ATTR_CAP(MAX_LENGTH)
 #define CTLV_ATTESTATION_ATTR_CAP_EVIDENCE_FLAGS _CTLV_ATTESTATION_ATTR_CAP(EVIDENCE_FLAGS)
-#define CTLV_ATTESTATION_ATTR_CAP_DEVSEC _CTLV_ATTESTATION_ATTR_CAP(DEVSEC)
-#define CTLV_ATTESTATION_ATTR_CAP_DEVSEC_FLAGS _CTLV_ATTESTATION_ATTR_CAP(DEVSEC_FLAGS)
 #define CTLV_ATTESTATION_ATTR_CAP_BLOB_INSTANCE _CTLV_ATTESTATION_ATTR_CAP(BLOB_INSTANCE)
 #define CTLV_ATTESTATION_ATTR_CAP_HASH_ALGO _CTLV_ATTESTATION_ATTR_CAP(HASH_ALGO)
 
@@ -368,10 +356,8 @@ enum _ctlv_attestation_set {
 	CTLV_ATTESTATION_SET_DEVICE_STATE_EVENT,
 	CTLV_ATTESTATION_SET_MR_LOG_EVENT,
 	CTLV_ATTESTATION_SET_MR_LOG_IMA,
-	CTLV_ATTESTATION_SET_DEVSEC_CAP,
 	CTLV_ATTESTATION_SET_EVIDENCE_GENERATION_REQUEST,
 	CTLV_ATTESTATION_SET_DEVSEC_STATE_REPLY,
-	CTLV_ATTESTATION_SET_DEVSEC_RUN_REPLY,
 	CTLV_ATTESTATION_SET_EVIDENCE_STATE_EVENT,
 	CTLV_ATTESTATION_SET_EVIDENCE_OPTIONS,
 	CTLV_ATTESTATION_SET_MR_OPTIONS,
@@ -395,7 +381,6 @@ enum _ctlv_attestation_op {
 	_CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT,
 	_CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR,
 	_CTLV_ATTESTATION_OP_DEVSEC_STATE_GET,
-	_CTLV_ATTESTATION_OP_DEVSEC_RUN,
 	_CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED,
 	_CTLV_ATTESTATION_OP_MR_LOG_GET,
 };
@@ -412,7 +397,6 @@ enum _ctlv_attestation_op {
 #define CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT _CTLV_ATTESTATION_OP(EVIDENCE_ACCEPT)
 #define CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR _CTLV_ATTESTATION_OP(EVIDENCE_ACCEPT_CLEAR)
 #define CTLV_ATTESTATION_OP_DEVSEC_STATE_GET _CTLV_ATTESTATION_OP(DEVSEC_STATE_GET)
-#define CTLV_ATTESTATION_OP_DEVSEC_RUN _CTLV_ATTESTATION_OP(DEVSEC_RUN)
 #define CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED _CTLV_ATTESTATION_OP(EVIDENCE_STATE_CHANGED)
 #define CTLV_ATTESTATION_OP_MR_LOG_GET _CTLV_ATTESTATION_OP(MR_LOG_GET)
 
diff --git a/tools/testing/selftests/attestation/attestation_test.c b/tools/testing/selftests/attestation/attestation_test.c
index 5f1caaac859e..bd831b0ef14f 100644
--- a/tools/testing/selftests/attestation/attestation_test.c
+++ b/tools/testing/selftests/attestation/attestation_test.c
@@ -154,7 +154,7 @@ FIXTURE_TEARDOWN(attestation)
 
 TEST_F(attestation, discovery_and_caps)
 {
-	const struct ctlv_attr *caps, *evidence, *mr, *devsec, *info;
+	const struct ctlv_attr *caps, *evidence, *mr, *info;
 	struct ctlv_reply *reply;
 	const char *name = NULL;
 	__u64 generation = 1;
@@ -175,10 +175,8 @@ TEST_F(attestation, discovery_and_caps)
 	ASSERT_NE(NULL, caps);
 	evidence = ctlv_attestation_cap_evidence_get(caps);
 	mr = ctlv_attestation_cap_mr_get(caps);
-	devsec = ctlv_attestation_cap_devsec_get(caps);
 	ASSERT_NE(NULL, evidence);
 	ASSERT_NE(NULL, mr);
-	ASSERT_NE(NULL, devsec);
 	EXPECT_TRUE(ctlv_attestation_cap_evidence_flags_get(evidence, &flags,
 							    &unknown));
 	EXPECT_EQ(0, unknown);
@@ -526,6 +524,7 @@ TEST_F(attestation, evidence_verdict_and_run)
 		unsigned char data[EVIDENCE_MAX];
 	} output;
 	struct ctlv_reply *reply;
+	const struct ctlv_attr *root;
 	struct ctlv_msg *request;
 	const __u64 subscriptions[] = {
 		CTLV_ATTESTATION_OP_DEVICE_STATE_CHANGED,
@@ -557,6 +556,13 @@ TEST_F(attestation, evidence_verdict_and_run)
 	expect_event(_metadata, self->dev,
 		     CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED);
 
+	ASSERT_EQ(0, ctlv_attestation_devsec_state_get(self->dev, NULL, &reply, &self->error));
+	root = ctlv_reply_root(reply);
+	EXPECT_TRUE(ctlv_attestation_devsec_tdisp_state_get(root, &state, &known));
+	EXPECT_TRUE(known);
+	EXPECT_EQ(CTLV_ATTESTATION_TDISP_STATE_RUN, state);
+	ctlv_reply_free(reply);
+
 	ASSERT_EQ(0,
 		  ctlv_attestation_evidence_accept_clear_request_new(&request));
 	ASSERT_EQ(0, ctlv_attestation_evidence_accept_clear(
@@ -569,22 +575,6 @@ TEST_F(attestation, evidence_verdict_and_run)
 	expect_event(_metadata, self->dev,
 		     CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED);
 
-	ASSERT_EQ(0, ctlv_attestation_devsec_run_request_new(&request));
-	ctlv_attestation_device_generation_put(request, device_generation);
-	ctlv_attestation_evidence_generation_put(
-		request, evidence_generation);
-	ASSERT_EQ(0, ctlv_attestation_devsec_run(
-			  self->dev, request, &reply, &self->error));
-	ctlv_msg_free(request);
-	EXPECT_TRUE(ctlv_attestation_device_generation_get(
-		ctlv_reply_root(reply), &device_generation));
-	EXPECT_EQ(2, device_generation);
-	ctlv_reply_free(reply);
-	expect_event(_metadata, self->dev,
-		     CTLV_ATTESTATION_OP_DEVICE_STATE_CHANGED);
-	expect_event(_metadata, self->dev,
-		     CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED);
-
 	ASSERT_EQ(0, ctlv_attestation_devsec_state_get(
 			  self->dev, NULL, &reply, &self->error));
 	EXPECT_TRUE(ctlv_attestation_devsec_tdisp_state_get(
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c b/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c
index bccac87ea4fc..a1223c4fd45e 100644
--- a/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c
+++ b/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c
@@ -133,17 +133,6 @@ static const struct ctlv_node_schema device_get_reply_caps_mr_attr_node_schemas[
 	},
 };
 
-/* cap.devsec, attrs */
-static const struct ctlv_node_schema device_get_reply_caps_devsec_attr_node_schemas[] = {
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_CAP_DEVSEC_FLAGS,
-		.kind = CTLV_SCHEMA_FLAGS,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_FLAGS_MASK,
-		.flags_mask = 1,
-	},
-};
-
 /* device.caps, optional attrs */
 static const struct ctlv_node_schema device_get_reply_caps_optional_attr_node_schemas[] = {
 	{
@@ -162,13 +151,6 @@ static const struct ctlv_node_schema device_get_reply_caps_optional_attr_node_sc
 		.attrs = device_get_reply_caps_mr_attr_node_schemas,
 		.attrs_count = 1,
 	},
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_CAP_DEVSEC,
-		.kind = CTLV_SCHEMA_NEST,
-		.max_occurrences = 1,
-		.attrs = device_get_reply_caps_devsec_attr_node_schemas,
-		.attrs_count = 1,
-	},
 };
 
 /* device.info, attrs */
@@ -198,7 +180,7 @@ static const struct ctlv_node_schema device_get_reply_attr_node_schemas[] = {
 		.kind = CTLV_SCHEMA_NEST,
 		.max_occurrences = 1,
 		.optional_attrs = device_get_reply_caps_optional_attr_node_schemas,
-		.optional_attrs_count = 3,
+		.optional_attrs_count = 2,
 	},
 	{
 		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_INFO,
@@ -1119,54 +1101,6 @@ static const struct ctlv_node_schema devsec_state_get_reply_node_schema = {
 	.optional_attrs_count = 4,
 };
 
-/* root, attrs */
-static const struct ctlv_node_schema devsec_run_request_attr_node_schemas[] = {
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
-		.kind = CTLV_SCHEMA_UINT,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
-		.min = 0,
-		.max = 0xffffffffULL,
-	},
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_GENERATION,
-		.kind = CTLV_SCHEMA_UINT,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
-		.min = 0,
-		.max = 0xffffffffULL,
-	},
-};
-
-static const struct ctlv_node_schema devsec_run_request_node_schema = {
-	.attr_id = CTLV_ATTR_ROOT,
-	.kind = CTLV_SCHEMA_NEST,
-	.max_occurrences = 1,
-	.attrs = devsec_run_request_attr_node_schemas,
-	.attrs_count = 2,
-};
-
-/* root, attrs */
-static const struct ctlv_node_schema devsec_run_reply_attr_node_schemas[] = {
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
-		.kind = CTLV_SCHEMA_UINT,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
-		.min = 0,
-		.max = 0xffffffffULL,
-	},
-};
-
-static const struct ctlv_node_schema devsec_run_reply_node_schema = {
-	.attr_id = CTLV_ATTR_ROOT,
-	.kind = CTLV_SCHEMA_NEST,
-	.max_occurrences = 1,
-	.attrs = devsec_run_reply_attr_node_schemas,
-	.attrs_count = 1,
-};
-
 /* root, attrs */
 static const struct ctlv_node_schema evidence_state_changed_reply_attr_node_schemas[] = {
 	{
@@ -1475,7 +1409,7 @@ static const struct ctlv_op_spec ctlv_attestation_sim_op_specs[] = {
 		.fd_access = CTLV_OP_FD_ACCESS_READ,
 		.availability = CTLV_OP_AVAILABILITY_FIXED,
 		.request_max_len = 16,
-		.action_reply_max_len = 13016,
+		.action_reply_max_len = 12976,
 		.request = &device_get_request_node_schema,
 		.reply = &device_get_reply_node_schema,
 	},
@@ -1578,17 +1512,6 @@ static const struct ctlv_op_spec ctlv_attestation_sim_op_specs[] = {
 		.request = &devsec_state_get_request_node_schema,
 		.reply = &devsec_state_get_reply_node_schema,
 	},
-	{
-		.op_id = CTLV_ATTESTATION_OP_DEVSEC_RUN,
-		.type = CTLV_OP_TYPE_ACTION,
-		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
-		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
-		.optional = true,
-		.request_max_len = 64,
-		.action_reply_max_len = 40,
-		.request = &devsec_run_request_node_schema,
-		.reply = &devsec_run_reply_node_schema,
-	},
 	{
 		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED,
 		.type = CTLV_OP_TYPE_EVENT,
@@ -1615,5 +1538,5 @@ const struct ctlv_family_spec ctlv_attestation_sim_spec = {
 	.family_id = 5,
 	.parent = &ctlv_attestation_spec,
 	.ops = ctlv_attestation_sim_op_specs,
-	.ops_count = 14,
+	.ops_count = 13,
 };
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c b/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c
index 3395c0ffc3e5..2b6d4537466f 100644
--- a/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c
+++ b/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c
@@ -133,17 +133,6 @@ static const struct ctlv_node_schema device_get_reply_caps_mr_attr_node_schemas[
 	},
 };
 
-/* cap.devsec, attrs */
-static const struct ctlv_node_schema device_get_reply_caps_devsec_attr_node_schemas[] = {
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_CAP_DEVSEC_FLAGS,
-		.kind = CTLV_SCHEMA_FLAGS,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_FLAGS_MASK,
-		.flags_mask = 1,
-	},
-};
-
 /* device.caps, optional attrs */
 static const struct ctlv_node_schema device_get_reply_caps_optional_attr_node_schemas[] = {
 	{
@@ -162,13 +151,6 @@ static const struct ctlv_node_schema device_get_reply_caps_optional_attr_node_sc
 		.attrs = device_get_reply_caps_mr_attr_node_schemas,
 		.attrs_count = 1,
 	},
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_CAP_DEVSEC,
-		.kind = CTLV_SCHEMA_NEST,
-		.max_occurrences = 1,
-		.attrs = device_get_reply_caps_devsec_attr_node_schemas,
-		.attrs_count = 1,
-	},
 };
 
 /* device.info, attrs */
@@ -214,7 +196,7 @@ static const struct ctlv_node_schema device_get_reply_attr_node_schemas[] = {
 		.kind = CTLV_SCHEMA_NEST,
 		.max_occurrences = 1,
 		.optional_attrs = device_get_reply_caps_optional_attr_node_schemas,
-		.optional_attrs_count = 3,
+		.optional_attrs_count = 2,
 	},
 	{
 		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_INFO,
@@ -1135,54 +1117,6 @@ static const struct ctlv_node_schema devsec_state_get_reply_node_schema = {
 	.optional_attrs_count = 4,
 };
 
-/* root, attrs */
-static const struct ctlv_node_schema devsec_run_request_attr_node_schemas[] = {
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
-		.kind = CTLV_SCHEMA_UINT,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
-		.min = 0,
-		.max = 0xffffffffULL,
-	},
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_GENERATION,
-		.kind = CTLV_SCHEMA_UINT,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
-		.min = 0,
-		.max = 0xffffffffULL,
-	},
-};
-
-static const struct ctlv_node_schema devsec_run_request_node_schema = {
-	.attr_id = CTLV_ATTR_ROOT,
-	.kind = CTLV_SCHEMA_NEST,
-	.max_occurrences = 1,
-	.attrs = devsec_run_request_attr_node_schemas,
-	.attrs_count = 2,
-};
-
-/* root, attrs */
-static const struct ctlv_node_schema devsec_run_reply_attr_node_schemas[] = {
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
-		.kind = CTLV_SCHEMA_UINT,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
-		.min = 0,
-		.max = 0xffffffffULL,
-	},
-};
-
-static const struct ctlv_node_schema devsec_run_reply_node_schema = {
-	.attr_id = CTLV_ATTR_ROOT,
-	.kind = CTLV_SCHEMA_NEST,
-	.max_occurrences = 1,
-	.attrs = devsec_run_reply_attr_node_schemas,
-	.attrs_count = 1,
-};
-
 /* root, attrs */
 static const struct ctlv_node_schema evidence_state_changed_reply_attr_node_schemas[] = {
 	{
@@ -1491,7 +1425,7 @@ static const struct ctlv_op_spec ctlv_attestation_tdx_op_specs[] = {
 		.fd_access = CTLV_OP_FD_ACCESS_READ,
 		.availability = CTLV_OP_AVAILABILITY_FIXED,
 		.request_max_len = 16,
-		.action_reply_max_len = 13128,
+		.action_reply_max_len = 13088,
 		.request = &device_get_request_node_schema,
 		.reply = &device_get_reply_node_schema,
 	},
@@ -1594,17 +1528,6 @@ static const struct ctlv_op_spec ctlv_attestation_tdx_op_specs[] = {
 		.request = &devsec_state_get_request_node_schema,
 		.reply = &devsec_state_get_reply_node_schema,
 	},
-	{
-		.op_id = CTLV_ATTESTATION_OP_DEVSEC_RUN,
-		.type = CTLV_OP_TYPE_ACTION,
-		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
-		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
-		.optional = true,
-		.request_max_len = 64,
-		.action_reply_max_len = 40,
-		.request = &devsec_run_request_node_schema,
-		.reply = &devsec_run_reply_node_schema,
-	},
 	{
 		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED,
 		.type = CTLV_OP_TYPE_EVENT,
@@ -1631,5 +1554,5 @@ const struct ctlv_family_spec ctlv_attestation_tdx_spec = {
 	.family_id = 2,
 	.parent = &ctlv_attestation_spec,
 	.ops = ctlv_attestation_tdx_op_specs,
-	.ops_count = 14,
+	.ops_count = 13,
 };
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c b/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c
index 6a363b29f80b..17756bf1afea 100644
--- a/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c
+++ b/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c
@@ -133,17 +133,6 @@ static const struct ctlv_node_schema device_get_reply_caps_mr_attr_node_schemas[
 	},
 };
 
-/* cap.devsec, attrs */
-static const struct ctlv_node_schema device_get_reply_caps_devsec_attr_node_schemas[] = {
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_CAP_DEVSEC_FLAGS,
-		.kind = CTLV_SCHEMA_FLAGS,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_FLAGS_MASK,
-		.flags_mask = 1,
-	},
-};
-
 /* device.caps, optional attrs */
 static const struct ctlv_node_schema device_get_reply_caps_optional_attr_node_schemas[] = {
 	{
@@ -162,13 +151,6 @@ static const struct ctlv_node_schema device_get_reply_caps_optional_attr_node_sc
 		.attrs = device_get_reply_caps_mr_attr_node_schemas,
 		.attrs_count = 1,
 	},
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_CAP_DEVSEC,
-		.kind = CTLV_SCHEMA_NEST,
-		.max_occurrences = 1,
-		.attrs = device_get_reply_caps_devsec_attr_node_schemas,
-		.attrs_count = 1,
-	},
 };
 
 /* root, attrs */
@@ -186,7 +168,7 @@ static const struct ctlv_node_schema device_get_reply_attr_node_schemas[] = {
 		.kind = CTLV_SCHEMA_NEST,
 		.max_occurrences = 1,
 		.optional_attrs = device_get_reply_caps_optional_attr_node_schemas,
-		.optional_attrs_count = 3,
+		.optional_attrs_count = 2,
 	},
 	{
 		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_INFO,
@@ -1105,54 +1087,6 @@ static const struct ctlv_node_schema devsec_state_get_reply_node_schema = {
 	.optional_attrs_count = 4,
 };
 
-/* root, attrs */
-static const struct ctlv_node_schema devsec_run_request_attr_node_schemas[] = {
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
-		.kind = CTLV_SCHEMA_UINT,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
-		.min = 0,
-		.max = 0xffffffffULL,
-	},
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_GENERATION,
-		.kind = CTLV_SCHEMA_UINT,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
-		.min = 0,
-		.max = 0xffffffffULL,
-	},
-};
-
-static const struct ctlv_node_schema devsec_run_request_node_schema = {
-	.attr_id = CTLV_ATTR_ROOT,
-	.kind = CTLV_SCHEMA_NEST,
-	.max_occurrences = 1,
-	.attrs = devsec_run_request_attr_node_schemas,
-	.attrs_count = 2,
-};
-
-/* root, attrs */
-static const struct ctlv_node_schema devsec_run_reply_attr_node_schemas[] = {
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
-		.kind = CTLV_SCHEMA_UINT,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
-		.min = 0,
-		.max = 0xffffffffULL,
-	},
-};
-
-static const struct ctlv_node_schema devsec_run_reply_node_schema = {
-	.attr_id = CTLV_ATTR_ROOT,
-	.kind = CTLV_SCHEMA_NEST,
-	.max_occurrences = 1,
-	.attrs = devsec_run_reply_attr_node_schemas,
-	.attrs_count = 1,
-};
-
 /* root, attrs */
 static const struct ctlv_node_schema evidence_state_changed_reply_attr_node_schemas[] = {
 	{
@@ -1461,7 +1395,7 @@ static const struct ctlv_op_spec ctlv_attestation_op_specs[] = {
 		.fd_access = CTLV_OP_FD_ACCESS_READ,
 		.availability = CTLV_OP_AVAILABILITY_FIXED,
 		.request_max_len = 16,
-		.action_reply_max_len = 12936,
+		.action_reply_max_len = 12896,
 		.request = &device_get_request_node_schema,
 		.reply = &device_get_reply_node_schema,
 	},
@@ -1564,17 +1498,6 @@ static const struct ctlv_op_spec ctlv_attestation_op_specs[] = {
 		.request = &devsec_state_get_request_node_schema,
 		.reply = &devsec_state_get_reply_node_schema,
 	},
-	{
-		.op_id = CTLV_ATTESTATION_OP_DEVSEC_RUN,
-		.type = CTLV_OP_TYPE_ACTION,
-		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
-		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
-		.optional = true,
-		.request_max_len = 64,
-		.action_reply_max_len = 40,
-		.request = &devsec_run_request_node_schema,
-		.reply = &devsec_run_reply_node_schema,
-	},
 	{
 		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED,
 		.type = CTLV_OP_TYPE_EVENT,
@@ -1601,5 +1524,5 @@ const struct ctlv_family_spec ctlv_attestation_spec = {
 	.family_id = 1,
 	.parent = &ctlv_spec,
 	.ops = ctlv_attestation_op_specs,
-	.ops_count = 14,
+	.ops_count = 13,
 };
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_user_gen.h b/tools/testing/selftests/attestation/ctlv_attestation_user_gen.h
index f4b7a4d3c0ce..67b8820b3efc 100644
--- a/tools/testing/selftests/attestation/ctlv_attestation_user_gen.h
+++ b/tools/testing/selftests/attestation/ctlv_attestation_user_gen.h
@@ -18,7 +18,7 @@ extern const struct ctlv_family_spec ctlv_attestation_spec;
 /* Executing an op. A query is reassembled into the one reply it fills. */
 
 #define CTLV_ATTESTATION_OP_DEVICE_GET_REQUEST_MAX_LEN 16
-#define CTLV_ATTESTATION_OP_DEVICE_GET_ACTION_REPLY_MAX_LEN 12936
+#define CTLV_ATTESTATION_OP_DEVICE_GET_ACTION_REPLY_MAX_LEN 12896
 
 static inline int ctlv_attestation_device_get_request_new(struct ctlv_msg **request)
 {
@@ -165,22 +165,6 @@ static inline int ctlv_attestation_devsec_state_get(struct ctlv_dev *dev, struct
 	return ctlv_query(dev, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET, request, reply, error);
 }
 
-#define CTLV_ATTESTATION_OP_DEVSEC_RUN_REQUEST_MAX_LEN 64
-#define CTLV_ATTESTATION_OP_DEVSEC_RUN_ACTION_REPLY_MAX_LEN 40
-
-static inline int ctlv_attestation_devsec_run_request_new(struct ctlv_msg **request)
-{
-	const __u32 len = CTLV_ATTESTATION_OP_DEVSEC_RUN_REQUEST_MAX_LEN;
-
-	return ctlv_msg_new(len, request);
-}
-
-static inline int ctlv_attestation_devsec_run(struct ctlv_dev *dev, struct ctlv_msg *request,
-					      struct ctlv_reply **reply, struct ctlv_error *error)
-{
-	return ctlv_action(dev, CTLV_ATTESTATION_OP_DEVSEC_RUN, request, reply, error);
-}
-
 #define CTLV_ATTESTATION_OP_MR_LOG_GET_REQUEST_MAX_LEN 10304
 #define CTLV_ATTESTATION_OP_MR_LOG_GET_QUERY_REPLY_MIN_LEN 65600
 #define CTLV_ATTESTATION_OP_MR_LOG_GET_QUERY_REPLY_SUGGESTED_LEN 131072
@@ -276,16 +260,6 @@ static inline int ctlv_attestation_cap_evidence_flags_put(struct ctlv_msg *msg,
 	return ctlv_put_u64(msg, CTLV_ATTESTATION_ATTR_CAP_EVIDENCE_FLAGS, value);
 }
 
-static inline int ctlv_attestation_cap_devsec_start(struct ctlv_msg *msg)
-{
-	return ctlv_nest_start(msg, CTLV_ATTESTATION_ATTR_CAP_DEVSEC);
-}
-
-static inline int ctlv_attestation_cap_devsec_flags_put(struct ctlv_msg *msg, __u64 value)
-{
-	return ctlv_put_u64(msg, CTLV_ATTESTATION_ATTR_CAP_DEVSEC_FLAGS, value);
-}
-
 static inline int ctlv_attestation_cap_blob_instance_put(struct ctlv_msg *msg, __u64 value)
 {
 	return ctlv_put_u64(msg, CTLV_ATTESTATION_ATTR_CAP_BLOB_INSTANCE, value);
@@ -721,30 +695,6 @@ static inline bool ctlv_attestation_cap_evidence_flags_get(const struct ctlv_att
 	return attr && !ctlv_get_flags(attr, CTLV_ATTESTATION_EVIDENCE_CAPS_MASK, value, unknown);
 }
 
-static inline bool ctlv_attestation_cap_devsec_present(const struct ctlv_attr *nest)
-{
-	return ctlv_attr_find(nest, CTLV_ATTESTATION_ATTR_CAP_DEVSEC);
-}
-
-static inline const struct ctlv_attr *ctlv_attestation_cap_devsec_get(const struct ctlv_attr *nest)
-{
-	return ctlv_attr_find(nest, CTLV_ATTESTATION_ATTR_CAP_DEVSEC);
-}
-
-static inline bool ctlv_attestation_cap_devsec_flags_present(const struct ctlv_attr *nest)
-{
-	return ctlv_attr_find(nest, CTLV_ATTESTATION_ATTR_CAP_DEVSEC_FLAGS);
-}
-
-static inline bool ctlv_attestation_cap_devsec_flags_get(const struct ctlv_attr *nest, __u64 *value,
-							 __u64 *unknown)
-{
-	const struct ctlv_attr *attr;
-
-	attr = ctlv_attr_find(nest, CTLV_ATTESTATION_ATTR_CAP_DEVSEC_FLAGS);
-	return attr && !ctlv_get_flags(attr, CTLV_ATTESTATION_DEVSEC_CAPS_MASK, value, unknown);
-}
-
 static inline bool ctlv_attestation_cap_blob_instance_present(const struct ctlv_attr *nest)
 {
 	return ctlv_attr_find(nest, CTLV_ATTESTATION_ATTR_CAP_BLOB_INSTANCE);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [RFC PATCH v1 04/11] attestation: Bind object selection to accepted evidence snapshots
  2026-10-28 15:26 [RFC PATCH v1 00/11] PCI/TSM: Add CTLV attestation Aneesh Kumar K.V (Arm)
                   ` (2 preceding siblings ...)
  2026-10-28 15:26 ` [RFC PATCH v1 03/11] attestation: Enter secure operation when evidence is accepted Aneesh Kumar K.V (Arm)
@ 2026-10-28 15:26 ` Aneesh Kumar K.V (Arm)
  2026-10-28 15:26 ` [RFC PATCH v1 05/11] attestation: Make the evidence-get challenge attribute optional Aneesh Kumar K.V (Arm)
                   ` (6 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-10-28 15:26 UTC (permalink / raw)
  To: linux-coco
  Cc: Aneesh Kumar K.V (Arm), Jiri Pirko, Kevin Tian,
	Alexey Kardashevskiy, Xu Yilun, Catalin Marinas, Suzuki K Poulose,
	Steven Price, Kiryl Shutsemau, Rick Edgecombe, Dave Hansen,
	Jonathan Cameron, Jason Gunthorpe, Bjorn Helgaas, Ashish Kalra,
	Tom Lendacky, John Allen, Will Deacon, Mark Rutland, patches

Follow-up PCI/TSM patches expose multiple populated SPDM certificate slots
and let userspace choose which chain to authorize. Device and evidence
generations identify an evidence snapshot, but cannot express which
certificate chain userspace selected from that snapshot.

Allow EVIDENCE_ACCEPT to select a logical evidence object by class, format,
and optional instance. For example, a request can name device generation
D, evidence generation E, and CERT_CHAIN with format "spdm-cert-chain"
and instance 3. Pass the resolved object to the provider so PCI/TSM can
call pci_tsm_evidence_accept(pdev, 3), allowing the backend to authenticate
the selected chain and establish streams using it.

Current object availability is not enough to authorize that choice. The
selected object must also have been retrieved in the referenced evidence
snapshot. Track the retrieved object set in evidence generation ranges
and reject selectors absent from that snapshot:

  Retrieved in snapshot E  Selected object  Membership check
  -----------------------  ---------------  ----------------
  Certificate slot 0       Slot 0           Pass
  Certificate slot 0       Slot 3           -ESTALE
  Certificate slots 0, 3   Slot 3           Pass

Reject slot 3 in the second case even if the instance currently supports
it. Per-instance object filtering checks availability; snapshot membership
checks that the selected object was included in the referenced retrieval.
Passing the membership check does not bypass the existing generation or
provider checks.

Include the selected object in verdict idempotence checks. Otherwise,
accepting slot 0 and then selecting slot 3 with the same device and
evidence generations would incorrectly return success without calling the
provider again. Let the provider handle the changed selection instead.

Advertise object selection through evidence capabilities and enable it in
the simulator for regression coverage.

Cc: Jiri Pirko <jiri@resnulli.us>
Cc: Kiryl Shutsemau <kas@kernel.org>
Cc: Rick Edgecombe <rick.p.edgecombe@intel.com>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Jonathan Cameron <jic23@kernel.org>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 Documentation/ctlv/abi/ctlv-attestation.yaml  | 12 ++-
 .../ctlv/specs/ctlv-attestation.yaml          | 10 +++
 drivers/attestation/core/core.c               |  1 +
 drivers/attestation/core/ctlv.c               | 83 +++++++++++++++++--
 drivers/attestation/core/internal.h           |  2 +
 .../generated/ctlv_attestation_gen.c          |  8 +-
 .../generated/ctlv_attestation_gen.h          | 22 +++++
 drivers/attestation/providers/sim.c           |  5 +-
 drivers/attestation/tests/core_kunit.c        |  4 +-
 include/linux/attestation.h                   |  8 +-
 include/uapi/linux/ctlv_attestation_gen.h     |  9 +-
 .../ctlv_attestation_sim_user_gen.c           | 52 +++++++++++-
 .../ctlv_attestation_tdx_user_gen.c           | 52 +++++++++++-
 .../attestation/ctlv_attestation_user_gen.c   | 52 +++++++++++-
 .../attestation/ctlv_attestation_user_gen.h   | 18 +++-
 15 files changed, 314 insertions(+), 24 deletions(-)

diff --git a/Documentation/ctlv/abi/ctlv-attestation.yaml b/Documentation/ctlv/abi/ctlv-attestation.yaml
index 0220c2e131d4..018417fe3682 100644
--- a/Documentation/ctlv/abi/ctlv-attestation.yaml
+++ b/Documentation/ctlv/abi/ctlv-attestation.yaml
@@ -22,7 +22,8 @@ flags:
   mr-device-caps: {read: 0, extend: 1, dynamic-topology: 2}
   mr-caps: {read: 0, extend: 1, removable: 2}
   mr-state-flags: {ima-active: 0}
-  evidence-caps: {evidence-accept: 0, mr-selection: 1, privlevel: 2, service: 3}
+  evidence-caps: {evidence-accept: 0, mr-selection: 1, privlevel: 2, service: 3,
+                  object-selection: 4}
 attr-groups:
   device:
     id: 1
@@ -69,6 +70,7 @@ attr-groups:
       service-guid: {id: 15, type: binary, length: 16}
       service-manifest-version: {id: 16, type: uint, max: 4294967295}
       options: {id: 17, type: nested}
+      accept-object: {id: 18, type: nested}
   mr:
     id: 4
     attrs:
@@ -145,6 +147,7 @@ attr-sets:
   mr-log-selection: {id: 30}
   mr-log-request: {id: 31}
   mr-log-reply: {id: 32}
+  evidence-object-selector: {id: 33}
 ops:
   device-get:
     abi: {id: 1, type: action, fd-access: read}
@@ -240,7 +243,12 @@ ops:
               - record.item: {attrs: [record.format, record.data]}
   evidence-accept:
     abi: {id: 9, type: action, availability: dynamic, optional: true}
-    request: {attrs: [device.generation, evidence.generation]}
+    request:
+      attrs: [device.generation, evidence.generation]
+      optional-attrs:
+        - evidence.accept-object:
+            attrs: [evidence.blob-class, evidence.format]
+            optional-attrs: [evidence.blob-instance]
   evidence-accept-clear:
     abi: {id: 10, type: action, availability: dynamic, optional: true}
   devsec-state-get:
diff --git a/Documentation/ctlv/specs/ctlv-attestation.yaml b/Documentation/ctlv/specs/ctlv-attestation.yaml
index 8494f716f9e9..98d6753d878f 100644
--- a/Documentation/ctlv/specs/ctlv-attestation.yaml
+++ b/Documentation/ctlv/specs/ctlv-attestation.yaml
@@ -68,6 +68,7 @@ flags:
       - mr-selection
       - privlevel
       - service
+      - object-selection
 
 attr-groups:
   - name: device
@@ -175,6 +176,8 @@ attr-groups:
         max: 4294967295
       - name: options
         attrs: evidence-options
+      - name: accept-object
+        attrs: evidence-object-selector
 
   - name: mr
     attrs:
@@ -424,6 +427,7 @@ attr-sets:
     attrs:
       - device.generation
       - evidence.generation
+    optional-attrs: evidence.accept-object
 
   - name: devsec-state-reply
     attrs: device.generation
@@ -470,6 +474,12 @@ attr-sets:
       - mr.end-generation
       - log.entry
 
+  - name: evidence-object-selector
+    attrs:
+      - evidence.blob-class
+      - evidence.format
+    optional-attrs: evidence.blob-instance
+
 ops:
   - name: device-get
     type: action
diff --git a/drivers/attestation/core/core.c b/drivers/attestation/core/core.c
index a1de19dcab7d..914f29693aee 100644
--- a/drivers/attestation/core/core.c
+++ b/drivers/attestation/core/core.c
@@ -248,6 +248,7 @@ void attestation_evidence_accept_clear_locked(struct attestation *att)
 		return;
 	att->provider->evidence_accept_clear(att);
 	att->verdict_recorded = false;
+	att->accepted_object = NULL;
 }
 
 static void attestation_evidence_ranges_clear(struct attestation *att, bool dependent_only)
diff --git a/drivers/attestation/core/ctlv.c b/drivers/attestation/core/ctlv.c
index 7f755b7aaf51..f0238c4c4cf2 100644
--- a/drivers/attestation/core/ctlv.c
+++ b/drivers/attestation/core/ctlv.c
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0-only
 #include <crypto/hash_info.h>
+#include <linux/bitmap.h>
 #include <linux/cleanup.h>
 #include <linux/err.h>
 #include <linux/limits.h>
@@ -85,6 +86,8 @@ static int attestation_device_caps_put(struct attestation *att, struct ctlv_curs
 			flags |= CTLV_ATTESTATION_EVIDENCE_CAPS_PRIVLEVEL;
 		if (caps->supports_service_attestation)
 			flags |= CTLV_ATTESTATION_EVIDENCE_CAPS_SERVICE;
+		if (caps->supports_object_accept)
+			flags |= CTLV_ATTESTATION_EVIDENCE_CAPS_OBJECT_SELECTION;
 		ok &= ctlv_attestation_cap_evidence_flags_put(&group, flags);
 		for (index = 0; index < caps->objects_count; index++) {
 			const struct attestation_evidence_supported_object *cap =
@@ -311,7 +314,7 @@ static bool attestation_ranges_required(const struct attestation *att)
 	const struct attestation_evidence_caps *caps = att->provider->evidence_caps;
 	size_t index;
 
-	if (caps->supports_mr_selection)
+	if (caps->supports_mr_selection || caps->supports_object_accept)
 		return true;
 	for (index = 0; index < caps->objects_count; index++)
 		if (caps->objects[index].binds_mrs)
@@ -320,7 +323,7 @@ static bool attestation_ranges_required(const struct attestation *att)
 }
 
 static struct attestation_evidence_range *attestation_evidence_range_tail(struct attestation *att,
-									  bool mr_dependent)
+		bool mr_dependent, const unsigned long *objects)
 {
 	struct attestation_evidence_range *range;
 	unsigned long index;
@@ -331,6 +334,9 @@ static struct attestation_evidence_range *attestation_evidence_range_tail(struct
 			continue;
 		if (mr_dependent && range->mr_generation != att->log->generation)
 			continue;
+		if (objects && !bitmap_equal(range->objects, objects,
+					     att->provider->evidence_caps->objects_count))
+			continue;
 		return range;
 	}
 	return NULL;
@@ -419,6 +425,8 @@ int ctlv_attestation_evidence_get_action(void *priv, struct ctlv_op_ctx *ctx,
 	struct attestation_event_batch events = {};
 	struct ctlv_cursor options;
 	struct attestation *att = priv;
+	const struct attestation_evidence_caps *caps = att->provider->evidence_caps;
+	unsigned long *object_mask = NULL;
 	bool mr_dependent = false;
 	size_t index;
 	int put_ret;
@@ -431,6 +439,16 @@ int ctlv_attestation_evidence_get_action(void *priv, struct ctlv_op_ctx *ctx,
 						  &options);
 	if (ret)
 		goto out;
+	if (caps->supports_object_accept) {
+		object_mask = bitmap_zalloc(caps->objects_count, GFP_KERNEL);
+		if (!object_mask) {
+			ret = -ENOMEM;
+			goto out;
+		}
+		for (index = 0; index < decoded.objects_count; index++)
+			__set_bit(decoded.objects[index].supported_object - caps->objects,
+				  object_mask);
+	}
 	result.object_lengths =
 		kcalloc(decoded.objects_count, sizeof(*result.object_lengths), GFP_KERNEL);
 	if (!result.object_lengths) {
@@ -445,9 +463,11 @@ int ctlv_attestation_evidence_get_action(void *priv, struct ctlv_op_ctx *ctx,
 		if (ret)
 			goto out;
 		if (attestation_ranges_required(att)) {
-			range = attestation_evidence_range_tail(att, mr_dependent);
+			range = attestation_evidence_range_tail(att, mr_dependent, object_mask);
 			if (!range) {
-				range = kzalloc_obj(*range, GFP_KERNEL);
+				size_t words = object_mask ? BITS_TO_LONGS(caps->objects_count) : 0;
+
+				range = kzalloc(struct_size(range, objects, words), GFP_KERNEL);
 				if (!range) {
 					ret = -ENOMEM;
 					goto out;
@@ -456,6 +476,9 @@ int ctlv_attestation_evidence_get_action(void *priv, struct ctlv_op_ctx *ctx,
 				range->end_generation = range->start_generation;
 				range->mr_dependent = mr_dependent;
 				range->mr_generation = att->log ? att->log->generation : 0;
+				if (object_mask)
+					bitmap_copy(range->objects, object_mask,
+						    caps->objects_count);
 				new_range = range;
 				ret = xa_reserve(&att->evidence_ranges, range->start_generation,
 						 GFP_KERNEL);
@@ -526,12 +549,14 @@ int ctlv_attestation_evidence_get_action(void *priv, struct ctlv_op_ctx *ctx,
 	}
 	attestation_events_abort(&events);
 	kfree(result.object_lengths);
+	bitmap_free(object_mask);
 	attestation_evidence_request_free(objects, decoded.objects_count, selection);
 	return ret;
 }
 
 static int attestation_evidence_validate(struct attestation *att, unsigned int device_generation,
-					 unsigned int evidence_generation)
+		unsigned int evidence_generation,
+		const struct attestation_evidence_supported_object *object)
 {
 	struct attestation_evidence_range *range;
 	unsigned long index;
@@ -550,17 +575,55 @@ static int attestation_evidence_validate(struct attestation *att, unsigned int d
 		if (range->mr_dependent &&
 		    (!att->log || range->mr_generation != att->log->generation))
 			return -ESTALE;
+		if (object && !test_bit(object - att->provider->evidence_caps->objects,
+				       range->objects))
+			return -ESTALE;
 		return 0;
 	}
 	return -ESTALE;
 }
 
+static int attestation_evidence_accept_object_decode(struct attestation *att,
+		const struct ctlv_cursor *request,
+		const struct attestation_evidence_supported_object **object)
+{
+	u64 instance = 0;
+	struct ctlv_cursor selector;
+	enum attestation_evidence_blob_class blob_class;
+	const char *format;
+	bool has_instance;
+
+	if (!ctlv_attestation_evidence_accept_evidence_accept_object_present(request))
+		return 0;
+	if (!att->provider->evidence_caps->supports_object_accept)
+		return -EOPNOTSUPP;
+	ctlv_attestation_evidence_accept_evidence_accept_object_get(&selector, request);
+	has_instance =
+		ctlv_attestation_evidence_accept_accept_object_evidence_blob_instance_present(
+		&selector);
+	if (has_instance)
+		instance =
+			ctlv_attestation_evidence_accept_accept_object_evidence_blob_instance_get(
+			&selector);
+	if (instance > UINT_MAX)
+		return -ERANGE;
+	blob_class =
+		ctlv_attestation_evidence_accept_accept_object_evidence_blob_class_get(
+			&selector);
+	format = ctlv_attestation_evidence_accept_accept_object_evidence_format_get(
+		&selector);
+	*object = attestation_evidence_object_find(att, blob_class, has_instance,
+						  instance, format);
+	return *object ? 0 : -EOPNOTSUPP;
+}
+
 int ctlv_attestation_evidence_accept_action(void *priv, struct ctlv_op_ctx *ctx,
 					    const struct ctlv_cursor *request,
 					    struct ctlv_cursor *reply)
 {
 	struct attestation_event_batch events = {};
 	struct attestation *att = priv;
+	const struct attestation_evidence_supported_object *object = NULL;
 	u64 device, evidence;
 	int ret;
 
@@ -568,21 +631,25 @@ int ctlv_attestation_evidence_accept_action(void *priv, struct ctlv_op_ctx *ctx,
 	evidence = ctlv_attestation_evidence_accept_evidence_generation_get(request);
 	if (device > UINT_MAX || evidence > UINT_MAX)
 		return -ESTALE;
-	ret = attestation_evidence_validate(att, device, evidence);
+	ret = attestation_evidence_accept_object_decode(att, request, &object);
+	if (ret)
+		return ret;
+	ret = attestation_evidence_validate(att, device, evidence, object);
 	if (ret)
 		return ret;
 	if (att->verdict_recorded && att->accepted_device_generation == device &&
-	    att->accepted_evidence_generation == evidence)
+	    att->accepted_evidence_generation == evidence && att->accepted_object == object)
 		return 0;
 	ret = attestation_events_prepare(att, &events, false, true, false);
 	if (ret)
 		return ret;
 	/* Acceptance authorizes this snapshot without retiring its generation binding. */
-	ret = att->provider->evidence_accept(att);
+	ret = att->provider->evidence_accept(att, object);
 	if (!ret) {
 		att->verdict_recorded = true;
 		att->accepted_device_generation = device;
 		att->accepted_evidence_generation = evidence;
+		att->accepted_object = object;
 		attestation_evidence_verdict_changed(att);
 		attestation_evidence_event_emit(&events, att);
 	}
diff --git a/drivers/attestation/core/internal.h b/drivers/attestation/core/internal.h
index 9e531e8483f7..7b444d61d996 100644
--- a/drivers/attestation/core/internal.h
+++ b/drivers/attestation/core/internal.h
@@ -25,6 +25,7 @@ struct attestation_evidence_range {
 	unsigned int end_generation;
 	bool mr_dependent;
 	unsigned int mr_generation;
+	unsigned long objects[];
 };
 
 struct attestation_mr_log_entry {
@@ -82,6 +83,7 @@ struct attestation {
 	bool verdict_recorded;
 	unsigned int accepted_device_generation;
 	unsigned int accepted_evidence_generation;
+	const struct attestation_evidence_supported_object *accepted_object;
 	unsigned int mr_read_generation;
 	unsigned int devsec_version;
 
diff --git a/drivers/attestation/generated/ctlv_attestation_gen.c b/drivers/attestation/generated/ctlv_attestation_gen.c
index 4afd0433e26f..a858a84da135 100644
--- a/drivers/attestation/generated/ctlv_attestation_gen.c
+++ b/drivers/attestation/generated/ctlv_attestation_gen.c
@@ -193,9 +193,13 @@ static const struct ctlv_node_schema mr_log_entry_appended_reply_node_schemas[]
 };
 
 static const struct ctlv_node_schema evidence_accept_request_node_schemas[] = {
-	CTLV_NODE_SCHEMA_ROOT(1, 2, 2),
+	CTLV_NODE_SCHEMA_ROOT(1, 3, 2),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 1, 0, U32_MAX),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_EVIDENCE_GENERATION, 1, 0, U32_MAX),
+	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT, 1, 4, 3, 2),
+	CTLV_NODE_SCHEMA_ENUM(CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_CLASS, 1, 9),
+	CTLV_NODE_SCHEMA_STRING(CTLV_ATTESTATION_ATTR_EVIDENCE_FORMAT, 1, 1, 128),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_INSTANCE, 1, 0, U32_MAX),
 };
 
 static const struct ctlv_node_schema evidence_accept_clear_request_node_schemas[] = {
@@ -286,7 +290,7 @@ const struct ctlv_op_schema ctlv_attestation_op_schemas[] = {
 	CTLV_OP_SCHEMA_EVENT(CTLV_ATTESTATION_OP_MR_LOG_ENTRY_APPENDED, 73416,
 			     CTLV_SCHEMA_TREE(mr_log_entry_appended_reply)),
 	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT, CTLV_OP_FD_ACCESS_READ_WRITE,
-			      CTLV_OP_AVAILABILITY_DYNAMIC, 64, 0,
+			      CTLV_OP_AVAILABILITY_DYNAMIC, 280, 0,
 			      CTLV_SCHEMA_TREE(evidence_accept_request), CTLV_SCHEMA_TREE_NONE(),
 			      .optional = true),
 	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR,
diff --git a/drivers/attestation/generated/ctlv_attestation_gen.h b/drivers/attestation/generated/ctlv_attestation_gen.h
index 12716a7fa7af..95cb6a083633 100644
--- a/drivers/attestation/generated/ctlv_attestation_gen.h
+++ b/drivers/attestation/generated/ctlv_attestation_gen.h
@@ -225,6 +225,26 @@ CTLV_IN_REF(ctlv_attestation_evidence_accept_evidence_generation,
 	    1, CTLV_SCHEMA_UINT)
 CTLV_IN_HAS(ctlv_attestation_evidence_accept_evidence_generation)
 CTLV_IN_U64(ctlv_attestation_evidence_accept_evidence_generation)
+CTLV_IN_REF(ctlv_attestation_evidence_accept_evidence_accept_object,
+	    &ctlv_attestation_op_schemas[8].request, CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT,
+	    0, 2, CTLV_SCHEMA_NEST)
+CTLV_IN_HAS(ctlv_attestation_evidence_accept_evidence_accept_object)
+CTLV_IN_NEST(ctlv_attestation_evidence_accept_evidence_accept_object)
+CTLV_IN_REF(ctlv_attestation_evidence_accept_accept_object_evidence_blob_class,
+	    &ctlv_attestation_op_schemas[8].request, CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_CLASS, 3,
+	    0, CTLV_SCHEMA_ENUM)
+CTLV_IN_HAS(ctlv_attestation_evidence_accept_accept_object_evidence_blob_class)
+CTLV_IN_U64(ctlv_attestation_evidence_accept_accept_object_evidence_blob_class)
+CTLV_IN_REF(ctlv_attestation_evidence_accept_accept_object_evidence_format,
+	    &ctlv_attestation_op_schemas[8].request, CTLV_ATTESTATION_ATTR_EVIDENCE_FORMAT, 3, 1,
+	    CTLV_SCHEMA_STRING)
+CTLV_IN_HAS(ctlv_attestation_evidence_accept_accept_object_evidence_format)
+CTLV_IN_STRING(ctlv_attestation_evidence_accept_accept_object_evidence_format)
+CTLV_IN_REF(ctlv_attestation_evidence_accept_accept_object_evidence_blob_instance,
+	    &ctlv_attestation_op_schemas[8].request, CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_INSTANCE,
+	    3, 2, CTLV_SCHEMA_UINT)
+CTLV_IN_HAS(ctlv_attestation_evidence_accept_accept_object_evidence_blob_instance)
+CTLV_IN_U64(ctlv_attestation_evidence_accept_accept_object_evidence_blob_instance)
 CTLV_IN_REF(ctlv_attestation_mr_log_get_mr_selection, &ctlv_attestation_op_schemas[12].request,
 	    CTLV_ATTESTATION_ATTR_MR_SELECTION, 0, 0, CTLV_SCHEMA_NEST)
 #define ctlv_attestation_mr_log_get_mr_selection_for_each(_attr, _nest) \
@@ -302,6 +322,8 @@ CTLV_PUT_U64(ctlv_attestation_evidence_service_manifest_version,
 	     CTLV_ATTESTATION_ATTR_EVIDENCE_SERVICE_MANIFEST_VERSION, CTLV_SCHEMA_UINT)
 CTLV_NEST_START(ctlv_attestation_evidence_options, CTLV_ATTESTATION_ATTR_EVIDENCE_OPTIONS,
 		CTLV_SCHEMA_NEST)
+CTLV_NEST_START(ctlv_attestation_evidence_accept_object,
+		CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT, CTLV_SCHEMA_NEST)
 CTLV_PUT_U64(ctlv_attestation_mr_id, CTLV_ATTESTATION_ATTR_MR_ID, CTLV_SCHEMA_UINT)
 CTLV_PUT_STRING(ctlv_attestation_mr_name, CTLV_ATTESTATION_ATTR_MR_NAME, CTLV_SCHEMA_STRING)
 CTLV_PUT_U64(ctlv_attestation_mr_hash_algo, CTLV_ATTESTATION_ATTR_MR_HASH_ALGO, CTLV_SCHEMA_UINT)
diff --git a/drivers/attestation/providers/sim.c b/drivers/attestation/providers/sim.c
index 7c35b1611f10..4a541462dc7e 100644
--- a/drivers/attestation/providers/sim.c
+++ b/drivers/attestation/providers/sim.c
@@ -179,6 +179,7 @@ static const struct attestation_evidence_caps attestation_sim_evidence_caps = {
 	.supports_mr_selection = true,
 	.supports_privlevel = true,
 	.supports_service_attestation = true,
+	.supports_object_accept = true,
 	.objects = attestation_sim_evidence_objects,
 	.objects_count = ARRAY_SIZE(attestation_sim_evidence_objects),
 };
@@ -570,7 +571,9 @@ attestation_sim_evidence_get(struct attestation *att,
 	return ret;
 }
 
-static int attestation_sim_evidence_accept(struct attestation *att)
+static int
+attestation_sim_evidence_accept(struct attestation *att,
+		const struct attestation_evidence_supported_object *object)
 {
 	struct attestation_sim *sim = attestation_sim_from_att(att);
 	int ret;
diff --git a/drivers/attestation/tests/core_kunit.c b/drivers/attestation/tests/core_kunit.c
index 08cb5eabee1b..898f5b34d32f 100644
--- a/drivers/attestation/tests/core_kunit.c
+++ b/drivers/attestation/tests/core_kunit.c
@@ -95,7 +95,9 @@ attestation_test_evidence_get(
 	return 0;
 }
 
-static int attestation_test_evidence_accept(struct attestation *att)
+static int
+attestation_test_evidence_accept(struct attestation *att,
+		const struct attestation_evidence_supported_object *object)
 {
 	return 0;
 }
diff --git a/include/linux/attestation.h b/include/linux/attestation.h
index 9813361ddd29..09ebd2af3a1e 100644
--- a/include/linux/attestation.h
+++ b/include/linux/attestation.h
@@ -154,6 +154,7 @@ struct attestation_evidence_supported_object {
  * @supports_mr_selection: requests may select MRs
  * @supports_privlevel: requests may select a privilege level
  * @supports_service_attestation: requests may identify a service
+ * @supports_object_accept: acceptance may select an object from its snapshot
  * @objects: supported evidence objects
  * @objects_count: number of objects
  */
@@ -164,6 +165,7 @@ struct attestation_evidence_caps {
 	bool supports_mr_selection;
 	bool supports_privlevel;
 	bool supports_service_attestation;
+	bool supports_object_accept;
 	const struct attestation_evidence_supported_object *objects;
 	size_t objects_count;
 };
@@ -235,7 +237,8 @@ struct attestation_evidence_result {
  * @evidence_object_supported: optionally restrict advertised and requested
  *	evidence objects per instance; called under the instance lock
  * @evidence_get: produce requested evidence
- * @evidence_accept: accept current evidence and enable secure operation
+ * @evidence_accept: accept evidence and enable secure operation; the optional
+ *		   object selector belongs to the referenced evidence snapshot
  * @evidence_accept_clear: clear a recorded acceptance
  * @mr_read: read one MR
  * @mr_extend: extend one MR
@@ -252,7 +255,8 @@ struct attestation_provider {
 	int (*evidence_get)(struct attestation *att,
 			    const struct attestation_evidence_request *request,
 			    struct attestation_evidence_result *result);
-	int (*evidence_accept)(struct attestation *att);
+	int (*evidence_accept)(struct attestation *att,
+		const struct attestation_evidence_supported_object *object);
 	void (*evidence_accept_clear)(struct attestation *att);
 	int (*mr_read)(struct attestation *att, unsigned int mr_id,
 		       const struct ctlv_cursor *mr_options,
diff --git a/include/uapi/linux/ctlv_attestation_gen.h b/include/uapi/linux/ctlv_attestation_gen.h
index 627c6f5f9377..f993def4fe57 100644
--- a/include/uapi/linux/ctlv_attestation_gen.h
+++ b/include/uapi/linux/ctlv_attestation_gen.h
@@ -106,6 +106,7 @@ enum _ctlv_attestation_evidence_caps_bit {
 	CTLV_ATTESTATION_EVIDENCE_CAPS_MR_SELECTION_BIT,
 	CTLV_ATTESTATION_EVIDENCE_CAPS_PRIVLEVEL_BIT,
 	CTLV_ATTESTATION_EVIDENCE_CAPS_SERVICE_BIT,
+	CTLV_ATTESTATION_EVIDENCE_CAPS_OBJECT_SELECTION_BIT,
 };
 
 #define CTLV_ATTESTATION_EVIDENCE_CAPS_EVIDENCE_ACCEPT \
@@ -115,11 +116,14 @@ enum _ctlv_attestation_evidence_caps_bit {
 #define CTLV_ATTESTATION_EVIDENCE_CAPS_PRIVLEVEL \
 	_BITULL(CTLV_ATTESTATION_EVIDENCE_CAPS_PRIVLEVEL_BIT)
 #define CTLV_ATTESTATION_EVIDENCE_CAPS_SERVICE _BITULL(CTLV_ATTESTATION_EVIDENCE_CAPS_SERVICE_BIT)
+#define CTLV_ATTESTATION_EVIDENCE_CAPS_OBJECT_SELECTION \
+	_BITULL(CTLV_ATTESTATION_EVIDENCE_CAPS_OBJECT_SELECTION_BIT)
 #define CTLV_ATTESTATION_EVIDENCE_CAPS_MASK \
 	(CTLV_ATTESTATION_EVIDENCE_CAPS_EVIDENCE_ACCEPT | \
 	 CTLV_ATTESTATION_EVIDENCE_CAPS_MR_SELECTION | \
 	 CTLV_ATTESTATION_EVIDENCE_CAPS_PRIVLEVEL | \
-	 CTLV_ATTESTATION_EVIDENCE_CAPS_SERVICE)
+	 CTLV_ATTESTATION_EVIDENCE_CAPS_SERVICE | \
+	 CTLV_ATTESTATION_EVIDENCE_CAPS_OBJECT_SELECTION)
 
 /* attr groups */
 enum _ctlv_attestation_group {
@@ -211,6 +215,7 @@ enum _ctlv_attestation_attr_evidence {
 	_CTLV_ATTESTATION_ATTR_EVIDENCE_SERVICE_GUID,
 	_CTLV_ATTESTATION_ATTR_EVIDENCE_SERVICE_MANIFEST_VERSION,
 	_CTLV_ATTESTATION_ATTR_EVIDENCE_OPTIONS,
+	_CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT,
 };
 
 #define CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE _CTLV_ATTESTATION_ATTR_EVIDENCE(CHALLENGE)
@@ -234,6 +239,7 @@ enum _ctlv_attestation_attr_evidence {
 #define CTLV_ATTESTATION_ATTR_EVIDENCE_SERVICE_MANIFEST_VERSION \
 	_CTLV_ATTESTATION_ATTR_EVIDENCE(SERVICE_MANIFEST_VERSION)
 #define CTLV_ATTESTATION_ATTR_EVIDENCE_OPTIONS _CTLV_ATTESTATION_ATTR_EVIDENCE(OPTIONS)
+#define CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT _CTLV_ATTESTATION_ATTR_EVIDENCE(ACCEPT_OBJECT)
 
 /* attrs of group mr */
 #define _CTLV_ATTESTATION_ATTR_MR(_attr) \
@@ -366,6 +372,7 @@ enum _ctlv_attestation_set {
 	CTLV_ATTESTATION_SET_MR_LOG_SELECTION,
 	CTLV_ATTESTATION_SET_MR_LOG_REQUEST,
 	CTLV_ATTESTATION_SET_MR_LOG_REPLY,
+	CTLV_ATTESTATION_SET_EVIDENCE_OBJECT_SELECTOR,
 };
 
 /* ops */
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c b/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c
index a1223c4fd45e..795cf514c761 100644
--- a/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c
+++ b/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c
@@ -118,7 +118,7 @@ static const struct ctlv_node_schema device_get_reply_caps_evidence_optional_att
 		.kind = CTLV_SCHEMA_FLAGS,
 		.max_occurrences = 1,
 		.present = CTLV_NODE_SCHEMA_HAS_FLAGS_MASK,
-		.flags_mask = 15,
+		.flags_mask = 31,
 	},
 };
 
@@ -1003,6 +1003,39 @@ static const struct ctlv_node_schema mr_log_entry_appended_reply_node_schema = {
 	.attrs_count = 1,
 };
 
+/* evidence.accept-object, attrs */
+static const struct ctlv_node_schema evidence_accept_request_accept_object_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_CLASS,
+		.kind = CTLV_SCHEMA_ENUM,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 1,
+		.max = 9,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_FORMAT,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 128,
+	},
+};
+
+/* evidence.accept-object, optional attrs */
+static const struct ctlv_node_schema
+	evidence_accept_request_accept_object_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_INSTANCE,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
 /* root, attrs */
 static const struct ctlv_node_schema evidence_accept_request_attr_node_schemas[] = {
 	{
@@ -1023,12 +1056,27 @@ static const struct ctlv_node_schema evidence_accept_request_attr_node_schemas[]
 	},
 };
 
+/* root, optional attrs */
+static const struct ctlv_node_schema evidence_accept_request_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+		.attrs = evidence_accept_request_accept_object_attr_node_schemas,
+		.attrs_count = 2,
+		.optional_attrs = evidence_accept_request_accept_object_optional_attr_node_schemas,
+		.optional_attrs_count = 1,
+	},
+};
+
 static const struct ctlv_node_schema evidence_accept_request_node_schema = {
 	.attr_id = CTLV_ATTR_ROOT,
 	.kind = CTLV_SCHEMA_NEST,
 	.max_occurrences = 1,
 	.attrs = evidence_accept_request_attr_node_schemas,
 	.attrs_count = 2,
+	.optional_attrs = evidence_accept_request_optional_attr_node_schemas,
+	.optional_attrs_count = 1,
 };
 
 static const struct ctlv_node_schema evidence_accept_clear_request_node_schema = {
@@ -1488,7 +1536,7 @@ static const struct ctlv_op_spec ctlv_attestation_sim_op_specs[] = {
 		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
 		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
-		.request_max_len = 64,
+		.request_max_len = 280,
 		.request = &evidence_accept_request_node_schema,
 	},
 	{
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c b/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c
index 2b6d4537466f..787edd7d8ce4 100644
--- a/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c
+++ b/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c
@@ -118,7 +118,7 @@ static const struct ctlv_node_schema device_get_reply_caps_evidence_optional_att
 		.kind = CTLV_SCHEMA_FLAGS,
 		.max_occurrences = 1,
 		.present = CTLV_NODE_SCHEMA_HAS_FLAGS_MASK,
-		.flags_mask = 15,
+		.flags_mask = 31,
 	},
 };
 
@@ -1019,6 +1019,39 @@ static const struct ctlv_node_schema mr_log_entry_appended_reply_node_schema = {
 	.attrs_count = 1,
 };
 
+/* evidence.accept-object, attrs */
+static const struct ctlv_node_schema evidence_accept_request_accept_object_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_CLASS,
+		.kind = CTLV_SCHEMA_ENUM,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 1,
+		.max = 9,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_FORMAT,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 128,
+	},
+};
+
+/* evidence.accept-object, optional attrs */
+static const struct ctlv_node_schema
+	evidence_accept_request_accept_object_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_INSTANCE,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
 /* root, attrs */
 static const struct ctlv_node_schema evidence_accept_request_attr_node_schemas[] = {
 	{
@@ -1039,12 +1072,27 @@ static const struct ctlv_node_schema evidence_accept_request_attr_node_schemas[]
 	},
 };
 
+/* root, optional attrs */
+static const struct ctlv_node_schema evidence_accept_request_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+		.attrs = evidence_accept_request_accept_object_attr_node_schemas,
+		.attrs_count = 2,
+		.optional_attrs = evidence_accept_request_accept_object_optional_attr_node_schemas,
+		.optional_attrs_count = 1,
+	},
+};
+
 static const struct ctlv_node_schema evidence_accept_request_node_schema = {
 	.attr_id = CTLV_ATTR_ROOT,
 	.kind = CTLV_SCHEMA_NEST,
 	.max_occurrences = 1,
 	.attrs = evidence_accept_request_attr_node_schemas,
 	.attrs_count = 2,
+	.optional_attrs = evidence_accept_request_optional_attr_node_schemas,
+	.optional_attrs_count = 1,
 };
 
 static const struct ctlv_node_schema evidence_accept_clear_request_node_schema = {
@@ -1504,7 +1552,7 @@ static const struct ctlv_op_spec ctlv_attestation_tdx_op_specs[] = {
 		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
 		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
-		.request_max_len = 64,
+		.request_max_len = 280,
 		.request = &evidence_accept_request_node_schema,
 	},
 	{
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c b/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c
index 17756bf1afea..b278511f5776 100644
--- a/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c
+++ b/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c
@@ -118,7 +118,7 @@ static const struct ctlv_node_schema device_get_reply_caps_evidence_optional_att
 		.kind = CTLV_SCHEMA_FLAGS,
 		.max_occurrences = 1,
 		.present = CTLV_NODE_SCHEMA_HAS_FLAGS_MASK,
-		.flags_mask = 15,
+		.flags_mask = 31,
 	},
 };
 
@@ -989,6 +989,39 @@ static const struct ctlv_node_schema mr_log_entry_appended_reply_node_schema = {
 	.attrs_count = 1,
 };
 
+/* evidence.accept-object, attrs */
+static const struct ctlv_node_schema evidence_accept_request_accept_object_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_CLASS,
+		.kind = CTLV_SCHEMA_ENUM,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 1,
+		.max = 9,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_FORMAT,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 128,
+	},
+};
+
+/* evidence.accept-object, optional attrs */
+static const struct ctlv_node_schema
+	evidence_accept_request_accept_object_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_INSTANCE,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
 /* root, attrs */
 static const struct ctlv_node_schema evidence_accept_request_attr_node_schemas[] = {
 	{
@@ -1009,12 +1042,27 @@ static const struct ctlv_node_schema evidence_accept_request_attr_node_schemas[]
 	},
 };
 
+/* root, optional attrs */
+static const struct ctlv_node_schema evidence_accept_request_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+		.attrs = evidence_accept_request_accept_object_attr_node_schemas,
+		.attrs_count = 2,
+		.optional_attrs = evidence_accept_request_accept_object_optional_attr_node_schemas,
+		.optional_attrs_count = 1,
+	},
+};
+
 static const struct ctlv_node_schema evidence_accept_request_node_schema = {
 	.attr_id = CTLV_ATTR_ROOT,
 	.kind = CTLV_SCHEMA_NEST,
 	.max_occurrences = 1,
 	.attrs = evidence_accept_request_attr_node_schemas,
 	.attrs_count = 2,
+	.optional_attrs = evidence_accept_request_optional_attr_node_schemas,
+	.optional_attrs_count = 1,
 };
 
 static const struct ctlv_node_schema evidence_accept_clear_request_node_schema = {
@@ -1474,7 +1522,7 @@ static const struct ctlv_op_spec ctlv_attestation_op_specs[] = {
 		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
 		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
-		.request_max_len = 64,
+		.request_max_len = 280,
 		.request = &evidence_accept_request_node_schema,
 	},
 	{
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_user_gen.h b/tools/testing/selftests/attestation/ctlv_attestation_user_gen.h
index 67b8820b3efc..88d8ac5048d8 100644
--- a/tools/testing/selftests/attestation/ctlv_attestation_user_gen.h
+++ b/tools/testing/selftests/attestation/ctlv_attestation_user_gen.h
@@ -115,7 +115,7 @@ static inline int ctlv_attestation_mr_extend(struct ctlv_dev *dev, struct ctlv_m
 	return ctlv_action(dev, CTLV_ATTESTATION_OP_MR_EXTEND, request, reply, error);
 }
 
-#define CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_REQUEST_MAX_LEN 64
+#define CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_REQUEST_MAX_LEN 280
 
 static inline int ctlv_attestation_evidence_accept_request_new(struct ctlv_msg **request)
 {
@@ -373,6 +373,11 @@ static inline int ctlv_attestation_evidence_options_start(struct ctlv_msg *msg)
 	return ctlv_nest_start(msg, CTLV_ATTESTATION_ATTR_EVIDENCE_OPTIONS);
 }
 
+static inline int ctlv_attestation_evidence_accept_object_start(struct ctlv_msg *msg)
+{
+	return ctlv_nest_start(msg, CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT);
+}
+
 static inline int ctlv_attestation_mr_id_put(struct ctlv_msg *msg, __u64 value)
 {
 	return ctlv_put_u64(msg, CTLV_ATTESTATION_ATTR_MR_ID, value);
@@ -913,6 +918,17 @@ ctlv_attestation_evidence_options_get(const struct ctlv_attr *nest)
 	return ctlv_attr_find(nest, CTLV_ATTESTATION_ATTR_EVIDENCE_OPTIONS);
 }
 
+static inline bool ctlv_attestation_evidence_accept_object_present(const struct ctlv_attr *nest)
+{
+	return ctlv_attr_find(nest, CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT);
+}
+
+static inline const struct ctlv_attr *
+ctlv_attestation_evidence_accept_object_get(const struct ctlv_attr *nest)
+{
+	return ctlv_attr_find(nest, CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT);
+}
+
 static inline bool ctlv_attestation_mr_id_present(const struct ctlv_attr *nest)
 {
 	return ctlv_attr_find(nest, CTLV_ATTESTATION_ATTR_MR_ID);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [RFC PATCH v1 05/11] attestation: Make the evidence-get challenge attribute optional
  2026-10-28 15:26 [RFC PATCH v1 00/11] PCI/TSM: Add CTLV attestation Aneesh Kumar K.V (Arm)
                   ` (3 preceding siblings ...)
  2026-10-28 15:26 ` [RFC PATCH v1 04/11] attestation: Bind object selection to accepted evidence snapshots Aneesh Kumar K.V (Arm)
@ 2026-10-28 15:26 ` Aneesh Kumar K.V (Arm)
  2026-10-28 15:26 ` [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family Aneesh Kumar K.V (Arm)
                   ` (5 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-10-28 15:26 UTC (permalink / raw)
  To: linux-coco
  Cc: Aneesh Kumar K.V (Arm), Jiri Pirko, Kevin Tian,
	Alexey Kardashevskiy, Xu Yilun, Catalin Marinas, Suzuki K Poulose,
	Steven Price, Kiryl Shutsemau, Rick Edgecombe, Dave Hansen,
	Jonathan Cameron, Jason Gunthorpe, Bjorn Helgaas, Ashish Kalra,
	Tom Lendacky, John Allen, Will Deacon, Mark Rutland, patches

The following PCI/TSM integration retrieves cached evidence through
EVIDENCE_GET. A challenge is used when refreshing that evidence, not
when reading the current cache, so requiring evidence.challenge forces
callers to supply an otherwise unused empty attribute.

Make evidence.challenge optional in the common request schema.

Cc: Jiri Pirko <jiri@resnulli.us>
Cc: Kiryl Shutsemau <kas@kernel.org>
Cc: Rick Edgecombe <rick.p.edgecombe@intel.com>
Cc: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Jonathan Cameron <jic23@kernel.org>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 Documentation/ctlv/abi/ctlv-attestation.yaml  |  2 +-
 .../ctlv/specs/ctlv-attestation.yaml          |  2 +-
 Documentation/driver-api/attestation.rst      | 12 ++++++++++
 drivers/attestation/core/ctlv.c               |  7 +++---
 .../generated/ctlv_attestation_gen.c          |  4 ++--
 .../generated/ctlv_attestation_gen.h          | 22 +++++++++----------
 .../selftests/attestation/attestation_test.c  |  4 +---
 .../ctlv_attestation_sim_user_gen.c           | 20 ++++++++---------
 .../ctlv_attestation_tdx_user_gen.c           | 20 ++++++++---------
 .../attestation/ctlv_attestation_user_gen.c   | 20 ++++++++---------
 .../attestation/tdx_attestation_test.c        |  9 ++++++--
 11 files changed, 69 insertions(+), 53 deletions(-)

diff --git a/Documentation/ctlv/abi/ctlv-attestation.yaml b/Documentation/ctlv/abi/ctlv-attestation.yaml
index 018417fe3682..373614f97021 100644
--- a/Documentation/ctlv/abi/ctlv-attestation.yaml
+++ b/Documentation/ctlv/abi/ctlv-attestation.yaml
@@ -172,12 +172,12 @@ ops:
     abi: {id: 2, type: action, availability: dynamic, optional: true}
     request:
       attrs:
-        - evidence.challenge
         - evidence.object:
             attrs: [evidence.blob-class, evidence.format,
                     evidence.representation, evidence.data]
             optional-attrs: [evidence.blob-instance, evidence.hash-algo]
       optional-attrs:
+        - evidence.challenge
         - device.generation
         - evidence.selection: {attrs: [mr.id, evidence.hash-algo]}
         - evidence.privlevel
diff --git a/Documentation/ctlv/specs/ctlv-attestation.yaml b/Documentation/ctlv/specs/ctlv-attestation.yaml
index 98d6753d878f..e2371813ec25 100644
--- a/Documentation/ctlv/specs/ctlv-attestation.yaml
+++ b/Documentation/ctlv/specs/ctlv-attestation.yaml
@@ -320,9 +320,9 @@ attr-sets:
 
   - name: evidence-request
     attrs:
-      - evidence.challenge
       - evidence.object
     optional-attrs:
+      - evidence.challenge
       - device.generation
       - evidence.selection
       - evidence.privlevel
diff --git a/Documentation/driver-api/attestation.rst b/Documentation/driver-api/attestation.rst
index 06af8df23a46..d23b7c65d35b 100644
--- a/Documentation/driver-api/attestation.rst
+++ b/Documentation/driver-api/attestation.rst
@@ -29,6 +29,18 @@ dynamic topology retains handles returned by ``attestation_mr_create()`` and
 passes each handle exactly once to ``attestation_mr_destroy()``.  Property
 storage remains valid for the handle's lifetime.
 
+Evidence retrieval
+==================
+
+``EVIDENCE_GET`` requests may omit ``evidence.challenge``. The core treats
+an omitted challenge as zero bytes and still enforces the provider's minimum
+and maximum challenge lengths. Providers that require a challenge, such as
+TDX, retain that requirement.
+
+Cached-evidence providers such as PCI/TSM can fetch the current evidence
+without a challenge. A challenge for fresh PCI/TSM evidence belongs to the
+separate evidence-refresh operation.
+
 Per-instance features
 =====================
 
diff --git a/drivers/attestation/core/ctlv.c b/drivers/attestation/core/ctlv.c
index f0238c4c4cf2..50aa3cb84ddd 100644
--- a/drivers/attestation/core/ctlv.c
+++ b/drivers/attestation/core/ctlv.c
@@ -350,13 +350,14 @@ static int attestation_evidence_request_decode(struct attestation *att, struct c
 					       struct ctlv_cursor *options)
 {
 	const struct attestation_evidence_caps *caps = att->provider->evidence_caps;
-	u32 challenge_length, guid_length;
+	u32 challenge_length = 0, guid_length;
 	const void *guid;
 	bool service;
 	int ret;
 
-	decoded->challenge =
-		ctlv_attestation_evidence_get_evidence_challenge_get(request, &challenge_length);
+	if (ctlv_attestation_evidence_get_evidence_challenge_present(request))
+		decoded->challenge = ctlv_attestation_evidence_get_evidence_challenge_get(
+			request, &challenge_length);
 	decoded->challenge_length = challenge_length;
 	if (challenge_length < caps->min_challenge_length ||
 	    challenge_length > caps->max_challenge_length)
diff --git a/drivers/attestation/generated/ctlv_attestation_gen.c b/drivers/attestation/generated/ctlv_attestation_gen.c
index a858a84da135..0bd4bc76b718 100644
--- a/drivers/attestation/generated/ctlv_attestation_gen.c
+++ b/drivers/attestation/generated/ctlv_attestation_gen.c
@@ -49,9 +49,9 @@ static const struct ctlv_stub_schema device_get_reply_stub_schemas[] = {
 };
 
 static const struct ctlv_node_schema evidence_get_request_node_schemas[] = {
-	CTLV_NODE_SCHEMA_ROOT(1, 9, 2),
-	CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE, 1, 0, 4096),
+	CTLV_NODE_SCHEMA_ROOT(1, 9, 1),
 	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_EVIDENCE_OBJECT, 16, 10, 6, 4),
+	CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE, 1, 0, 4096),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 1, 0, U32_MAX),
 	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_EVIDENCE_SELECTION, 256, 16, 2, 2),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_EVIDENCE_PRIVLEVEL, 1, 0, U32_MAX),
diff --git a/drivers/attestation/generated/ctlv_attestation_gen.h b/drivers/attestation/generated/ctlv_attestation_gen.h
index 95cb6a083633..bd55158d8819 100644
--- a/drivers/attestation/generated/ctlv_attestation_gen.h
+++ b/drivers/attestation/generated/ctlv_attestation_gen.h
@@ -81,15 +81,15 @@ CTLV_OP_DISABLE(ctlv_attestation_devsec_state_get, CTLV_ATTESTATION_OP_DEVSEC_ST
 CTLV_OP_PROVIDE(ctlv_attestation_mr_log_get, CTLV_ATTESTATION_OP_MR_LOG_GET)
 
 /* Reading a validated message. A blob has a resolver instead of a getter. */
+CTLV_IN_REF(ctlv_attestation_evidence_get_evidence_object, &ctlv_attestation_op_schemas[1].request,
+	    CTLV_ATTESTATION_ATTR_EVIDENCE_OBJECT, 0, 0, CTLV_SCHEMA_NEST)
+#define ctlv_attestation_evidence_get_evidence_object_for_each(_attr, _nest) \
+	CTLV_IN_FOR_EACH(ctlv_attestation_evidence_get_evidence_object, _attr, _nest)
 CTLV_IN_REF(ctlv_attestation_evidence_get_evidence_challenge,
-	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE, 0, 0,
+	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE, 0, 1,
 	    CTLV_SCHEMA_BINARY)
 CTLV_IN_HAS(ctlv_attestation_evidence_get_evidence_challenge)
 CTLV_IN_BINARY(ctlv_attestation_evidence_get_evidence_challenge)
-CTLV_IN_REF(ctlv_attestation_evidence_get_evidence_object, &ctlv_attestation_op_schemas[1].request,
-	    CTLV_ATTESTATION_ATTR_EVIDENCE_OBJECT, 0, 1, CTLV_SCHEMA_NEST)
-#define ctlv_attestation_evidence_get_evidence_object_for_each(_attr, _nest) \
-	CTLV_IN_FOR_EACH(ctlv_attestation_evidence_get_evidence_object, _attr, _nest)
 CTLV_IN_REF(ctlv_attestation_evidence_get_device_generation,
 	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 0, 2,
 	    CTLV_SCHEMA_UINT)
@@ -125,32 +125,32 @@ CTLV_IN_REF(ctlv_attestation_evidence_get_evidence_options, &ctlv_attestation_op
 CTLV_IN_HAS(ctlv_attestation_evidence_get_evidence_options)
 CTLV_IN_NEST(ctlv_attestation_evidence_get_evidence_options)
 CTLV_IN_REF(ctlv_attestation_evidence_get_object_evidence_blob_class,
-	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_CLASS, 2,
+	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_CLASS, 1,
 	    0, CTLV_SCHEMA_ENUM)
 CTLV_IN_HAS(ctlv_attestation_evidence_get_object_evidence_blob_class)
 CTLV_IN_U64(ctlv_attestation_evidence_get_object_evidence_blob_class)
 CTLV_IN_REF(ctlv_attestation_evidence_get_object_evidence_format,
-	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_FORMAT, 2, 1,
+	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_FORMAT, 1, 1,
 	    CTLV_SCHEMA_STRING)
 CTLV_IN_HAS(ctlv_attestation_evidence_get_object_evidence_format)
 CTLV_IN_STRING(ctlv_attestation_evidence_get_object_evidence_format)
 CTLV_IN_REF(ctlv_attestation_evidence_get_object_evidence_representation,
 	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_REPRESENTATION,
-	    2, 2, CTLV_SCHEMA_ENUM)
+	    1, 2, CTLV_SCHEMA_ENUM)
 CTLV_IN_HAS(ctlv_attestation_evidence_get_object_evidence_representation)
 CTLV_IN_U64(ctlv_attestation_evidence_get_object_evidence_representation)
 CTLV_IN_REF(ctlv_attestation_evidence_get_object_evidence_data,
-	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_DATA, 2, 3,
+	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_DATA, 1, 3,
 	    CTLV_SCHEMA_BLOB)
 CTLV_IN_HAS(ctlv_attestation_evidence_get_object_evidence_data)
 CTLV_IN_BLOB(ctlv_attestation_evidence_get_object_evidence_data)
 CTLV_IN_REF(ctlv_attestation_evidence_get_object_evidence_blob_instance,
 	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_INSTANCE,
-	    2, 4, CTLV_SCHEMA_UINT)
+	    1, 4, CTLV_SCHEMA_UINT)
 CTLV_IN_HAS(ctlv_attestation_evidence_get_object_evidence_blob_instance)
 CTLV_IN_U64(ctlv_attestation_evidence_get_object_evidence_blob_instance)
 CTLV_IN_REF(ctlv_attestation_evidence_get_object_evidence_hash_algo,
-	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_HASH_ALGO, 2, 5,
+	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_HASH_ALGO, 1, 5,
 	    CTLV_SCHEMA_UINT)
 CTLV_IN_HAS(ctlv_attestation_evidence_get_object_evidence_hash_algo)
 CTLV_IN_U64(ctlv_attestation_evidence_get_object_evidence_hash_algo)
diff --git a/tools/testing/selftests/attestation/attestation_test.c b/tools/testing/selftests/attestation/attestation_test.c
index bd831b0ef14f..40be7fd0c492 100644
--- a/tools/testing/selftests/attestation/attestation_test.c
+++ b/tools/testing/selftests/attestation/attestation_test.c
@@ -374,15 +374,13 @@ TEST_F(attestation, extension_record_log_and_event)
 static int evidence_get(struct ctlv_dev *dev, void *output, size_t output_length,
 			struct ctlv_reply **reply, struct ctlv_error *error)
 {
-	static const unsigned char challenge[] = "freshness";
 	struct ctlv_msg *request;
 	int ret;
 
 	ret = ctlv_attestation_evidence_get_request_new(&request);
 	if (ret)
 		return ret;
-	ctlv_attestation_evidence_challenge_put(
-		request, challenge, sizeof(challenge) - 1);
+	/* Providers with a zero minimum accept an omitted challenge. */
 	ctlv_attestation_evidence_object_start(request);
 	ctlv_attestation_evidence_blob_class_put(
 		request, CTLV_ATTESTATION_EVIDENCE_BLOB_CLASS_PRIMARY);
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c b/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c
index 795cf514c761..89b7db4df55b 100644
--- a/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c
+++ b/tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c
@@ -279,14 +279,6 @@ static const struct ctlv_node_schema evidence_get_request_selection_attr_node_sc
 
 /* root, attrs */
 static const struct ctlv_node_schema evidence_get_request_attr_node_schemas[] = {
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE,
-		.kind = CTLV_SCHEMA_BINARY,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
-		.min_length = 0,
-		.max_length = 4096,
-	},
 	{
 		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_OBJECT,
 		.kind = CTLV_SCHEMA_NEST,
@@ -300,6 +292,14 @@ static const struct ctlv_node_schema evidence_get_request_attr_node_schemas[] =
 
 /* root, optional attrs */
 static const struct ctlv_node_schema evidence_get_request_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 0,
+		.max_length = 4096,
+	},
 	{
 		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
 		.kind = CTLV_SCHEMA_UINT,
@@ -359,9 +359,9 @@ static const struct ctlv_node_schema evidence_get_request_node_schema = {
 	.kind = CTLV_SCHEMA_NEST,
 	.max_occurrences = 1,
 	.attrs = evidence_get_request_attr_node_schemas,
-	.attrs_count = 2,
+	.attrs_count = 1,
 	.optional_attrs = evidence_get_request_optional_attr_node_schemas,
-	.optional_attrs_count = 7,
+	.optional_attrs_count = 8,
 };
 
 /* root, attrs */
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c b/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c
index 787edd7d8ce4..cdf54af49a81 100644
--- a/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c
+++ b/tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c
@@ -295,14 +295,6 @@ static const struct ctlv_node_schema evidence_get_request_selection_attr_node_sc
 
 /* root, attrs */
 static const struct ctlv_node_schema evidence_get_request_attr_node_schemas[] = {
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE,
-		.kind = CTLV_SCHEMA_BINARY,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
-		.min_length = 0,
-		.max_length = 4096,
-	},
 	{
 		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_OBJECT,
 		.kind = CTLV_SCHEMA_NEST,
@@ -316,6 +308,14 @@ static const struct ctlv_node_schema evidence_get_request_attr_node_schemas[] =
 
 /* root, optional attrs */
 static const struct ctlv_node_schema evidence_get_request_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 0,
+		.max_length = 4096,
+	},
 	{
 		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
 		.kind = CTLV_SCHEMA_UINT,
@@ -375,9 +375,9 @@ static const struct ctlv_node_schema evidence_get_request_node_schema = {
 	.kind = CTLV_SCHEMA_NEST,
 	.max_occurrences = 1,
 	.attrs = evidence_get_request_attr_node_schemas,
-	.attrs_count = 2,
+	.attrs_count = 1,
 	.optional_attrs = evidence_get_request_optional_attr_node_schemas,
-	.optional_attrs_count = 7,
+	.optional_attrs_count = 8,
 };
 
 /* root, attrs */
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c b/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c
index b278511f5776..077f8c7fbc81 100644
--- a/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c
+++ b/tools/testing/selftests/attestation/ctlv_attestation_user_gen.c
@@ -265,14 +265,6 @@ static const struct ctlv_node_schema evidence_get_request_selection_attr_node_sc
 
 /* root, attrs */
 static const struct ctlv_node_schema evidence_get_request_attr_node_schemas[] = {
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE,
-		.kind = CTLV_SCHEMA_BINARY,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
-		.min_length = 0,
-		.max_length = 4096,
-	},
 	{
 		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_OBJECT,
 		.kind = CTLV_SCHEMA_NEST,
@@ -286,6 +278,14 @@ static const struct ctlv_node_schema evidence_get_request_attr_node_schemas[] =
 
 /* root, optional attrs */
 static const struct ctlv_node_schema evidence_get_request_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 0,
+		.max_length = 4096,
+	},
 	{
 		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
 		.kind = CTLV_SCHEMA_UINT,
@@ -345,9 +345,9 @@ static const struct ctlv_node_schema evidence_get_request_node_schema = {
 	.kind = CTLV_SCHEMA_NEST,
 	.max_occurrences = 1,
 	.attrs = evidence_get_request_attr_node_schemas,
-	.attrs_count = 2,
+	.attrs_count = 1,
 	.optional_attrs = evidence_get_request_optional_attr_node_schemas,
-	.optional_attrs_count = 7,
+	.optional_attrs_count = 8,
 };
 
 /* root, attrs */
diff --git a/tools/testing/selftests/attestation/tdx_attestation_test.c b/tools/testing/selftests/attestation/tdx_attestation_test.c
index a06315af2021..1a370a529f50 100644
--- a/tools/testing/selftests/attestation/tdx_attestation_test.c
+++ b/tools/testing/selftests/attestation/tdx_attestation_test.c
@@ -165,8 +165,9 @@ static int tdx_evidence_get(struct ctlv_dev *dev, const char *format,
 	ret = ctlv_attestation_evidence_get_request_new(&request);
 	if (ret)
 		return ret;
-	ctlv_attestation_evidence_challenge_put(request, challenge,
-						challenge_length);
+	if (challenge)
+		ctlv_attestation_evidence_challenge_put(request, challenge,
+							challenge_length);
 	ctlv_attestation_evidence_object_start(request);
 	ctlv_attestation_evidence_blob_class_put(request,
 						 CTLV_ATTESTATION_EVIDENCE_BLOB_CLASS_PRIMARY);
@@ -244,6 +245,10 @@ TEST_F(tdx_attestation, common_validation_precedes_native_calls)
 	struct ctlv_reply *reply;
 	struct ctlv_msg *request;
 
+	EXPECT_EQ(-EINVAL,
+		  tdx_evidence_get(self->dev, "tdx-tdreport-1.0", NULL, 0,
+				   report.bytes, sizeof(report.bytes), &reply,
+				   &self->error));
 	EXPECT_EQ(-EINVAL,
 		  tdx_evidence_get(self->dev, "tdx-tdreport-1.0", challenge,
 				   sizeof(challenge) - 1, report.bytes,
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family
  2026-10-28 15:26 [RFC PATCH v1 00/11] PCI/TSM: Add CTLV attestation Aneesh Kumar K.V (Arm)
                   ` (4 preceding siblings ...)
  2026-10-28 15:26 ` [RFC PATCH v1 05/11] attestation: Make the evidence-get challenge attribute optional Aneesh Kumar K.V (Arm)
@ 2026-10-28 15:26 ` Aneesh Kumar K.V (Arm)
  2026-10-28 15:26 ` [RFC PATCH v1 07/11] attestation: Add attestation helpers for PCI/TSM integration Aneesh Kumar K.V (Arm)
                   ` (4 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-10-28 15:26 UTC (permalink / raw)
  To: linux-coco
  Cc: Aneesh Kumar K.V (Arm), Jiri Pirko, Kevin Tian,
	Alexey Kardashevskiy, Xu Yilun, Catalin Marinas, Suzuki K Poulose,
	Steven Price, Kiryl Shutsemau, Rick Edgecombe, Dave Hansen,
	Jonathan Cameron, Jason Gunthorpe, Bjorn Helgaas, Ashish Kalra,
	Tom Lendacky, John Allen, Will Deacon, Mark Rutland, patches,
	Dan Williams

Register ctlv-attestation-pci as a child of the common attestation
family. Add PCI state reporting and an object-selecting evidence refresh
operation with an optional challenge. Keep connect, disconnect, lock and
unlock in sysfs; IDE setup and TDISP run transition is driven by
evidence acceptance.

Cc: Jiri Pirko <jiri@resnulli.us>
Cc: Dan Williams <djbw@kernel.org>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Jonathan Cameron <jic23@kernel.org>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 .../ctlv/abi/ctlv-attestation-pci.yaml        |   43 +
 Documentation/ctlv/abi/families.registry.yaml |    1 +
 Documentation/ctlv/families.yaml              |    1 +
 Documentation/ctlv/generated-files.yaml       |   11 +
 .../ctlv/specs/ctlv-attestation-pci.yaml      |   79 +
 drivers/pci/tsm/ctlv_attestation_pci_gen.c    |  111 ++
 drivers/pci/tsm/ctlv_attestation_pci_gen.h    |  100 +
 include/uapi/linux/ctlv_attestation_pci_gen.h |   74 +
 include/uapi/linux/ctlv_families_gen.h        |    1 +
 tools/ctlv/tests/support.py                   |    3 +-
 tools/ctlv/tests/test_outputs.py              |    6 +-
 tools/ctlv/tests/test_semantics.py            |   38 +
 tools/ctlv/tests/test_spec.py                 |    1 +
 .../ctlv_attestation_pci_user_gen.c           | 1697 +++++++++++++++++
 .../ctlv_attestation_pci_user_gen.h           |  230 +++
 15 files changed, 2392 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/ctlv/abi/ctlv-attestation-pci.yaml
 create mode 100644 Documentation/ctlv/specs/ctlv-attestation-pci.yaml
 create mode 100644 drivers/pci/tsm/ctlv_attestation_pci_gen.c
 create mode 100644 drivers/pci/tsm/ctlv_attestation_pci_gen.h
 create mode 100644 include/uapi/linux/ctlv_attestation_pci_gen.h
 create mode 100644 tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.c
 create mode 100644 tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.h

diff --git a/Documentation/ctlv/abi/ctlv-attestation-pci.yaml b/Documentation/ctlv/abi/ctlv-attestation-pci.yaml
new file mode 100644
index 000000000000..04fe5c0ab8fa
--- /dev/null
+++ b/Documentation/ctlv/abi/ctlv-attestation-pci.yaml
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+%YAML 1.2
+---
+# Checked-in ABI snapshot of the ctlv-attestation-pci family, written by
+# "make -C tools/ctlv abi-update".
+#
+# The checker compares this family's declarations and inherited stub fills
+# against this file. Updating it is how a UAPI change gets reviewed; an update
+# that removes or renumbers anything, narrows a bound, or makes a placement
+# required is an ABI break, whatever this file says afterwards.
+family: {name: ctlv-attestation-pci, id: 6, inherits: ctlv-attestation,
+         pre-init: true}
+attr-groups:
+  pci:
+    id: 1
+    attrs:
+      name: {id: 1, type: string, min-length: 1, max-length: 31}
+      generation: {id: 2, type: uint, max: 4294967295}
+      tsm-id: {id: 3, type: uint, max: 2147483647}
+      connected: {id: 4, type: uint, max: 1}
+      locked: {id: 5, type: uint, max: 1}
+      accepted: {id: 6, type: uint, max: 1}
+      dsm: {id: 7, type: string, min-length: 1, max-length: 31}
+      object: {id: 8, enum: evidence-blob-class}
+      challenge: {id: 9, type: binary, length: 32}
+attr-sets:
+  change-reply: {id: 1}
+  state-reply: {id: 2}
+  refresh-request: {id: 3}
+fills:
+  device-info: {attrs: [pci.name]}
+ops:
+  state-get:
+    abi: {id: 1, type: action, fd-access: read}
+    reply:
+      attrs: [pci.generation, pci.connected, pci.locked, pci.accepted]
+      optional-attrs: [pci.tsm-id, pci.dsm]
+  evidence-refresh:
+    abi: {id: 2, type: action, availability: dynamic, optional: true}
+    request:
+      attrs: [pci.generation, pci.object]
+      optional-attrs: [pci.challenge]
+    reply: {attrs: [pci.generation]}
diff --git a/Documentation/ctlv/abi/families.registry.yaml b/Documentation/ctlv/abi/families.registry.yaml
index 7cdae176f5b4..a1a0836cef7e 100644
--- a/Documentation/ctlv/abi/families.registry.yaml
+++ b/Documentation/ctlv/abi/families.registry.yaml
@@ -10,3 +10,4 @@ families:
   - ctlv-test
   - ctlv-test-child
   - ctlv-attestation-sim
+  - ctlv-attestation-pci
diff --git a/Documentation/ctlv/families.yaml b/Documentation/ctlv/families.yaml
index db8b5185af11..8c1efa5e66bb 100644
--- a/Documentation/ctlv/families.yaml
+++ b/Documentation/ctlv/families.yaml
@@ -16,3 +16,4 @@ families:
   - ctlv-test
   - ctlv-test-child
   - ctlv-attestation-sim
+  - ctlv-attestation-pci
diff --git a/Documentation/ctlv/generated-files.yaml b/Documentation/ctlv/generated-files.yaml
index 3e6cda5fd91f..505e1db064e1 100644
--- a/Documentation/ctlv/generated-files.yaml
+++ b/Documentation/ctlv/generated-files.yaml
@@ -67,3 +67,14 @@ families:
         tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.h
       user-source: >-
         tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c
+
+  - name: ctlv-attestation-pci
+    source: Documentation/ctlv/specs/ctlv-attestation-pci.yaml
+    outputs:
+      uapi-header: include/uapi/linux/ctlv_attestation_pci_gen.h
+      kernel-header: drivers/pci/tsm/ctlv_attestation_pci_gen.h
+      kernel-source: drivers/pci/tsm/ctlv_attestation_pci_gen.c
+      user-header: >-
+        tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.h
+      user-source: >-
+        tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.c
diff --git a/Documentation/ctlv/specs/ctlv-attestation-pci.yaml b/Documentation/ctlv/specs/ctlv-attestation-pci.yaml
new file mode 100644
index 000000000000..edb35cede0c2
--- /dev/null
+++ b/Documentation/ctlv/specs/ctlv-attestation-pci.yaml
@@ -0,0 +1,79 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+%YAML 1.2
+---
+family:
+  name: ctlv-attestation-pci
+  inherits: ctlv-attestation
+  pre-init: true
+
+attr-groups:
+  - name: pci
+    attrs:
+      - name: name
+        type: string
+        min-length: 1
+        max-length: 31
+      - name: generation
+        type: uint
+        max: 4294967295
+      - name: tsm-id
+        type: uint
+        max: 2147483647
+      - name: connected
+        type: uint
+        max: 1
+      - name: locked
+        type: uint
+        max: 1
+      - name: accepted
+        type: uint
+        max: 1
+      - name: dsm
+        type: string
+        min-length: 1
+        max-length: 31
+
+      - name: object
+        enum: evidence-blob-class
+      - name: challenge
+        type: binary
+        min-length: 32
+        max-length: 32
+
+attr-sets:
+  - name: device-info
+    attrs: pci.name
+  - name: change-reply
+    attrs: pci.generation
+  - name: state-reply
+    attrs:
+      - pci.generation
+      - pci.connected
+      - pci.locked
+      - pci.accepted
+    optional-attrs:
+      - pci.tsm-id
+      - pci.dsm
+
+  - name: refresh-request
+    attrs:
+      - pci.generation
+      - pci.object
+    optional-attrs:
+      - pci.challenge
+
+ops:
+  - name: state-get
+    type: action
+    fd-access: read
+    reply:
+      attrs: state-reply
+  - name: evidence-refresh
+    type: action
+    fd-access: read-write
+    availability: dynamic
+    optional: true
+    request:
+      attrs: refresh-request
+    reply:
+      attrs: change-reply
diff --git a/drivers/pci/tsm/ctlv_attestation_pci_gen.c b/drivers/pci/tsm/ctlv_attestation_pci_gen.c
new file mode 100644
index 000000000000..a857fe158dc8
--- /dev/null
+++ b/drivers/pci/tsm/ctlv_attestation_pci_gen.c
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/* Do not edit, regenerate with tools/ctlv/ctlv-regen.sh, from: */
+/*	Documentation/ctlv/specs/ctlv-attestation-pci.yaml */
+/* CTLV-GEN kernel source */
+
+#define _CTLV_GENERATED_METADATA_ONLY
+#include <linux/array_size.h>
+#include <linux/build_bug.h>
+#include <linux/ctlv_family.h>
+#include <linux/export.h>
+#include <linux/limits.h>
+
+#include <uapi/linux/ctlv_gen.h>
+#include <uapi/linux/ctlv_attestation_pci_gen.h>
+
+#include "ctlv_attestation_pci_gen.h"
+
+static const struct ctlv_node_schema state_get_request_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(0, 0, 0),
+};
+
+static const struct ctlv_node_schema state_get_reply_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(1, 6, 4),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_PCI_ATTR_PCI_GENERATION, 1, 0, U32_MAX),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_PCI_ATTR_PCI_CONNECTED, 1, 0, 1),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_PCI_ATTR_PCI_LOCKED, 1, 0, 1),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_PCI_ATTR_PCI_ACCEPTED, 1, 0, 1),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_PCI_ATTR_PCI_TSM_ID, 1, 0, 2147483647),
+	CTLV_NODE_SCHEMA_STRING(CTLV_ATTESTATION_PCI_ATTR_PCI_DSM, 1, 1, 31),
+};
+
+static const struct ctlv_node_schema evidence_refresh_request_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(1, 3, 2),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_PCI_ATTR_PCI_GENERATION, 1, 0, U32_MAX),
+	CTLV_NODE_SCHEMA_ENUM(CTLV_ATTESTATION_PCI_ATTR_PCI_OBJECT, 1, 9),
+	CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_PCI_ATTR_PCI_CHALLENGE, 1, 32, 32),
+};
+
+static const struct ctlv_node_schema evidence_refresh_reply_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(1, 1, 1),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_PCI_ATTR_PCI_GENERATION, 1, 0, U32_MAX),
+};
+
+static const struct ctlv_node_schema device_info_fill_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(1, 1, 1),
+	CTLV_NODE_SCHEMA_STRING(CTLV_ATTESTATION_PCI_ATTR_PCI_NAME, 1, 1, 31),
+};
+
+/* The extension points this family fills. */
+static const struct ctlv_stub_fill_schema ctlv_attestation_pci_stub_fill_schemas[] = {
+	CTLV_STUB_FILL_SCHEMA(CTLV_FAMILY_ATTESTATION, CTLV_ATTESTATION_SET_DEVICE_INFO,
+			      device_info),
+};
+
+/* The ops this family declares, in declaration order. */
+const struct ctlv_op_schema ctlv_attestation_pci_op_schemas[] = {
+	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_PCI_OP_STATE_GET, CTLV_OP_FD_ACCESS_READ,
+			      CTLV_OP_AVAILABILITY_FIXED, 16, 184,
+			      CTLV_SCHEMA_TREE(state_get_request),
+			      CTLV_SCHEMA_TREE(state_get_reply)),
+	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_PCI_OP_EVIDENCE_REFRESH,
+			      CTLV_OP_FD_ACCESS_READ_WRITE, CTLV_OP_AVAILABILITY_DYNAMIC, 112, 40,
+			      CTLV_SCHEMA_TREE(evidence_refresh_request),
+			      CTLV_SCHEMA_TREE(evidence_refresh_reply), .optional = true),
+};
+
+/* The family ID is its position in the central registry. */
+const struct ctlv_family_schema ctlv_attestation_pci_schema = {
+	.name = "ctlv-attestation-pci",
+	.family_id = 6,
+	.ops = ctlv_attestation_pci_op_schemas,
+	.ops_count = ARRAY_SIZE(ctlv_attestation_pci_op_schemas),
+	.stub_fill_schemas = ctlv_attestation_pci_stub_fill_schemas,
+	.stub_fill_schemas_count = ARRAY_SIZE(ctlv_attestation_pci_stub_fill_schemas),
+};
+
+/* Maximum uevent cost for this family chain. */
+static_assert(150 <= CTLV_UEVENT_MAX_LEN);
+static_assert(5 <= CTLV_UEVENT_MAX_VARS);
+
+/* Guards for generated maxima. */
+static_assert(184 <= CTLV_MAX_INLINE_MESSAGE_LEN);
+
+/* Local action and query bindings in declaration order. */
+static const struct ctlv_op ctlv_attestation_pci_ops[] = {
+	CTLV_OP_ACTION(ctlv_attestation_pci_state_get),
+	CTLV_OP_ACTION(ctlv_attestation_pci_evidence_refresh),
+};
+
+/* Extension-point callbacks. */
+static const struct ctlv_stub_ops ctlv_attestation_pci_stub_ops[] = {
+	CTLV_STUB_OPS(CTLV_FAMILY_ATTESTATION, CTLV_ATTESTATION_SET_DEVICE_INFO,
+		      ctlv_attestation_pci_device_info),
+};
+
+/* Family descriptor used by ctlv_create(). */
+const struct ctlv_family ctlv_attestation_pci_family = {
+	.schema = &ctlv_attestation_pci_schema,
+	.parent = &ctlv_attestation_family,
+	.ops = ctlv_attestation_pci_ops,
+	.ops_count = ARRAY_SIZE(ctlv_attestation_pci_ops),
+	.pre = ctlv_attestation_pci_op_pre,
+	.post = ctlv_attestation_pci_op_post,
+	.priv_size = ctlv_attestation_pci_priv_size,
+	.pre_init = ctlv_attestation_pci_pre_init,
+	.init = ctlv_attestation_pci_init,
+	.fini = ctlv_attestation_pci_fini,
+	.stub_ops = ctlv_attestation_pci_stub_ops,
+	.stub_ops_count = ARRAY_SIZE(ctlv_attestation_pci_stub_ops),
+};
+EXPORT_SYMBOL_NS_GPL(ctlv_attestation_pci_family, "CTLV");
diff --git a/drivers/pci/tsm/ctlv_attestation_pci_gen.h b/drivers/pci/tsm/ctlv_attestation_pci_gen.h
new file mode 100644
index 000000000000..95cad4e7b0bf
--- /dev/null
+++ b/drivers/pci/tsm/ctlv_attestation_pci_gen.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit, regenerate with tools/ctlv/ctlv-regen.sh, from: */
+/*	Documentation/ctlv/specs/ctlv-attestation-pci.yaml */
+/* CTLV-GEN kernel header */
+
+/* Kernel metadata and family API of the ctlv-attestation-pci CTLV family. */
+
+#ifndef _LINUX_CTLV_ATTESTATION_PCI_GEN_H
+#define _LINUX_CTLV_ATTESTATION_PCI_GEN_H
+
+#include <linux/ctlv_family.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+
+#include <uapi/linux/ctlv_attestation_pci_gen.h>
+
+#include "ctlv_attestation_gen.h"
+
+/* Ops this family declares, in declaration order. */
+extern const struct ctlv_op_schema ctlv_attestation_pci_op_schemas[];
+
+extern const struct ctlv_family_schema ctlv_attestation_pci_schema;
+
+/* Implemented by this family, one per action and query it declares. */
+int ctlv_attestation_pci_state_get_action(void *priv, struct ctlv_op_ctx *ctx,
+					  const struct ctlv_cursor *request,
+					  struct ctlv_cursor *reply);
+int ctlv_attestation_pci_evidence_refresh_action(void *priv, struct ctlv_op_ctx *ctx,
+						 const struct ctlv_cursor *request,
+						 struct ctlv_cursor *reply);
+
+/* Family-wide hooks around every effective non-core op. */
+int ctlv_attestation_pci_op_pre(void *priv, struct ctlv_op_ctx *ctx);
+void ctlv_attestation_pci_op_post(void *priv, struct ctlv_op_ctx *ctx);
+
+/* Core-allocated family priv lifecycle. */
+size_t ctlv_attestation_pci_priv_size(void);
+const void *ctlv_attestation_pci_pre_init(const void *init_info);
+int ctlv_attestation_pci_init(struct ctlv_device *ctlvdev, const void *init_info, void *priv);
+void ctlv_attestation_pci_fini(struct ctlv_device *ctlvdev, void *priv);
+
+/* device-info of ctlv-attestation: fill and read callbacks. */
+int ctlv_attestation_pci_device_info_fill(void *priv, struct ctlv_op_ctx *ctx,
+					  const struct ctlv_cursor *request,
+					  struct ctlv_cursor *nest);
+void ctlv_attestation_pci_device_info_read(void *priv, struct ctlv_op_ctx *ctx,
+					   const struct ctlv_cursor *nest);
+
+extern const struct ctlv_family ctlv_attestation_pci_family;
+
+#ifndef _CTLV_GENERATED_METADATA_ONLY
+
+/* Optional publication and dynamic availability helpers. */
+CTLV_OP_PROVIDE(ctlv_attestation_pci_evidence_refresh, CTLV_ATTESTATION_PCI_OP_EVIDENCE_REFRESH)
+CTLV_OP_ENABLE(ctlv_attestation_pci_evidence_refresh, CTLV_ATTESTATION_PCI_OP_EVIDENCE_REFRESH)
+CTLV_OP_DISABLE(ctlv_attestation_pci_evidence_refresh, CTLV_ATTESTATION_PCI_OP_EVIDENCE_REFRESH)
+
+/* Reading a validated message. A blob has a resolver instead of a getter. */
+CTLV_IN_REF(ctlv_attestation_pci_evidence_refresh_generation,
+	    &ctlv_attestation_pci_op_schemas[1].request, CTLV_ATTESTATION_PCI_ATTR_PCI_GENERATION,
+	    0, 0, CTLV_SCHEMA_UINT)
+CTLV_IN_HAS(ctlv_attestation_pci_evidence_refresh_generation)
+CTLV_IN_U64(ctlv_attestation_pci_evidence_refresh_generation)
+CTLV_IN_REF(ctlv_attestation_pci_evidence_refresh_object,
+	    &ctlv_attestation_pci_op_schemas[1].request, CTLV_ATTESTATION_PCI_ATTR_PCI_OBJECT, 0, 1,
+	    CTLV_SCHEMA_ENUM)
+CTLV_IN_HAS(ctlv_attestation_pci_evidence_refresh_object)
+CTLV_IN_U64(ctlv_attestation_pci_evidence_refresh_object)
+CTLV_IN_REF(ctlv_attestation_pci_evidence_refresh_challenge,
+	    &ctlv_attestation_pci_op_schemas[1].request, CTLV_ATTESTATION_PCI_ATTR_PCI_CHALLENGE, 0,
+	    2, CTLV_SCHEMA_BINARY)
+CTLV_IN_HAS(ctlv_attestation_pci_evidence_refresh_challenge)
+CTLV_IN_BINARY(ctlv_attestation_pci_evidence_refresh_challenge)
+CTLV_IN_REF(ctlv_attestation_pci_device_info_name,
+	    &ctlv_attestation_pci_schema.stub_fill_schemas[0].members,
+	    CTLV_ATTESTATION_PCI_ATTR_PCI_NAME, 0, 0, CTLV_SCHEMA_STRING)
+CTLV_IN_HAS(ctlv_attestation_pci_device_info_name)
+CTLV_IN_STRING(ctlv_attestation_pci_device_info_name)
+
+/* Writing a reply. An append is atomic; a nest ends with ctlv_nest_end(). */
+CTLV_PUT_STRING(ctlv_attestation_pci_pci_name, CTLV_ATTESTATION_PCI_ATTR_PCI_NAME,
+		CTLV_SCHEMA_STRING)
+CTLV_PUT_U64(ctlv_attestation_pci_pci_generation, CTLV_ATTESTATION_PCI_ATTR_PCI_GENERATION,
+	     CTLV_SCHEMA_UINT)
+CTLV_PUT_U64(ctlv_attestation_pci_pci_tsm_id, CTLV_ATTESTATION_PCI_ATTR_PCI_TSM_ID,
+	     CTLV_SCHEMA_UINT)
+CTLV_PUT_U64(ctlv_attestation_pci_pci_connected, CTLV_ATTESTATION_PCI_ATTR_PCI_CONNECTED,
+	     CTLV_SCHEMA_UINT)
+CTLV_PUT_U64(ctlv_attestation_pci_pci_locked, CTLV_ATTESTATION_PCI_ATTR_PCI_LOCKED,
+	     CTLV_SCHEMA_UINT)
+CTLV_PUT_U64(ctlv_attestation_pci_pci_accepted, CTLV_ATTESTATION_PCI_ATTR_PCI_ACCEPTED,
+	     CTLV_SCHEMA_UINT)
+CTLV_PUT_STRING(ctlv_attestation_pci_pci_dsm, CTLV_ATTESTATION_PCI_ATTR_PCI_DSM, CTLV_SCHEMA_STRING)
+CTLV_PUT_U64(ctlv_attestation_pci_pci_object, CTLV_ATTESTATION_PCI_ATTR_PCI_OBJECT,
+	     CTLV_SCHEMA_ENUM)
+CTLV_PUT_BINARY(ctlv_attestation_pci_pci_challenge, CTLV_ATTESTATION_PCI_ATTR_PCI_CHALLENGE,
+		CTLV_SCHEMA_BINARY)
+#endif /* _CTLV_GENERATED_METADATA_ONLY */
+
+#endif /* _LINUX_CTLV_ATTESTATION_PCI_GEN_H */
diff --git a/include/uapi/linux/ctlv_attestation_pci_gen.h b/include/uapi/linux/ctlv_attestation_pci_gen.h
new file mode 100644
index 000000000000..160385bc2c2d
--- /dev/null
+++ b/include/uapi/linux/ctlv_attestation_pci_gen.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit, regenerate with tools/ctlv/ctlv-regen.sh, from: */
+/*	Documentation/ctlv/specs/ctlv-attestation-pci.yaml */
+/* CTLV-GEN uapi header */
+
+/*
+ * Constants of the ctlv-attestation-pci CTLV family. Attr and op IDs are complete; the wire format
+ * carrying them is in <linux/ctlv.h>.
+ *
+ * The inherited ctlv-attestation header is included here, so one include covers the whole effective
+ * schema.
+ */
+
+#ifndef _UAPI_LINUX_CTLV_ATTESTATION_PCI_GEN_H
+#define _UAPI_LINUX_CTLV_ATTESTATION_PCI_GEN_H
+
+#include <linux/ctlv.h>
+#include <linux/ctlv_attestation_gen.h>
+#include <linux/ctlv_families_gen.h>
+
+/* attr groups */
+enum _ctlv_attestation_pci_group {
+	CTLV_ATTESTATION_PCI_GROUP_PCI = 1,
+};
+
+#define _CTLV_ATTESTATION_PCI_ATTR(_group, _attr) \
+	CTLV_ATTR(CTLV_FAMILY_ATTESTATION_PCI, (_group), (_attr))
+
+/* attrs of group pci */
+#define _CTLV_ATTESTATION_PCI_ATTR_PCI(_attr) \
+	_CTLV_ATTESTATION_PCI_ATTR(CTLV_ATTESTATION_PCI_GROUP_PCI, \
+				   _CTLV_ATTESTATION_PCI_ATTR_PCI_##_attr)
+
+enum _ctlv_attestation_pci_attr_pci {
+	_CTLV_ATTESTATION_PCI_ATTR_PCI_NAME = 1,
+	_CTLV_ATTESTATION_PCI_ATTR_PCI_GENERATION,
+	_CTLV_ATTESTATION_PCI_ATTR_PCI_TSM_ID,
+	_CTLV_ATTESTATION_PCI_ATTR_PCI_CONNECTED,
+	_CTLV_ATTESTATION_PCI_ATTR_PCI_LOCKED,
+	_CTLV_ATTESTATION_PCI_ATTR_PCI_ACCEPTED,
+	_CTLV_ATTESTATION_PCI_ATTR_PCI_DSM,
+	_CTLV_ATTESTATION_PCI_ATTR_PCI_OBJECT,
+	_CTLV_ATTESTATION_PCI_ATTR_PCI_CHALLENGE,
+};
+
+#define CTLV_ATTESTATION_PCI_ATTR_PCI_NAME _CTLV_ATTESTATION_PCI_ATTR_PCI(NAME)
+#define CTLV_ATTESTATION_PCI_ATTR_PCI_GENERATION _CTLV_ATTESTATION_PCI_ATTR_PCI(GENERATION)
+#define CTLV_ATTESTATION_PCI_ATTR_PCI_TSM_ID _CTLV_ATTESTATION_PCI_ATTR_PCI(TSM_ID)
+#define CTLV_ATTESTATION_PCI_ATTR_PCI_CONNECTED _CTLV_ATTESTATION_PCI_ATTR_PCI(CONNECTED)
+#define CTLV_ATTESTATION_PCI_ATTR_PCI_LOCKED _CTLV_ATTESTATION_PCI_ATTR_PCI(LOCKED)
+#define CTLV_ATTESTATION_PCI_ATTR_PCI_ACCEPTED _CTLV_ATTESTATION_PCI_ATTR_PCI(ACCEPTED)
+#define CTLV_ATTESTATION_PCI_ATTR_PCI_DSM _CTLV_ATTESTATION_PCI_ATTR_PCI(DSM)
+#define CTLV_ATTESTATION_PCI_ATTR_PCI_OBJECT _CTLV_ATTESTATION_PCI_ATTR_PCI(OBJECT)
+#define CTLV_ATTESTATION_PCI_ATTR_PCI_CHALLENGE _CTLV_ATTESTATION_PCI_ATTR_PCI(CHALLENGE)
+
+/* attr sets */
+enum _ctlv_attestation_pci_set {
+	CTLV_ATTESTATION_PCI_SET_CHANGE_REPLY = 1,
+	CTLV_ATTESTATION_PCI_SET_STATE_REPLY,
+	CTLV_ATTESTATION_PCI_SET_REFRESH_REQUEST,
+};
+
+/* ops */
+enum _ctlv_attestation_pci_op {
+	_CTLV_ATTESTATION_PCI_OP_STATE_GET = 1,
+	_CTLV_ATTESTATION_PCI_OP_EVIDENCE_REFRESH,
+};
+
+#define _CTLV_ATTESTATION_PCI_OP(_op) \
+	CTLV_OP(CTLV_FAMILY_ATTESTATION_PCI, _CTLV_ATTESTATION_PCI_OP_##_op)
+#define CTLV_ATTESTATION_PCI_OP_STATE_GET _CTLV_ATTESTATION_PCI_OP(STATE_GET)
+#define CTLV_ATTESTATION_PCI_OP_EVIDENCE_REFRESH _CTLV_ATTESTATION_PCI_OP(EVIDENCE_REFRESH)
+
+#endif /* _UAPI_LINUX_CTLV_ATTESTATION_PCI_GEN_H */
diff --git a/include/uapi/linux/ctlv_families_gen.h b/include/uapi/linux/ctlv_families_gen.h
index 19c478660429..bad143ec85fd 100644
--- a/include/uapi/linux/ctlv_families_gen.h
+++ b/include/uapi/linux/ctlv_families_gen.h
@@ -15,6 +15,7 @@ enum _ctlv_family {
 	CTLV_FAMILY_TEST,
 	CTLV_FAMILY_TEST_CHILD,
 	CTLV_FAMILY_ATTESTATION_SIM,
+	CTLV_FAMILY_ATTESTATION_PCI,
 };
 
 #endif /* _UAPI_LINUX_CTLV_FAMILIES_GEN_H */
diff --git a/tools/ctlv/tests/support.py b/tools/ctlv/tests/support.py
index c50bb6b5b831..ddddefe48d4c 100644
--- a/tools/ctlv/tests/support.py
+++ b/tools/ctlv/tests/support.py
@@ -31,7 +31,8 @@ FIXTURE_REGISTRY = TESTS_DIR / 'families.yaml'
 
 # Every registered family, in registry order.
 FAMILIES = ('ctlv', 'ctlv-attestation', 'ctlv-attestation-tdx',
-            'ctlv-test', 'ctlv-test-child', 'ctlv-attestation-sim')
+            'ctlv-test', 'ctlv-test-child', 'ctlv-attestation-sim',
+            'ctlv-attestation-pci')
 
 # The fixture families, in fixture registry order: a parent holding one of
 # every shape the language allows, and a child that inherits it.
diff --git a/tools/ctlv/tests/test_outputs.py b/tools/ctlv/tests/test_outputs.py
index 5be773189ae1..c2443b2963db 100644
--- a/tools/ctlv/tests/test_outputs.py
+++ b/tools/ctlv/tests/test_outputs.py
@@ -67,9 +67,9 @@ class TestGeneratedOutputs(unittest.TestCase):
     def test_manifest_is_complete_and_unique(self):
         plan = outputs.load(self.paths)
 
-        self.assertEqual(37, len(plan))
-        self.assertEqual(37, len({item.path for item in plan}))
-        self.assertEqual(37, len({(item.source, item.flavour)
+        self.assertEqual(42, len(plan))
+        self.assertEqual(42, len({item.path for item in plan}))
+        self.assertEqual(42, len({(item.source, item.flavour)
                                  for item in plan}))
         registry_outputs = [item for item in plan if item.family is None]
         self.assertEqual(
diff --git a/tools/ctlv/tests/test_semantics.py b/tools/ctlv/tests/test_semantics.py
index 70f0a99f44f6..c0fb0b336db7 100644
--- a/tools/ctlv/tests/test_semantics.py
+++ b/tools/ctlv/tests/test_semantics.py
@@ -576,6 +576,44 @@ class TestRecursion(unittest.TestCase):
                                  'the same members, node for node')
 
 
+class TestPciAttestationPolicy(unittest.TestCase):
+    def test_connection_controls_are_not_in_ctlv(self):
+        family = support.load('ctlv-attestation-pci')
+        names = {op.name for op in family.ops}
+        self.assertNotIn('connect', names)
+        self.assertNotIn('disconnect', names)
+        self.assertNotIn('lock', names)
+        self.assertNotIn('unlock', names)
+        self.assertNotIn('devsec-run', names)
+
+    def test_backend_dependent_operations_are_dynamic(self):
+        family = support.load('ctlv-attestation-pci')
+        for name in ('evidence-refresh', 'evidence-get', 'evidence-accept',
+                     'evidence-accept-clear', 'devsec-state-get'):
+            with self.subTest(op=name):
+                op = support.op(family, name)
+                self.assertEqual('dynamic', op.availability)
+                self.assertTrue(op.optional)
+
+    def test_refresh_selects_an_object_with_an_optional_challenge(self):
+        family = support.load('ctlv-attestation-pci')
+        request = support.op(family, 'evidence-refresh').request
+        target = support.placement(request, 'pci.object')
+        challenge = support.placement(request, 'pci.challenge')
+        self.assertTrue(target.required)
+        self.assertFalse(challenge.required)
+        self.assertEqual(family.attrs['evidence.blob-class'].decl.ref,
+                         target.attr.decl.ref)
+
+    def test_state_operations_are_fixed(self):
+        family = support.load('ctlv-attestation-pci')
+        for name in ('state-get', 'device-get'):
+            with self.subTest(op=name):
+                op = support.op(family, name)
+                self.assertEqual('fixed', op.availability)
+                self.assertFalse(op.optional)
+
+
 class TestDump(unittest.TestCase):
     def test_repeated_renders_are_identical(self):
         for name in support.ALL_FAMILIES:
diff --git a/tools/ctlv/tests/test_spec.py b/tools/ctlv/tests/test_spec.py
index d80b16673675..248db73b044d 100644
--- a/tools/ctlv/tests/test_spec.py
+++ b/tools/ctlv/tests/test_spec.py
@@ -347,6 +347,7 @@ class TestRegistry(unittest.TestCase):
         self.assertEqual(3, registry.family_id('ctlv-test'))
         self.assertEqual(4, registry.family_id('ctlv-test-child'))
         self.assertEqual(5, registry.family_id('ctlv-attestation-sim'))
+        self.assertEqual(6, registry.family_id('ctlv-attestation-pci'))
 
     def test_the_fixture_registry_numbers_the_fixture_families(self):
         registry = spec.load_registry(
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.c b/tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.c
new file mode 100644
index 000000000000..dcf1477f3941
--- /dev/null
+++ b/tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.c
@@ -0,0 +1,1697 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/* Do not edit, regenerate with tools/ctlv/ctlv-regen.sh, from: */
+/*	Documentation/ctlv/specs/ctlv-attestation-pci.yaml */
+/* CTLV-GEN user source */
+
+#include <stdint.h>
+
+#include <ctlv.h>
+
+#include "ctlv_attestation_pci_user_gen.h"
+
+/* Effective request and reply schemas. */
+
+static const struct ctlv_node_schema device_get_request_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+};
+
+/* cap.object, attrs */
+static const struct ctlv_node_schema device_get_reply_caps_evidence_object_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_CAP_BLOB_CLASS,
+		.kind = CTLV_SCHEMA_ENUM,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 1,
+		.max = 9,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_CAP_FORMAT,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 128,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_CAP_REPRESENTATION,
+		.kind = CTLV_SCHEMA_ENUM,
+		.max_occurrences = 2,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 1,
+		.max = 2,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_CAP_MAX_LENGTH,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = UINT64_MAX,
+	},
+};
+
+/* cap.object, optional attrs */
+static const struct ctlv_node_schema
+	device_get_reply_caps_evidence_object_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_CAP_BLOB_INSTANCE,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_CAP_HASH_ALGO,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 21,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 20,
+	},
+};
+
+/* cap.evidence, attrs */
+static const struct ctlv_node_schema device_get_reply_caps_evidence_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_CAP_MIN_CHALLENGE_LENGTH,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = UINT64_MAX,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_CAP_MAX_CHALLENGE_LENGTH,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = UINT64_MAX,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_CAP_MAX_TOTAL_OUTPUT_LENGTH,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = UINT64_MAX,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_CAP_OBJECT,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 16,
+		.attrs = device_get_reply_caps_evidence_object_attr_node_schemas,
+		.attrs_count = 4,
+		.optional_attrs = device_get_reply_caps_evidence_object_optional_attr_node_schemas,
+		.optional_attrs_count = 2,
+	},
+};
+
+/* cap.evidence, optional attrs */
+static const struct ctlv_node_schema device_get_reply_caps_evidence_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_CAP_EVIDENCE_FLAGS,
+		.kind = CTLV_SCHEMA_FLAGS,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_FLAGS_MASK,
+		.flags_mask = 15,
+	},
+};
+
+/* cap.mr, attrs */
+static const struct ctlv_node_schema device_get_reply_caps_mr_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_CAP_MR_FLAGS,
+		.kind = CTLV_SCHEMA_FLAGS,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_FLAGS_MASK,
+		.flags_mask = 7,
+	},
+};
+
+/* device.caps, optional attrs */
+static const struct ctlv_node_schema device_get_reply_caps_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_CAP_EVIDENCE,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+		.attrs = device_get_reply_caps_evidence_attr_node_schemas,
+		.attrs_count = 4,
+		.optional_attrs = device_get_reply_caps_evidence_optional_attr_node_schemas,
+		.optional_attrs_count = 1,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_CAP_MR,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+		.attrs = device_get_reply_caps_mr_attr_node_schemas,
+		.attrs_count = 1,
+	},
+};
+
+/* device.info, attrs */
+static const struct ctlv_node_schema device_get_reply_info_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_PCI_ATTR_PCI_NAME,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 31,
+	},
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema device_get_reply_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_CAPS,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+		.optional_attrs = device_get_reply_caps_optional_attr_node_schemas,
+		.optional_attrs_count = 2,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_INFO,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+		.attrs = device_get_reply_info_attr_node_schemas,
+		.attrs_count = 1,
+	},
+};
+
+static const struct ctlv_node_schema device_get_reply_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = device_get_reply_attr_node_schemas,
+	.attrs_count = 3,
+};
+
+/* evidence.object, attrs */
+static const struct ctlv_node_schema evidence_get_request_object_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_CLASS,
+		.kind = CTLV_SCHEMA_ENUM,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 1,
+		.max = 9,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_FORMAT,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 128,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_REPRESENTATION,
+		.kind = CTLV_SCHEMA_ENUM,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 1,
+		.max = 2,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_DATA,
+		.kind = CTLV_SCHEMA_BLOB,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_BLOB,
+		.blob_direction = CTLV_BLOB_DIRECTION_OUTPUT,
+		.blob_backings = 14,
+		.blob_max_length = 0xffffffffULL,
+		.blob_alignment = 1,
+	},
+};
+
+/* evidence.object, optional attrs */
+static const struct ctlv_node_schema evidence_get_request_object_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_INSTANCE,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_HASH_ALGO,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 20,
+	},
+};
+
+/* evidence.selection, attrs */
+static const struct ctlv_node_schema evidence_get_request_selection_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_ID,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_HASH_ALGO,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 20,
+	},
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema evidence_get_request_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_OBJECT,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 16,
+		.attrs = evidence_get_request_object_attr_node_schemas,
+		.attrs_count = 4,
+		.optional_attrs = evidence_get_request_object_optional_attr_node_schemas,
+		.optional_attrs_count = 2,
+	},
+};
+
+/* root, optional attrs */
+static const struct ctlv_node_schema evidence_get_request_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 0,
+		.max_length = 4096,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_SELECTION,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 256,
+		.attrs = evidence_get_request_selection_attr_node_schemas,
+		.attrs_count = 2,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_PRIVLEVEL,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_SERVICE_PROVIDER,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 128,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_SERVICE_GUID,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 16,
+		.max_length = 16,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_SERVICE_MANIFEST_VERSION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_OPTIONS,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+	},
+};
+
+static const struct ctlv_node_schema evidence_get_request_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = evidence_get_request_attr_node_schemas,
+	.attrs_count = 1,
+	.optional_attrs = evidence_get_request_optional_attr_node_schemas,
+	.optional_attrs_count = 8,
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema evidence_get_reply_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_OBJECT_LENGTH,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 16,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = UINT64_MAX,
+	},
+};
+
+/* root, optional attrs */
+static const struct ctlv_node_schema evidence_get_reply_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
+static const struct ctlv_node_schema evidence_get_reply_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = evidence_get_reply_attr_node_schemas,
+	.attrs_count = 2,
+	.optional_attrs = evidence_get_reply_optional_attr_node_schemas,
+	.optional_attrs_count = 2,
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema mr_get_request_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_ID,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
+static const struct ctlv_node_schema mr_get_request_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = mr_get_request_attr_node_schemas,
+	.attrs_count = 1,
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema mr_get_reply_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_NAME,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 128,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_SUPPORTED_HASH_ALGO,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 21,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 20,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_CAPS,
+		.kind = CTLV_SCHEMA_FLAGS,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_FLAGS_MASK,
+		.flags_mask = 7,
+	},
+};
+
+/* root, optional attrs */
+static const struct ctlv_node_schema mr_get_reply_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_REGISTER_INDEX,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_STATE_FLAGS,
+		.kind = CTLV_SCHEMA_FLAGS,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_FLAGS_MASK,
+		.flags_mask = 1,
+	},
+};
+
+static const struct ctlv_node_schema mr_get_reply_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = mr_get_reply_attr_node_schemas,
+	.attrs_count = 3,
+	.optional_attrs = mr_get_reply_optional_attr_node_schemas,
+	.optional_attrs_count = 2,
+};
+
+static const struct ctlv_node_schema mr_dump_request_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+};
+
+/* mr.item, attrs */
+static const struct ctlv_node_schema mr_dump_reply_item_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_ID,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_NAME,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 128,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_SUPPORTED_HASH_ALGO,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 21,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 20,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_CAPS,
+		.kind = CTLV_SCHEMA_FLAGS,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_FLAGS_MASK,
+		.flags_mask = 7,
+	},
+};
+
+/* mr.item, optional attrs */
+static const struct ctlv_node_schema mr_dump_reply_item_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_REGISTER_INDEX,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_STATE_FLAGS,
+		.kind = CTLV_SCHEMA_FLAGS,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_FLAGS_MASK,
+		.flags_mask = 1,
+	},
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema mr_dump_reply_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_ITEM,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 0, /* unlimited */
+		.attrs = mr_dump_reply_item_attr_node_schemas,
+		.attrs_count = 4,
+		.optional_attrs = mr_dump_reply_item_optional_attr_node_schemas,
+		.optional_attrs_count = 2,
+	},
+};
+
+static const struct ctlv_node_schema mr_dump_reply_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = mr_dump_reply_attr_node_schemas,
+	.attrs_count = 1,
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema mr_read_request_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_ID,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
+/* root, optional attrs */
+static const struct ctlv_node_schema mr_read_request_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_OPTIONS,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+	},
+};
+
+static const struct ctlv_node_schema mr_read_request_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = mr_read_request_attr_node_schemas,
+	.attrs_count = 1,
+	.optional_attrs = mr_read_request_optional_attr_node_schemas,
+	.optional_attrs_count = 2,
+};
+
+/* mr.value, attrs */
+static const struct ctlv_node_schema mr_read_reply_value_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_HASH_ALGO,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 20,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_DATA,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 16,
+		.max_length = 64,
+	},
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema mr_read_reply_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_VALUE,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 21,
+		.attrs = mr_read_reply_value_attr_node_schemas,
+		.attrs_count = 2,
+	},
+};
+
+static const struct ctlv_node_schema mr_read_reply_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = mr_read_reply_attr_node_schemas,
+	.attrs_count = 3,
+};
+
+/* mr.value, attrs */
+static const struct ctlv_node_schema mr_extend_request_value_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_HASH_ALGO,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 20,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_DATA,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 16,
+		.max_length = 64,
+	},
+};
+
+/* record.item, attrs */
+static const struct ctlv_node_schema mr_extend_request_item_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_RECORD_FORMAT,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 128,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_RECORD_DATA,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 0,
+		.max_length = 4096,
+	},
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema mr_extend_request_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_ID,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_VALUE,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 21,
+		.attrs = mr_extend_request_value_attr_node_schemas,
+		.attrs_count = 2,
+	},
+};
+
+/* root, optional attrs */
+static const struct ctlv_node_schema mr_extend_request_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_OPTIONS,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_RECORD_ITEM,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+		.attrs = mr_extend_request_item_attr_node_schemas,
+		.attrs_count = 2,
+	},
+};
+
+static const struct ctlv_node_schema mr_extend_request_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = mr_extend_request_attr_node_schemas,
+	.attrs_count = 2,
+	.optional_attrs = mr_extend_request_optional_attr_node_schemas,
+	.optional_attrs_count = 3,
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema mr_extend_reply_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
+static const struct ctlv_node_schema mr_extend_reply_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = mr_extend_reply_attr_node_schemas,
+	.attrs_count = 2,
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema device_state_changed_reply_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
+static const struct ctlv_node_schema device_state_changed_reply_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = device_state_changed_reply_attr_node_schemas,
+	.attrs_count = 1,
+};
+
+/* mr.value, attrs */
+static const struct ctlv_node_schema mr_log_entry_appended_reply_entry_value_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_HASH_ALGO,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 20,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_DATA,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 16,
+		.max_length = 64,
+	},
+};
+
+/* log.mr, attrs */
+static const struct ctlv_node_schema mr_log_entry_appended_reply_entry_mr_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_NAME,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 128,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_SUPPORTED_HASH_ALGO,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 21,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 20,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_CAPS,
+		.kind = CTLV_SCHEMA_FLAGS,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_FLAGS_MASK,
+		.flags_mask = 7,
+	},
+};
+
+/* log.mr, optional attrs */
+static const struct ctlv_node_schema
+	mr_log_entry_appended_reply_entry_mr_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_REGISTER_INDEX,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
+/* log.ima, attrs */
+static const struct ctlv_node_schema mr_log_entry_appended_reply_entry_ima_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_LOG_TEMPLATE_NAME,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 128,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_LOG_TEMPLATE_DATA,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 0,
+		.max_length = 0x10000ULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_LOG_VIOLATION,
+		.kind = CTLV_SCHEMA_BOOL,
+		.max_occurrences = 1,
+	},
+};
+
+/* record.item, attrs */
+static const struct ctlv_node_schema mr_log_entry_appended_reply_entry_item_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_RECORD_FORMAT,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 128,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_RECORD_DATA,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 0,
+		.max_length = 4096,
+	},
+};
+
+/* notification.entry, attrs */
+static const struct ctlv_node_schema mr_log_entry_appended_reply_entry_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_ID,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_LOG_EVENT,
+		.kind = CTLV_SCHEMA_ENUM,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 1,
+		.max = 2,
+	},
+};
+
+/* notification.entry, optional attrs */
+static const struct ctlv_node_schema
+	mr_log_entry_appended_reply_entry_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_VALUE,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 21,
+		.attrs = mr_log_entry_appended_reply_entry_value_attr_node_schemas,
+		.attrs_count = 2,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_LOG_MR,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+		.attrs = mr_log_entry_appended_reply_entry_mr_attr_node_schemas,
+		.attrs_count = 3,
+		.optional_attrs = mr_log_entry_appended_reply_entry_mr_optional_attr_node_schemas,
+		.optional_attrs_count = 1,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_LOG_IMA,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+		.attrs = mr_log_entry_appended_reply_entry_ima_attr_node_schemas,
+		.attrs_count = 3,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_RECORD_ITEM,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+		.attrs = mr_log_entry_appended_reply_entry_item_attr_node_schemas,
+		.attrs_count = 2,
+	},
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema mr_log_entry_appended_reply_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_NOTIFICATION_ENTRY,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+		.attrs = mr_log_entry_appended_reply_entry_attr_node_schemas,
+		.attrs_count = 4,
+		.optional_attrs = mr_log_entry_appended_reply_entry_optional_attr_node_schemas,
+		.optional_attrs_count = 4,
+	},
+};
+
+static const struct ctlv_node_schema mr_log_entry_appended_reply_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = mr_log_entry_appended_reply_attr_node_schemas,
+	.attrs_count = 1,
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema evidence_accept_request_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
+static const struct ctlv_node_schema evidence_accept_request_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = evidence_accept_request_attr_node_schemas,
+	.attrs_count = 2,
+};
+
+static const struct ctlv_node_schema evidence_accept_clear_request_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+};
+
+static const struct ctlv_node_schema devsec_state_get_request_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema devsec_state_get_reply_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
+/* root, optional attrs */
+static const struct ctlv_node_schema devsec_state_get_reply_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPTED_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVSEC_TDISP_STATE,
+		.kind = CTLV_SCHEMA_ENUM,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 1,
+		.max = 4,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVSEC_DEVICE_TRUST,
+		.kind = CTLV_SCHEMA_ENUM,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 1,
+		.max = 5,
+	},
+};
+
+static const struct ctlv_node_schema devsec_state_get_reply_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = devsec_state_get_reply_attr_node_schemas,
+	.attrs_count = 1,
+	.optional_attrs = devsec_state_get_reply_optional_attr_node_schemas,
+	.optional_attrs_count = 4,
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema evidence_state_changed_reply_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
+/* root, optional attrs */
+static const struct ctlv_node_schema evidence_state_changed_reply_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPTED_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
+static const struct ctlv_node_schema evidence_state_changed_reply_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = evidence_state_changed_reply_attr_node_schemas,
+	.attrs_count = 1,
+	.optional_attrs = evidence_state_changed_reply_optional_attr_node_schemas,
+	.optional_attrs_count = 2,
+};
+
+/* mr.selection, attrs */
+static const struct ctlv_node_schema mr_log_get_request_selection_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_ID,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
+/* root, optional attrs */
+static const struct ctlv_node_schema mr_log_get_request_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_SELECTION,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 256,
+		.attrs = mr_log_get_request_selection_attr_node_schemas,
+		.attrs_count = 1,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_START_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_END_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
+static const struct ctlv_node_schema mr_log_get_request_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.optional_attrs = mr_log_get_request_optional_attr_node_schemas,
+	.optional_attrs_count = 3,
+};
+
+/* mr.value, attrs */
+static const struct ctlv_node_schema mr_log_get_reply_entry_value_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_HASH_ALGO,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 20,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_DATA,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 16,
+		.max_length = 64,
+	},
+};
+
+/* log.mr, attrs */
+static const struct ctlv_node_schema mr_log_get_reply_entry_mr_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_NAME,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 128,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_SUPPORTED_HASH_ALGO,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 21,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 20,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_CAPS,
+		.kind = CTLV_SCHEMA_FLAGS,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_FLAGS_MASK,
+		.flags_mask = 7,
+	},
+};
+
+/* log.mr, optional attrs */
+static const struct ctlv_node_schema mr_log_get_reply_entry_mr_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_REGISTER_INDEX,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
+/* log.ima, attrs */
+static const struct ctlv_node_schema mr_log_get_reply_entry_ima_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_LOG_TEMPLATE_NAME,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 128,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_LOG_TEMPLATE_DATA,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 0,
+		.max_length = 0x10000ULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_LOG_VIOLATION,
+		.kind = CTLV_SCHEMA_BOOL,
+		.max_occurrences = 1,
+	},
+};
+
+/* record.item, attrs */
+static const struct ctlv_node_schema mr_log_get_reply_entry_item_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_RECORD_FORMAT,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 128,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_RECORD_DATA,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 0,
+		.max_length = 4096,
+	},
+};
+
+/* log.entry, attrs */
+static const struct ctlv_node_schema mr_log_get_reply_entry_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_ID,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_LOG_EVENT,
+		.kind = CTLV_SCHEMA_ENUM,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 1,
+		.max = 2,
+	},
+};
+
+/* log.entry, optional attrs */
+static const struct ctlv_node_schema mr_log_get_reply_entry_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_VALUE,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 21,
+		.attrs = mr_log_get_reply_entry_value_attr_node_schemas,
+		.attrs_count = 2,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_LOG_MR,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+		.attrs = mr_log_get_reply_entry_mr_attr_node_schemas,
+		.attrs_count = 3,
+		.optional_attrs = mr_log_get_reply_entry_mr_optional_attr_node_schemas,
+		.optional_attrs_count = 1,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_LOG_IMA,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+		.attrs = mr_log_get_reply_entry_ima_attr_node_schemas,
+		.attrs_count = 3,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_RECORD_ITEM,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 1,
+		.attrs = mr_log_get_reply_entry_item_attr_node_schemas,
+		.attrs_count = 2,
+	},
+};
+
+/* root, optional attrs */
+static const struct ctlv_node_schema mr_log_get_reply_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_START_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_MR_END_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_LOG_ENTRY,
+		.kind = CTLV_SCHEMA_NEST,
+		.max_occurrences = 0, /* unlimited */
+		.attrs = mr_log_get_reply_entry_attr_node_schemas,
+		.attrs_count = 4,
+		.optional_attrs = mr_log_get_reply_entry_optional_attr_node_schemas,
+		.optional_attrs_count = 4,
+	},
+};
+
+static const struct ctlv_node_schema mr_log_get_reply_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.optional_attrs = mr_log_get_reply_optional_attr_node_schemas,
+	.optional_attrs_count = 3,
+};
+
+static const struct ctlv_node_schema state_get_request_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema state_get_reply_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_PCI_ATTR_PCI_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_PCI_ATTR_PCI_CONNECTED,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 1,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_PCI_ATTR_PCI_LOCKED,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 1,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_PCI_ATTR_PCI_ACCEPTED,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 1,
+	},
+};
+
+/* root, optional attrs */
+static const struct ctlv_node_schema state_get_reply_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_PCI_ATTR_PCI_TSM_ID,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0x7fffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_PCI_ATTR_PCI_DSM,
+		.kind = CTLV_SCHEMA_STRING,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 1,
+		.max_length = 31,
+	},
+};
+
+static const struct ctlv_node_schema state_get_reply_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = state_get_reply_attr_node_schemas,
+	.attrs_count = 4,
+	.optional_attrs = state_get_reply_optional_attr_node_schemas,
+	.optional_attrs_count = 2,
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema evidence_refresh_request_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_PCI_ATTR_PCI_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+	{
+		.attr_id = CTLV_ATTESTATION_PCI_ATTR_PCI_OBJECT,
+		.kind = CTLV_SCHEMA_ENUM,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 1,
+		.max = 9,
+	},
+};
+
+/* root, optional attrs */
+static const struct ctlv_node_schema evidence_refresh_request_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_PCI_ATTR_PCI_CHALLENGE,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 32,
+		.max_length = 32,
+	},
+};
+
+static const struct ctlv_node_schema evidence_refresh_request_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = evidence_refresh_request_attr_node_schemas,
+	.attrs_count = 2,
+	.optional_attrs = evidence_refresh_request_optional_attr_node_schemas,
+	.optional_attrs_count = 1,
+};
+
+/* root, attrs */
+static const struct ctlv_node_schema evidence_refresh_reply_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_PCI_ATTR_PCI_GENERATION,
+		.kind = CTLV_SCHEMA_UINT,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN | CTLV_NODE_SCHEMA_HAS_MAX,
+		.min = 0,
+		.max = 0xffffffffULL,
+	},
+};
+
+static const struct ctlv_node_schema evidence_refresh_reply_node_schema = {
+	.attr_id = CTLV_ATTR_ROOT,
+	.kind = CTLV_SCHEMA_NEST,
+	.max_occurrences = 1,
+	.attrs = evidence_refresh_reply_attr_node_schemas,
+	.attrs_count = 1,
+};
+
+/* Effective ops in declaration order. */
+static const struct ctlv_op_spec ctlv_attestation_pci_op_specs[] = {
+	{
+		.op_id = CTLV_ATTESTATION_OP_DEVICE_GET,
+		.type = CTLV_OP_TYPE_ACTION,
+		.fd_access = CTLV_OP_FD_ACCESS_READ,
+		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.request_max_len = 16,
+		.action_reply_max_len = 12944,
+		.request = &device_get_request_node_schema,
+		.reply = &device_get_reply_node_schema,
+	},
+	{
+		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_GET,
+		.type = CTLV_OP_TYPE_ACTION,
+		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
+		.optional = true,
+		.request_max_len = 26800,
+		.action_reply_max_len = 472,
+		.request = &evidence_get_request_node_schema,
+		.reply = &evidence_get_reply_node_schema,
+	},
+	{
+		.op_id = CTLV_ATTESTATION_OP_MR_GET,
+		.type = CTLV_OP_TYPE_ACTION,
+		.fd_access = CTLV_OP_FD_ACCESS_READ,
+		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.optional = true,
+		.request_max_len = 40,
+		.action_reply_max_len = 744,
+		.request = &mr_get_request_node_schema,
+		.reply = &mr_get_reply_node_schema,
+	},
+	{
+		.op_id = CTLV_ATTESTATION_OP_MR_DUMP,
+		.type = CTLV_OP_TYPE_QUERY,
+		.fd_access = CTLV_OP_FD_ACCESS_READ,
+		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.optional = true,
+		.request_max_len = 16,
+		.query_reply_min_len = 184,
+		.query_reply_suggested_len = 4096,
+		.request = &mr_dump_request_node_schema,
+		.reply = &mr_dump_reply_node_schema,
+	},
+	{
+		.op_id = CTLV_ATTESTATION_OP_MR_READ,
+		.type = CTLV_OP_TYPE_QUERY,
+		.fd_access = CTLV_OP_FD_ACCESS_READ,
+		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.optional = true,
+		.request_max_len = 80,
+		.query_reply_min_len = 112,
+		.query_reply_suggested_len = 4096,
+		.request = &mr_read_request_node_schema,
+		.reply = &mr_read_reply_node_schema,
+	},
+	{
+		.op_id = CTLV_ATTESTATION_OP_MR_EXTEND,
+		.type = CTLV_OP_TYPE_ACTION,
+		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
+		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.optional = true,
+		.request_max_len = 6880,
+		.action_reply_max_len = 64,
+		.request = &mr_extend_request_node_schema,
+		.reply = &mr_extend_reply_node_schema,
+	},
+	{
+		.op_id = CTLV_ATTESTATION_OP_DEVICE_STATE_CHANGED,
+		.type = CTLV_OP_TYPE_EVENT,
+		.event_record_max_len = 64,
+		.reply = &device_state_changed_reply_node_schema,
+	},
+	{
+		.op_id = CTLV_ATTESTATION_OP_MR_LOG_ENTRY_APPENDED,
+		.type = CTLV_OP_TYPE_EVENT,
+		.event_record_max_len = 73416,
+		.reply = &mr_log_entry_appended_reply_node_schema,
+	},
+	{
+		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT,
+		.type = CTLV_OP_TYPE_ACTION,
+		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
+		.optional = true,
+		.request_max_len = 64,
+		.request = &evidence_accept_request_node_schema,
+	},
+	{
+		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR,
+		.type = CTLV_OP_TYPE_ACTION,
+		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
+		.optional = true,
+		.request_max_len = 16,
+		.request = &evidence_accept_clear_request_node_schema,
+	},
+	{
+		.op_id = CTLV_ATTESTATION_OP_DEVSEC_STATE_GET,
+		.type = CTLV_OP_TYPE_QUERY,
+		.fd_access = CTLV_OP_FD_ACCESS_READ,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
+		.optional = true,
+		.request_max_len = 16,
+		.query_reply_min_len = 40,
+		.query_reply_suggested_len = 1024,
+		.request = &devsec_state_get_request_node_schema,
+		.reply = &devsec_state_get_reply_node_schema,
+	},
+	{
+		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED,
+		.type = CTLV_OP_TYPE_EVENT,
+		.event_record_max_len = 112,
+		.reply = &evidence_state_changed_reply_node_schema,
+	},
+	{
+		.op_id = CTLV_ATTESTATION_OP_MR_LOG_GET,
+		.type = CTLV_OP_TYPE_QUERY,
+		.fd_access = CTLV_OP_FD_ACCESS_READ,
+		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.optional = true,
+		.request_max_len = 10304,
+		.query_reply_min_len = 65600,
+		.query_reply_suggested_len = 131072,
+		.request = &mr_log_get_request_node_schema,
+		.reply = &mr_log_get_reply_node_schema,
+	},
+	{
+		.op_id = CTLV_ATTESTATION_PCI_OP_STATE_GET,
+		.type = CTLV_OP_TYPE_ACTION,
+		.fd_access = CTLV_OP_FD_ACCESS_READ,
+		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.request_max_len = 16,
+		.action_reply_max_len = 184,
+		.request = &state_get_request_node_schema,
+		.reply = &state_get_reply_node_schema,
+	},
+	{
+		.op_id = CTLV_ATTESTATION_PCI_OP_EVIDENCE_REFRESH,
+		.type = CTLV_OP_TYPE_ACTION,
+		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
+		.optional = true,
+		.request_max_len = 112,
+		.action_reply_max_len = 40,
+		.request = &evidence_refresh_request_node_schema,
+		.reply = &evidence_refresh_reply_node_schema,
+	},
+};
+
+/* Family specification descriptor. */
+const struct ctlv_family_spec ctlv_attestation_pci_spec = {
+	.name = "ctlv-attestation-pci",
+	.family_id = 6,
+	.parent = &ctlv_attestation_spec,
+	.ops = ctlv_attestation_pci_op_specs,
+	.ops_count = 15,
+};
diff --git a/tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.h b/tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.h
new file mode 100644
index 000000000000..468546732a43
--- /dev/null
+++ b/tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.h
@@ -0,0 +1,230 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit, regenerate with tools/ctlv/ctlv-regen.sh, from: */
+/*	Documentation/ctlv/specs/ctlv-attestation-pci.yaml */
+/* CTLV-GEN user header */
+
+/* Userspace binding of the ctlv-attestation-pci CTLV family. */
+
+#ifndef _CTLV_CTLV_ATTESTATION_PCI_USER_GEN_H
+#define _CTLV_CTLV_ATTESTATION_PCI_USER_GEN_H
+
+#include <ctlv.h>
+
+#include <linux/ctlv_attestation_pci_gen.h>
+
+#include "ctlv_attestation_user_gen.h"
+
+/* What this family's specification says, for holding a device against it. */
+extern const struct ctlv_family_spec ctlv_attestation_pci_spec;
+
+/* Executing an op. A query is reassembled into the one reply it fills. */
+
+#define CTLV_ATTESTATION_PCI_OP_STATE_GET_REQUEST_MAX_LEN 16
+#define CTLV_ATTESTATION_PCI_OP_STATE_GET_ACTION_REPLY_MAX_LEN 184
+
+static inline int ctlv_attestation_pci_state_get_request_new(struct ctlv_msg **request)
+{
+	const __u32 len = CTLV_ATTESTATION_PCI_OP_STATE_GET_REQUEST_MAX_LEN;
+
+	return ctlv_msg_new(len, request);
+}
+
+static inline int ctlv_attestation_pci_state_get(struct ctlv_dev *dev, struct ctlv_msg *request,
+						 struct ctlv_reply **reply,
+						 struct ctlv_error *error)
+{
+	return ctlv_action(dev, CTLV_ATTESTATION_PCI_OP_STATE_GET, request, reply, error);
+}
+
+#define CTLV_ATTESTATION_PCI_OP_EVIDENCE_REFRESH_REQUEST_MAX_LEN 112
+#define CTLV_ATTESTATION_PCI_OP_EVIDENCE_REFRESH_ACTION_REPLY_MAX_LEN 40
+
+static inline int ctlv_attestation_pci_evidence_refresh_request_new(struct ctlv_msg **request)
+{
+	const __u32 len = CTLV_ATTESTATION_PCI_OP_EVIDENCE_REFRESH_REQUEST_MAX_LEN;
+
+	return ctlv_msg_new(len, request);
+}
+
+static inline int ctlv_attestation_pci_evidence_refresh(struct ctlv_dev *dev,
+							struct ctlv_msg *request,
+							struct ctlv_reply **reply,
+							struct ctlv_error *error)
+{
+	return ctlv_action(dev, CTLV_ATTESTATION_PCI_OP_EVIDENCE_REFRESH, request, reply, error);
+}
+
+/* Building a request. ctlv_msg_err() reports the first failure. */
+
+static inline int ctlv_attestation_pci_pci_name_put(struct ctlv_msg *msg, const char *value)
+{
+	return ctlv_put_str(msg, CTLV_ATTESTATION_PCI_ATTR_PCI_NAME, value);
+}
+
+static inline int ctlv_attestation_pci_pci_generation_put(struct ctlv_msg *msg, __u64 value)
+{
+	return ctlv_put_u64(msg, CTLV_ATTESTATION_PCI_ATTR_PCI_GENERATION, value);
+}
+
+static inline int ctlv_attestation_pci_pci_tsm_id_put(struct ctlv_msg *msg, __u64 value)
+{
+	return ctlv_put_u64(msg, CTLV_ATTESTATION_PCI_ATTR_PCI_TSM_ID, value);
+}
+
+static inline int ctlv_attestation_pci_pci_connected_put(struct ctlv_msg *msg, __u64 value)
+{
+	return ctlv_put_u64(msg, CTLV_ATTESTATION_PCI_ATTR_PCI_CONNECTED, value);
+}
+
+static inline int ctlv_attestation_pci_pci_locked_put(struct ctlv_msg *msg, __u64 value)
+{
+	return ctlv_put_u64(msg, CTLV_ATTESTATION_PCI_ATTR_PCI_LOCKED, value);
+}
+
+static inline int ctlv_attestation_pci_pci_accepted_put(struct ctlv_msg *msg, __u64 value)
+{
+	return ctlv_put_u64(msg, CTLV_ATTESTATION_PCI_ATTR_PCI_ACCEPTED, value);
+}
+
+static inline int ctlv_attestation_pci_pci_dsm_put(struct ctlv_msg *msg, const char *value)
+{
+	return ctlv_put_str(msg, CTLV_ATTESTATION_PCI_ATTR_PCI_DSM, value);
+}
+
+static inline int ctlv_attestation_pci_pci_object_put(struct ctlv_msg *msg, __u64 value)
+{
+	return ctlv_put_u64(msg, CTLV_ATTESTATION_PCI_ATTR_PCI_OBJECT, value);
+}
+
+static inline int ctlv_attestation_pci_pci_challenge_put(struct ctlv_msg *msg, const void *value,
+							 __u32 len)
+{
+	return ctlv_put_bin(msg, CTLV_ATTESTATION_PCI_ATTR_PCI_CHALLENGE, value, len);
+}
+
+/* Reading a reply or a record. A getter answers whether it could read. */
+
+static inline bool ctlv_attestation_pci_pci_name_present(const struct ctlv_attr *nest)
+{
+	return ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_NAME);
+}
+
+static inline bool ctlv_attestation_pci_pci_name_get(const struct ctlv_attr *nest,
+						     const char **value)
+{
+	const struct ctlv_attr *attr;
+
+	attr = ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_NAME);
+	return attr && !ctlv_get_str(attr, value);
+}
+
+static inline bool ctlv_attestation_pci_pci_generation_present(const struct ctlv_attr *nest)
+{
+	return ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_GENERATION);
+}
+
+static inline bool ctlv_attestation_pci_pci_generation_get(const struct ctlv_attr *nest,
+							   __u64 *value)
+{
+	const struct ctlv_attr *attr;
+
+	attr = ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_GENERATION);
+	return attr && !ctlv_get_u64(attr, value);
+}
+
+static inline bool ctlv_attestation_pci_pci_tsm_id_present(const struct ctlv_attr *nest)
+{
+	return ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_TSM_ID);
+}
+
+static inline bool ctlv_attestation_pci_pci_tsm_id_get(const struct ctlv_attr *nest, __u64 *value)
+{
+	const struct ctlv_attr *attr;
+
+	attr = ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_TSM_ID);
+	return attr && !ctlv_get_u64(attr, value);
+}
+
+static inline bool ctlv_attestation_pci_pci_connected_present(const struct ctlv_attr *nest)
+{
+	return ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_CONNECTED);
+}
+
+static inline bool ctlv_attestation_pci_pci_connected_get(const struct ctlv_attr *nest,
+							  __u64 *value)
+{
+	const struct ctlv_attr *attr;
+
+	attr = ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_CONNECTED);
+	return attr && !ctlv_get_u64(attr, value);
+}
+
+static inline bool ctlv_attestation_pci_pci_locked_present(const struct ctlv_attr *nest)
+{
+	return ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_LOCKED);
+}
+
+static inline bool ctlv_attestation_pci_pci_locked_get(const struct ctlv_attr *nest, __u64 *value)
+{
+	const struct ctlv_attr *attr;
+
+	attr = ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_LOCKED);
+	return attr && !ctlv_get_u64(attr, value);
+}
+
+static inline bool ctlv_attestation_pci_pci_accepted_present(const struct ctlv_attr *nest)
+{
+	return ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_ACCEPTED);
+}
+
+static inline bool ctlv_attestation_pci_pci_accepted_get(const struct ctlv_attr *nest, __u64 *value)
+{
+	const struct ctlv_attr *attr;
+
+	attr = ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_ACCEPTED);
+	return attr && !ctlv_get_u64(attr, value);
+}
+
+static inline bool ctlv_attestation_pci_pci_dsm_present(const struct ctlv_attr *nest)
+{
+	return ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_DSM);
+}
+
+static inline bool ctlv_attestation_pci_pci_dsm_get(const struct ctlv_attr *nest,
+						    const char **value)
+{
+	const struct ctlv_attr *attr;
+
+	attr = ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_DSM);
+	return attr && !ctlv_get_str(attr, value);
+}
+
+static inline bool ctlv_attestation_pci_pci_object_present(const struct ctlv_attr *nest)
+{
+	return ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_OBJECT);
+}
+
+static inline bool ctlv_attestation_pci_pci_object_get(const struct ctlv_attr *nest, __u64 *value,
+						       bool *known)
+{
+	const struct ctlv_attr *attr;
+
+	attr = ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_OBJECT);
+	return attr && !ctlv_get_enum(attr, 9, value, known);
+}
+
+static inline bool ctlv_attestation_pci_pci_challenge_present(const struct ctlv_attr *nest)
+{
+	return ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_CHALLENGE);
+}
+
+static inline bool ctlv_attestation_pci_pci_challenge_get(const struct ctlv_attr *nest,
+							  const void **value, __u32 *len)
+{
+	const struct ctlv_attr *attr;
+
+	attr = ctlv_attr_find(nest, CTLV_ATTESTATION_PCI_ATTR_PCI_CHALLENGE);
+	return attr && !ctlv_get_bin(attr, value, len);
+}
+
+#endif /* _CTLV_CTLV_ATTESTATION_PCI_USER_GEN_H */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [RFC PATCH v1 07/11] attestation: Add attestation helpers for PCI/TSM integration
  2026-10-28 15:26 [RFC PATCH v1 00/11] PCI/TSM: Add CTLV attestation Aneesh Kumar K.V (Arm)
                   ` (5 preceding siblings ...)
  2026-10-28 15:26 ` [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family Aneesh Kumar K.V (Arm)
@ 2026-10-28 15:26 ` Aneesh Kumar K.V (Arm)
  2026-10-28 15:26 ` [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts Aneesh Kumar K.V (Arm)
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 17+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-10-28 15:26 UTC (permalink / raw)
  To: linux-coco
  Cc: Aneesh Kumar K.V (Arm), Jiri Pirko, Kevin Tian,
	Alexey Kardashevskiy, Xu Yilun, Catalin Marinas, Suzuki K Poulose,
	Steven Price, Kiryl Shutsemau, Rick Edgecombe, Dave Hansen,
	Jonathan Cameron, Jason Gunthorpe, Bjorn Helgaas, Ashish Kalra,
	Tom Lendacky, John Allen, Will Deacon, Mark Rutland, patches

Add helpers that let the following PCI/TSM patch coordinate device state
changes with attestation state.

PCI child-family operations use ctlv_attestation_op_pre() to acquire the
attestation lock before invoking the backend. They need to validate the
requested device generation before changing state and invalidating
previous evidence.

The existing attestation_state_changed() takes the attestation lock itself.
Factor out a locked variant so those operations can report a state
change without acquiring the same mutex again.

Also expose helpers to check for device-generation overflow, read the
current device generation, and acquire the instance mutex for notifications
outside CTLV dispatch.

Cc: Jiri Pirko <jiri@resnulli.us>
Cc: Jonathan Cameron <jic23@kernel.org>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 drivers/attestation/core/core.c | 40 +++++++++++++++++++++++++---
 include/linux/attestation.h     | 47 +++++++++++++++++++++++++++++++++
 2 files changed, 84 insertions(+), 3 deletions(-)

diff --git a/drivers/attestation/core/core.c b/drivers/attestation/core/core.c
index 914f29693aee..e35c45bc715d 100644
--- a/drivers/attestation/core/core.c
+++ b/drivers/attestation/core/core.c
@@ -430,14 +430,13 @@ void attestation_mr_destroy(struct attestation_mr *mr)
 }
 EXPORT_SYMBOL_NS_GPL(attestation_mr_destroy, "ATTESTATION");
 
-void attestation_state_changed(struct attestation *att)
+void attestation_state_changed_locked(struct attestation *att)
 {
 	struct attestation_event_batch events = {};
 	bool evidence;
 	int ret;
 
-	guard(mutex)
-		(&att->lock);
+	lockdep_assert_held(&att->lock);
 	if (!attestation_registered(att))
 		return;
 	evidence = att->evidence_generation > att->evidence_floor || att->verdict_recorded;
@@ -458,8 +457,43 @@ void attestation_state_changed(struct attestation *att)
 		attestation_evidence_event_emit(&events, att);
 	attestation_events_abort(&events);
 }
+EXPORT_SYMBOL_NS_GPL(attestation_state_changed_locked, "ATTESTATION");
+
+void attestation_state_changed(struct attestation *att)
+{
+	guard(mutex)(&att->lock);
+	attestation_state_changed_locked(att);
+}
 EXPORT_SYMBOL_NS_GPL(attestation_state_changed, "ATTESTATION");
 
+void attestation_lock(struct attestation *att)
+{
+	mutex_lock(&att->lock);
+}
+EXPORT_SYMBOL_NS_GPL(attestation_lock, "ATTESTATION");
+
+void attestation_unlock(struct attestation *att)
+{
+	mutex_unlock(&att->lock);
+}
+EXPORT_SYMBOL_NS_GPL(attestation_unlock, "ATTESTATION");
+
+int attestation_state_change_prepare(struct attestation *att)
+{
+	lockdep_assert_held(&att->lock);
+	if (!attestation_registered(att))
+		return -ENODEV;
+	return attestation_preflight(att, true, false, false);
+}
+EXPORT_SYMBOL_NS_GPL(attestation_state_change_prepare, "ATTESTATION");
+
+unsigned int attestation_get_device_generation(struct attestation *att)
+{
+	lockdep_assert_held(&att->lock);
+	return att->device_generation;
+}
+EXPORT_SYMBOL_NS_GPL(attestation_get_device_generation, "ATTESTATION");
+
 static unsigned int attestation_provider_features(const struct attestation_provider *provider)
 {
 	unsigned int features = 0;
diff --git a/include/linux/attestation.h b/include/linux/attestation.h
index 09ebd2af3a1e..0dae626b2e0e 100644
--- a/include/linux/attestation.h
+++ b/include/linux/attestation.h
@@ -299,6 +299,53 @@ void attestation_mr_destroy(struct attestation_mr *mr);
  */
 void attestation_state_changed(struct attestation *att);
 
+/**
+ * attestation_lock - Lock the attestation instance.
+ * @att: attestation instance
+ *
+ * CTLV operations already hold this mutex. Other callers must follow the
+ * same lock order as the provider's CTLV pre-hook.
+ * Do not call while holding an attestation instance mutex.
+ */
+void attestation_lock(struct attestation *att);
+
+/**
+ * attestation_unlock - Release the attestation instance mutex.
+ * @att: attestation instance locked by attestation_lock()
+ */
+void attestation_unlock(struct attestation *att);
+
+/**
+ * attestation_state_change_prepare - Check whether device state can change.
+ * @att: attestation instance
+ *
+ * Call with the instance mutex held, before changing device state.
+ *
+ * Return: 0 on success, -ENODEV if the instance is no longer registered,
+ * or -EOVERFLOW if the device generation cannot be incremented.
+ */
+int attestation_state_change_prepare(struct attestation *att);
+
+/**
+ * attestation_state_changed_locked - Report a device state change.
+ * @att: attestation instance
+ *
+ * Call with the instance mutex held.
+ *
+ * Advance the device generation, invalidate previous evidence and acceptance
+ * verdicts, and emit state-change events. Invalidation still occurs if
+ * allocating the events fails.
+ */
+void attestation_state_changed_locked(struct attestation *att);
+
+/**
+ * attestation_get_device_generation - Read the device generation.
+ * @att: attestation instance
+ *
+ * The caller must hold the instance mutex.
+ */
+unsigned int attestation_get_device_generation(struct attestation *att);
+
 /**
  * attestation_features_set_locked - Set enabled features with the mutex held.
  * @att: attestation instance
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts
  2026-10-28 15:26 [RFC PATCH v1 00/11] PCI/TSM: Add CTLV attestation Aneesh Kumar K.V (Arm)
                   ` (6 preceding siblings ...)
  2026-10-28 15:26 ` [RFC PATCH v1 07/11] attestation: Add attestation helpers for PCI/TSM integration Aneesh Kumar K.V (Arm)
@ 2026-10-28 15:26 ` Aneesh Kumar K.V (Arm)
  2026-10-28 22:56   ` Dave Jiang
  2026-10-28 15:26 ` [RFC PATCH v1 09/11] crypto/ccp: Defer SEV-TIO link setup until evidence acceptance Aneesh Kumar K.V (Arm)
                   ` (2 subsequent siblings)
  10 siblings, 1 reply; 17+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-10-28 15:26 UTC (permalink / raw)
  To: linux-coco
  Cc: Aneesh Kumar K.V (Arm), Jiri Pirko, Kevin Tian,
	Alexey Kardashevskiy, Xu Yilun, Catalin Marinas, Suzuki K Poulose,
	Steven Price, Kiryl Shutsemau, Rick Edgecombe, Dave Hansen,
	Jonathan Cameron, Jason Gunthorpe, Bjorn Helgaas, Ashish Kalra,
	Tom Lendacky, John Allen, Will Deacon, Mark Rutland, patches

Create a PCI attestation instance only after sysfs connect or lock
succeeds. Export cached evidence through the common blob API and replace
the netlink evidence interface. Backends declare their evidence
capability bitmap; the common provider advertises and accepts only those
objects.

Route evidence acceptance to a link backend callback or guest ACCEPT.
Support an optional evidence.accept-object selector for any certificate
slot declared by the link backend, while the common core checks that the
object belongs to the retrieved evidence generation. Pass the selected
slot through pci_tsm.evidence.slot before invoking the backend.

Cc: Ashish Kalra <ashish.kalra@amd.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Cc: John Allen <john.allen@amd.com>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Jonathan Cameron <jic23@kernel.org>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 Documentation/ABI/testing/sysfs-bus-pci   | 108 ++---
 Documentation/ABI/testing/sysfs-class-tsm |   9 +-
 Documentation/driver-api/pci/tsm.rst      | 104 ++++-
 Documentation/netlink/specs/pci-tsm.yaml  | 151 -------
 drivers/crypto/ccp/sev-dev-tsm.c          |   1 +
 drivers/pci/Kconfig                       |   3 +
 drivers/pci/tsm/Makefile                  |   6 +-
 drivers/pci/tsm/attestation.c             | 525 ++++++++++++++++++++++
 drivers/pci/tsm/core.c                    | 430 ++++++++++--------
 drivers/pci/tsm/evidence.c                | 274 -----------
 drivers/pci/tsm/internal.h                |  26 ++
 drivers/pci/tsm/netlink.c                 |  43 --
 drivers/pci/tsm/netlink.h                 |  23 -
 drivers/virt/coco/arm-cca-guest/main.c    |  34 +-
 drivers/virt/coco/arm-cca-guest/rsi-da.c  |  16 +-
 include/linux/pci-tsm.h                   |  86 +++-
 include/linux/pci.h                       |   2 +
 include/uapi/linux/pci-tsm-netlink.h      | 101 -----
 samples/devsec/common.c                   |   1 -
 samples/devsec/link_tsm.c                 |   1 +
 samples/devsec/tsm.c                      |  32 +-
 21 files changed, 1075 insertions(+), 901 deletions(-)
 delete mode 100644 Documentation/netlink/specs/pci-tsm.yaml
 create mode 100644 drivers/pci/tsm/attestation.c
 delete mode 100644 drivers/pci/tsm/evidence.c
 create mode 100644 drivers/pci/tsm/internal.h
 delete mode 100644 drivers/pci/tsm/netlink.c
 delete mode 100644 drivers/pci/tsm/netlink.h
 delete mode 100644 include/uapi/linux/pci-tsm-netlink.h

diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
index adce5b6cba9f..56b1d92427ef 100644
--- a/Documentation/ABI/testing/sysfs-bus-pci
+++ b/Documentation/ABI/testing/sysfs-bus-pci
@@ -625,49 +625,56 @@ Description:
 What:		/sys/bus/pci/devices/.../tsm/
 Contact:	linux-coco@lists.linux.dev
 Description:
-		This directory only appears if a physical device function
-		supports authentication (PCIe CMA-SPDM), interface security
-		(PCIe TDISP), and is accepted for secure operation by the
-		platform TSM driver. This attribute directory appears
-		dynamically after the platform TSM driver loads. So, only after
-		the /sys/class/tsm/tsm0 device arrives can tools assume that
-		devices without a tsm/ attribute directory will never have one;
-		before that, the security capabilities of the device relative to
-		the platform TSM are unknown. See
-		Documentation/ABI/testing/sysfs-class-tsm.
+		Link TSM control and status. This directory appears when a link
+		TSM is registered for a capable PCIe function or its managed
+		subfunctions. The dsm attribute identifies the connected DSM.
+		Host PF0 exposes connect and disconnect controls. Evidence
+		retrieval and acceptance use CTLV attestation; see
+		Documentation/driver-api/pci/tsm.rst.
 
 What:		/sys/bus/pci/devices/.../tsm/connect
 Contact:	linux-coco@lists.linux.dev
 Description:
-		(RW) Write the name of a TSM (TEE Security Manager) device from
-		/sys/class/tsm to this file to establish a connection with the
-		device.  This typically includes an SPDM (DMTF Security
-		Protocols and Data Models) session over PCIe DOE (Data Object
-		Exchange) and may also include PCIe IDE (Integrity and Data
-		Encryption) establishment. Reads from this attribute return the
-		name of the connected TSM or the empty string if not
-		connected. A TSM device signals its readiness to accept PCI
-		connection via a KOBJ_CHANGE event. This is a "link" TSM
-		attribute, see Documentation/ABI/testing/sysfs-class-tsm.
+		(RW) On host PF0, write a link TSM name (tsmN) to associate the
+		device with that TSM and collect its supported identity evidence.
+		Reads return the associated TSM name, or an empty string.
+		Successful connect creates the CTLV attestation instance.
+		Backends may defer secure-session and stream setup until
+		userspace verifies the chain and issues CTLV evidence-accept.
 
 What:		/sys/bus/pci/devices/.../tsm/disconnect
 Contact:	linux-coco@lists.linux.dev
 Description:
-		(WO) Write the name of the TSM device that was specified
-		to 'connect' to teardown the connection.
-		The write fails with EBUSY while any vdevice depends on the
-		connection. Userspace must destroy those vdevices before
-		disconnecting the link. This is a
-		"link" TSM attribute, see
-		Documentation/ABI/testing/sysfs-class-tsm.
+		(WO) On host PF0, write the associated TSM name to release the
+		connection, including any established stream. Works before
+		evidence acceptance too. Successful disconnect removes the CTLV
+		attestation instance. Returns EBUSY while IOMMUFD vdevices
+		hold connection contexts.
+
+What:		/sys/bus/pci/devices/.../tsm/lock
+Contact:	linux-coco@lists.linux.dev
+Description:	(RW) Lock a TEE-capable device with a device-security TSM by
+		writing its name, for example "tsm0". Requires the device driver
+		to be detached. Reads return the associated TSM name or an empty
+		string. Successful locking collects evidence and creates the
+		device's CTLV attestation instance. CTLV evidence-accept enters
+		RUN after userspace has verified evidence from evidence-get.
+
+What:		/sys/bus/pci/devices/.../tsm/unlock
+Contact:	linux-coco@lists.linux.dev
+Description:	(WO) Unlock a device by writing its associated TSM name.
+		Requires the device driver to be detached. Successful unlock
+		revokes confidential-device acceptance and removes the CTLV
+		attestation instance. Failed unlock retains the error context
+		for retry through this file.
 
 What:		/sys/bus/pci/devices/.../tsm/dsm
 Contact:	linux-coco@lists.linux.dev
 Description:	(RO) Return PCI device name of this device's DSM (Device
 		Security Manager). When a device is in the connected state it
-		indicates that the platform TSM (TEE Security Manager) has made
-		a secure-session connection with a device's DSM. A DSM is always
-		physical function 0 and when the device supports TDISP (TEE
+		identifies the DSM associated with the platform TSM (TEE Security
+		Manager). This association can precede secure-session setup.
+		A DSM is always physical function 0 and when the device supports TDISP (TEE
 		Device Interface Security Protocol) its managed functions also
 		populate this tsm/dsm attribute. The managed functions of a DSM
 		are SR-IOV (Single Root I/O Virtualization) virtual functions,
@@ -692,45 +699,10 @@ Description:
 		available authentication methods for the device.
 
 		When present and the tsm/ attribute directory is present, the
-		authenticated attribute is an alias for the device 'connect'
-		state. See the 'tsm/connect' attribute for more details.
+		authenticated attribute reports the TSM name only after secure
+		session and stream setup succeed, otherwise an empty string.
+		Connection control uses tsm/connect and tsm/disconnect.
 		This is a "link" TSM attribute, see
 		Documentation/ABI/testing/sysfs-class-tsm.
 
-What:		/sys/bus/pci/devices/.../tsm/lock
-Contact:	linux-coco@lists.linux.dev
-Description:
-		(RW) Write the name of a TSM (TEE Security Manager) device
-		instance to this file to request that the platform transition
-		the PCIe device configuration to the TDISP LOCKED state. This
-		puts the device in a state where security sensitive
-		configuration setting can not be changed without transitioning
-		the device the PCIe TDISP ERROR state. Reads from this attribute
-		return the name of the TSM device instance that has arranged for
-		the device to be locked, or the empty string if not locked. A
-		TSM class device signals its readiness for lock requests via a
-		KOBJ_CHANGE event. Writes fail with EBUSY if this device is
-		bound to a driver. This is a "devsec" TSM attribute, see
-		Documentation/ABI/testing/sysfs-class-tsm. See
-		Documentation/driver-api/pci/tsm.rst for more details.
-
-What:		/sys/bus/pci/devices/.../tsm/unlock
-Contact:	linux-coco@lists.linux.dev
-Description:
-		(WO) Write the name of the TSM device that was specified to
-		'lock' to teardown the connection. Writes fail with EBUSY if
-		this device is bound to a driver. This is a "devsec" TSM
-		attribute, see Documentation/ABI/testing/sysfs-class-tsm.
 
-What:		/sys/bus/pci/devices/.../tsm/accept
-Contact:	linux-coco@lists.linux.dev
-Description:
-		(RW) Write "1" (or any boolean "true" string) to this file to
-		request that TSM transition the device from the TDISP LOCKED
-		state to the RUN state and arrange the for the secure IOMMU to
-		accept requests with T=1 in the PCIe packet header (TLP)
-		targeting private memory. Per TDISP the only exits from the RUN
-		state are via an explicit unlock request or an event that
-		transitions the device to the ERROR state. Writes fail with
-		EBUSY if this device is bound to a driver. This is a "devsec"
-		TSM attribute, see Documentation/ABI/testing/sysfs-class-tsm.
diff --git a/Documentation/ABI/testing/sysfs-class-tsm b/Documentation/ABI/testing/sysfs-class-tsm
index 76704501f082..854efd3f1b28 100644
--- a/Documentation/ABI/testing/sysfs-class-tsm
+++ b/Documentation/ABI/testing/sysfs-class-tsm
@@ -35,7 +35,8 @@ Description:
 			      transition assigned devices through the TDISP
 			      state machine UNLOCKED->LOCKED->RUN.
 
-		See the "tsm/" entries in
-		Documentation/ABI/testing/sysfs-bus-pci for the available PCI
-		device attributes when a TSM with the given "pci_mode" is
-		registered.
+		Host connection control uses the PCI device's tsm/connect and
+		tsm/disconnect sysfs files. Guest lifecycle operations use
+		ctlv-attestation-pci. The numeric suffix of tsmN is its
+		pci.tsm-id. See
+		Documentation/driver-api/pci/tsm.rst.
diff --git a/Documentation/driver-api/pci/tsm.rst b/Documentation/driver-api/pci/tsm.rst
index f20ec028935c..28c9ec5dd4e6 100644
--- a/Documentation/driver-api/pci/tsm.rst
+++ b/Documentation/driver-api/pci/tsm.rst
@@ -19,19 +19,29 @@ of an adversary. The adversarial threats are:
 - Virtual: Device MMIO presented to a guest may not actually map the
   device, device DMA may be redirected.
 
-In Linux a "tsm" is a broader concept. It is a class device interface to
-mitigate one or more of the above threats. A "tsm driver" registers a
-tsm device that publishes either the 'tsm/connect' or
-'tsm/{lock,accept}' set of attributes for the PCIe device. The typical
-expectation is that 'tsm/{lock,accept}' is published by a guest "tsm
-driver" to mitigate "Virtual" threats. The 'tsm/connect' interface is
-published by a host "tsm driver" to mitigate "Identity" and/or
-"Physical" threats.
+A TSM driver registers a class device under /sys/class/tsm. Its pci_mode
+identifies a host link manager or a guest device-security manager. PCI
+control uses the ``tsm/connect``, ``tsm/disconnect``, ``tsm/lock`` and
+``tsm/unlock`` sysfs files. Evidence uses a ``ctlv-attestation-pci`` instance,
+which inherits the common ``ctlv-attestation`` family. Only a successfully
+connected host DSM or locked guest device has a CTLV instance. Discovery and
+backend registration alone do not create instances.
+
+Operation availability
+======================
+
+CTLV provides DEVICE_GET, PCI STATE_GET, EVIDENCE_GET, EVIDENCE_ACCEPT,
+EVIDENCE_ACCEPT_CLEAR and DEVSEC_STATE_GET on established contexts. Backends
+declare the evidence objects and certificate slots they expose. The common
+state query omits TDISP state for a link context. PCI exposes no
+measurement-register operations.
+
+Guest EVIDENCE_REFRESH requires a locked context and a backend refresh
+callback. It is disabled after acceptance has entered RUN.
 
 Device Interface LOCK
 =====================
-The lock operation facilitated by tsm/lock (see
-Documentation/ABI/testing/sysfs-bus-pci) places the device in a mode
+Writing a guest TSM name to ``tsm/lock`` places the device in a mode
 where any security sensitive changes to the device configuration results
 in the device transitioning to the ERROR state. The device presents
 signed evidence of its LOCK state to the kernel through the tsm driver.
@@ -49,6 +59,80 @@ the TCB when locked, depending on the TSM architecture implementation,
 T=0 DMA from the device may be blocked until the device is next
 unlocked.
 
+Userspace sequence
+==================
+
+After sysfs connect or lock succeeds, discover the new instance through CTLV
+and use DEVICE_GET's ``pci.name`` to match the PCI BDF. DEVICE_GET exposes common
+evidence capabilities; PCI STATE_GET returns the current generation,
+connection/lock/acceptance state, selected TSM ID and, for a connected link, the
+DSM BDF. The TSM ID is the numeric suffix of /sys/class/tsm/tsmN. PCI
+EVIDENCE_REFRESH requires ``pci.generation`` from STATE_GET; a mismatch returns
+ESTALE before invoking the native backend.
+
+For a link backend that defers secure-link setup until evidence acceptance,
+write its TSM name to the DSM's ``tsm/connect`` sysfs file to collect identity
+evidence. DEVICE_GET advertises the backend's available objects and certificate
+slots; EVIDENCE_GET retrieves the cached chain. After verifying it, issue
+EVIDENCE_ACCEPT with the returned device and evidence generations to complete
+session and IDE setup. Writing the same TSM name to ``tsm/disconnect`` releases
+the context, including an identity context that has not been accepted.
+
+A guest verifier follows this sequence:
+
+1. Unbind the device driver and write the guest TSM name to ``tsm/lock``.
+   Successful locking collects and verifies the initial evidence, then publishes
+   the CTLV device.
+2. Optionally refresh evidence if the backend supports EVIDENCE_REFRESH.
+3. Use common EVIDENCE_GET to export objects from the current snapshot,
+   then verify identity, transcript, nonce, measurements and interface
+   report under the relying party's policy.
+4. Issue EVIDENCE_ACCEPT with the device and evidence generations returned by
+   EVIDENCE_GET. This invokes native ACCEPT and driver-core confidential-device
+   acceptance. The verdict is recorded only when both succeed. Acceptance keeps
+   the evidence generation binding and updates the verdict/state query version.
+   Repeated acceptance, including after EVIDENCE_ACCEPT_CLEAR, does not repeat
+   the native RUN transition. CLEAR only clears the userspace verdict.
+5. After unbinding the driver, write the TSM name to ``tsm/unlock`` to revoke
+   acceptance and tear down native mappings. Successful unlock removes the CTLV
+   instance. Another lock creates a new instance.
+
+Backends declare the evidence objects they provide in
+``pci_tsm_ops.evidence_types``, using bits from ``PCI_TSM_EVIDENCE_TYPE_*``.
+DEVICE_GET advertises only these objects and EVIDENCE_GET rejects other objects.
+The CCA guest and synthetic guest backend declare slot zero, VCA, measurements
+and interface reports. Host certificate slots are declared by the link backend.
+
+Host EVIDENCE_ACCEPT may select any advertised certificate slot through
+``evidence.accept-object``. The selected object must belong to the retrieved
+evidence generation. The provider passes the slot to the link backend through
+``pci_tsm.evidence.slot``; omitting the selector retains the existing slot.
+Once streams are connected, selecting a different slot returns EBUSY.
+
+Raw evidence and trusted digests are exported by the common blob API:
+
+================= ======================== ===================
+Native object     Common blob class        Format
+================= ======================== ===================
+cert0..cert7       cert-chain (instance N)  spdm-cert-chain
+VCA               spdm-vca                 spdm-vca
+measurements      spdm-measurements        spdm-measurements
+interface report  tdisp-report             tdisp-report
+================= ======================== ===================
+
+Use RAW for the wire bytes or TCB_DIGEST with the native digest algorithm
+(SHA-256, SHA-384 or SHA-512). Capabilities describe potential outputs;
+ENODATA means that the current TSM did not populate that object or
+representation, and EOPNOTSUPP means the selected digest algorithm does
+not match the native one. Provision output buffers using the advertised maximum
+length. The native cache remains private to PCI/TSM for MMIO report parsing.
+
+Refresh invalidates older evidence and verdicts. A backend refresh failure
+also invalidates them and requires recovery. Invalid object or challenge
+requests leave the context unchanged.
+
+Read-only ``tsm/dsm`` and ``authenticated`` sysfs status remain.
+
 Subsystem Interfaces
 ====================
 
diff --git a/Documentation/netlink/specs/pci-tsm.yaml b/Documentation/netlink/specs/pci-tsm.yaml
deleted file mode 100644
index eb7fc03bd705..000000000000
--- a/Documentation/netlink/specs/pci-tsm.yaml
+++ /dev/null
@@ -1,151 +0,0 @@
-# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
-#
----
-name: pci-tsm
-protocol: genetlink
-uapi-header: linux/pci-tsm-netlink.h
-doc: PCI TSM Evidence retrieval over generic netlink
-
-definitions:
-  -
-    type: const
-    name: max-object-size
-    value: 0x01000000
-  -
-    type: const
-    name: max-nonce-size
-    value: 256
-  -
-    type: const
-    name: max-obj-type
-    value: 4
-  -
-    name: evidence-type
-    type: enum
-    doc: PCI device security evidence objects
-    entries:
-      -
-        name: cert0
-        doc: SPDM certificate chain from device slot0
-      -
-        name: cert1
-        doc: SPDM certificate chain from device slot1
-      -
-        name: cert2
-        doc: SPDM certificate chain from device slot2
-      -
-        name: cert3
-        doc: SPDM certificate chain from device slot3
-      -
-        name: cert4
-        doc: SPDM certificate chain from device slot4
-      -
-        name: cert5
-        doc: SPDM certificate chain from device slot5
-      -
-        name: cert6
-        doc: SPDM certificate chain from device slot6
-      -
-        name: cert7
-        doc: SPDM certificate chain from device slot7
-      -
-        name: vca
-        doc: SPDM transcript of version, capabilities, and algorithms negotiation
-      -
-        name: measurements
-        doc: SPDM GET_MEASUREMENTS response
-      -
-        name: report
-        doc: TDISP GET_DEVICE_INTERFACE_REPORT response
-
-    render-max: true
-  -
-    name: evidence-type-flag
-    type: flags
-    doc: PCI device security evidence request flags
-    render-max: true
-    # NOTE! these values must match the name and order of evidence-type
-    entries:
-      -
-        name: cert0
-      -
-        name: cert1
-      -
-        name: cert2
-      -
-        name: cert3
-      -
-        name: cert4
-      -
-        name: cert5
-      -
-        name: cert6
-      -
-        name: cert7
-      -
-        name: vca
-      -
-        name: measurements
-      -
-        name: report
-  -
-    name: evidence-flag
-    type: flags
-    render-max: true
-    doc: Flags to control evidence retrieval
-    entries:
-      -
-        name: digest
-        doc: Request the TSM's private digest of an evidence object
-
-attribute-sets:
-  -
-    name: evidence-object
-    attributes:
-      -
-        name: type
-        type: u32
-        doc: evidence type-id
-      -
-        name: type-mask
-        type: u32
-        doc: evidence types as a flag mask
-      -
-        name: flags
-        type: u32
-        doc: evidence modifier flags like 'request TSM digest'
-      -
-        name: dev-name
-        type: string
-        doc: PCI device name
-      -
-        name: nonce
-        type: binary
-        checks:
-          max-len: max-nonce-size
-      -
-        name: val
-        type: binary
-        checks:
-          max-len: max-obj-size
-
-operations:
-  list:
-    -
-      name: evidence-read
-      doc: Read the PCI TSM objects of a given type mask
-      attribute-set: evidence-object
-      flags: [admin-perm]
-      dump:
-        pre: pci-tsm-nl-evidence-read-pre
-        post: pci-tsm-nl-evidence-read-post
-        request:
-          attributes:
-            - type-mask
-            - flags
-            - dev-name
-            - nonce
-        reply:
-          attributes:
-            - type
-            - val
diff --git a/drivers/crypto/ccp/sev-dev-tsm.c b/drivers/crypto/ccp/sev-dev-tsm.c
index 6d089714900c..e905ed9de9b5 100644
--- a/drivers/crypto/ccp/sev-dev-tsm.c
+++ b/drivers/crypto/ccp/sev-dev-tsm.c
@@ -307,6 +307,7 @@ static int dsm_connect(struct pci_dev *pdev)
 
 	tc_mask = streams_setup(dev_data->ide, ids);
 
+	dev_data->cert_slot = pdev->tsm->evidence.slot;
 	ret = sev_tio_dev_connect(dev_data, tc_mask, ids, dev_data->cert_slot);
 	ret = sev_tio_spdm_cmd(dsm, ret);
 	if (ret)
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index f7b7a6e269a2..b605eb2d4d5e 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -131,6 +131,9 @@ config PCI_TSM
 	select PCI_IDE
 	select PCI_DOE
 	select TSM
+	select CTLV
+	select ATTESTATION
+	select CRYPTO_HASH_INFO
 	help
 	  The TEE (Trusted Execution Environment) Device Interface
 	  Security Protocol (TDISP) defines a "TSM" as a platform agent
diff --git a/drivers/pci/tsm/Makefile b/drivers/pci/tsm/Makefile
index afa775224b8d..62181b8ca635 100644
--- a/drivers/pci/tsm/Makefile
+++ b/drivers/pci/tsm/Makefile
@@ -4,6 +4,6 @@
 
 obj-$(CONFIG_PCI_TSM) += tsm.o
 
-tsm-y := core.o
-tsm-$(CONFIG_NET) += netlink.o
-tsm-$(CONFIG_NET) += evidence.o
+ccflags-y += -I$(srctree)/drivers/attestation/generated
+
+tsm-y := core.o attestation.o ctlv_attestation_pci_gen.o
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;
+
+	pci_tsm_attestation_notify_state_change(pa->pdev);
+	return ret ?: pci_attestation_reply(pa, reply);
+}
+
+int ctlv_attestation_pci_state_get_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_dev *pdev = pa->pdev;
+	struct pci_tsm *tsm = pdev->tsm;
+	bool accepted = device_cc_accepted(&pdev->dev);
+	bool ok = !pci_attestation_reply(pa, reply);
+
+	if (tsm && tsm->dsm_dev == pdev) {
+		struct pci_tsm_pf0 *pf0 = container_of(tsm, struct pci_tsm_pf0, base_tsm);
+
+		accepted = pf0->stream_connected;
+	}
+
+	ok &= ctlv_attestation_pci_pci_connected_put(reply, tsm && tsm->dsm_dev);
+	ok &= ctlv_attestation_pci_pci_locked_put(reply, tsm && !tsm->dsm_dev);
+	ok &= ctlv_attestation_pci_pci_accepted_put(reply, accepted);
+
+	if (tsm) {
+		ok &= ctlv_attestation_pci_pci_tsm_id_put(reply, tsm->tsm_dev->id);
+		if (tsm->dsm_dev)
+			ok &= ctlv_attestation_pci_pci_dsm_put(reply, pci_name(tsm->dsm_dev));
+	}
+
+	return ok ? 0 : -EMSGSIZE;
+}
+
+static const enum attestation_evidence_representation pci_representations[] = {
+	ATTESTATION_EVIDENCE_REPRESENTATION_RAW,
+	ATTESTATION_EVIDENCE_REPRESENTATION_TCB_DIGEST,
+};
+
+static const enum hash_algo pci_algorithms[] = {
+	HASH_ALGO_SHA256, HASH_ALGO_SHA384, HASH_ALGO_SHA512,
+};
+
+/* These describe potential outputs; availability depends on the native TSM. */
+#define PCI_OBJECT(_class, _instance, _slot, _format) { \
+	.blob_class = ATTESTATION_EVIDENCE_BLOB_##_class, \
+	.has_blob_instance = _instance, .blob_instance = _slot, \
+	.format = _format, .representations = pci_representations, \
+	.representations_count = ARRAY_SIZE(pci_representations), \
+	.hash_algorithms = pci_algorithms, \
+	.hash_algorithms_count = ARRAY_SIZE(pci_algorithms), \
+	.max_length = PCI_TSM_MAX_OBJECT_SIZE, \
+}
+
+static const struct attestation_evidence_supported_object pci_objects[] = {
+	PCI_OBJECT(CERT_CHAIN, true, 0, "spdm-cert-chain"),
+	PCI_OBJECT(CERT_CHAIN, true, 1, "spdm-cert-chain"),
+	PCI_OBJECT(CERT_CHAIN, true, 2, "spdm-cert-chain"),
+	PCI_OBJECT(CERT_CHAIN, true, 3, "spdm-cert-chain"),
+	PCI_OBJECT(CERT_CHAIN, true, 4, "spdm-cert-chain"),
+	PCI_OBJECT(CERT_CHAIN, true, 5, "spdm-cert-chain"),
+	PCI_OBJECT(CERT_CHAIN, true, 6, "spdm-cert-chain"),
+	PCI_OBJECT(CERT_CHAIN, true, 7, "spdm-cert-chain"),
+	PCI_OBJECT(SPDM_VCA, false, 0, "spdm-vca"),
+	PCI_OBJECT(SPDM_MEASUREMENTS, false, 0, "spdm-measurements"),
+	PCI_OBJECT(TDISP_REPORT, false, 0, "tdisp-report"),
+};
+
+static_assert(ARRAY_SIZE(pci_objects) == PCI_TSM_EVIDENCE_TYPE_MAX + 1);
+
+static const struct attestation_evidence_caps pci_evidence_caps = {
+	.supports_object_accept = true,
+	.objects = pci_objects,
+	.objects_count = ARRAY_SIZE(pci_objects),
+	.max_total_output_length = PCI_TSM_MAX_OBJECT_SIZE * ARRAY_SIZE(pci_objects) * 2,
+};
+
+static bool pci_evidence_object_supported(struct attestation *att,
+		const struct attestation_evidence_supported_object *object)
+{
+	struct pci_tsm_attestation *pa = ctlv_priv_child(att);
+	struct pci_tsm *tsm = pa->pdev->tsm;
+
+	if (!tsm)
+		return false;
+
+	return tsm->tsm_dev->pci_ops->evidence_types & BIT(object - pci_objects);
+}
+
+static int pci_evidence_get(struct attestation *att,
+		const struct attestation_evidence_request *request,
+		struct attestation_evidence_result *result)
+{
+	struct pci_tsm_attestation *pa = ctlv_priv_child(att);
+	struct pci_tsm *tsm = pa->pdev->tsm;
+	struct pci_tsm_evidence *evidence;
+	size_t i;
+	int ret;
+
+	if (!tsm)
+		return -ENOTCONN;
+
+	if (tsm->error)
+		return -EIO;
+
+	evidence = &tsm->evidence;
+	guard(rwsem_read)(&evidence->lock);
+	/* Validate the entire set before writing any of its outputs. */
+	for (i = 0; i < request->objects_count; i++) {
+		const struct attestation_evidence_object_request *req = &request->objects[i];
+		unsigned int type = req->supported_object - pci_objects;
+		struct pci_tsm_evidence_object *obj = &evidence->obj[type];
+
+		if (req->representation == ATTESTATION_EVIDENCE_REPRESENTATION_RAW) {
+			if (!obj->data || !obj->len)
+				return -ENODATA;
+
+			result->object_lengths[i] = obj->len;
+		} else {
+			if (!obj->digest)
+				return -ENODATA;
+
+			if (req->hash_algorithm != evidence->digest_algo)
+				return -EOPNOTSUPP;
+
+			result->object_lengths[i] = hash_digest_size[evidence->digest_algo];
+		}
+
+		if (result->object_lengths[i] > req->supported_object->max_length ||
+		    result->object_lengths[i] > ctlv_blob_len(req->output))
+			return -EMSGSIZE;
+	}
+
+	for (i = 0; i < request->objects_count; i++) {
+		const struct attestation_evidence_object_request *req = &request->objects[i];
+		unsigned int type = req->supported_object - pci_objects;
+		struct pci_tsm_evidence_object *obj = &evidence->obj[type];
+		const void *data = obj->digest;
+
+		if (req->representation == ATTESTATION_EVIDENCE_REPRESENTATION_RAW)
+			data = obj->data;
+
+		ret = ctlv_blob_write(req->output, 0, data, result->object_lengths[i]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int pci_evidence_accept(struct attestation *att,
+		const struct attestation_evidence_supported_object *object)
+{
+	struct pci_tsm_attestation *pa = ctlv_priv_child(att);
+	struct pci_tsm *tsm = pa->pdev->tsm;
+
+	if (!tsm)
+		return -ENOTCONN;
+
+	if (tsm->error)
+		return -EIO;
+
+	if (tsm->dsm_dev) {
+		if (!is_pci_tsm_pf0(pa->pdev))
+			return -EOPNOTSUPP;
+
+		if (!object)
+			return pci_tsm_evidence_accept(pa->pdev, tsm->evidence.slot);
+
+		if (object->blob_class != ATTESTATION_EVIDENCE_BLOB_CERT_CHAIN ||
+		    !object->has_blob_instance || object->blob_instance >= 8)
+			return -EINVAL;
+
+		return pci_tsm_evidence_accept(pa->pdev, object->blob_instance);
+	}
+
+	/* Clearing and restoring a verdict must not issue a second RUN. */
+	if (device_cc_accepted(&pa->pdev->dev))
+		return 0;
+
+	return pci_tsm_accept(pa->pdev);
+}
+
+static void pci_evidence_accept_clear(struct attestation *att)
+{
+	/* The common core owns the verdict and its generation binding. */
+}
+
+static int pci_tdisp_state_get(struct attestation *att,
+		enum attestation_tdisp_state *state)
+{
+	struct pci_tsm_attestation *pa = ctlv_priv_child(att);
+	struct pci_tsm *tsm = pa->pdev->tsm;
+
+	if (tsm && tsm->dsm_dev)
+		return -EOPNOTSUPP;
+
+	if (!tsm)
+		*state = ATTESTATION_TDISP_STATE_UNLOCKED;
+	else if (tsm->error)
+		*state = ATTESTATION_TDISP_STATE_ERROR;
+	else if (device_cc_accepted(&pa->pdev->dev))
+		*state = ATTESTATION_TDISP_STATE_RUN;
+	else
+		*state = ATTESTATION_TDISP_STATE_LOCKED;
+
+	return 0;
+}
+
+static const struct attestation_provider pci_attestation_provider = {
+	.evidence_caps = &pci_evidence_caps,
+	.evidence_object_supported = pci_evidence_object_supported,
+	.evidence_get = pci_evidence_get,
+	.evidence_accept = pci_evidence_accept,
+	.evidence_accept_clear = pci_evidence_accept_clear,
+	.tdisp_state_get = pci_tdisp_state_get,
+};
+
+size_t ctlv_attestation_pci_priv_size(void)
+{
+	return sizeof(struct pci_tsm_attestation);
+}
+
+const void *ctlv_attestation_pci_pre_init(const void *init_info)
+{
+	return &pci_attestation_provider;
+}
+
+struct pci_attestation_init {
+	struct pci_dev *pdev;
+	struct pci_tsm_attestation **result;
+};
+
+int ctlv_attestation_pci_init(struct ctlv_device *ctlvdev,
+		const void *init_info, void *priv)
+{
+	struct pci_tsm_attestation *pa = priv;
+	const struct pci_attestation_init *info = init_info;
+	struct pci_dev *pdev = info->pdev;
+
+	pa->pdev = pci_dev_get(pdev);
+	pa->ctlvdev = ctlvdev;
+
+	if (!pdev->tsm->dsm_dev)
+		ctlv_attestation_pci_evidence_refresh_provide(ctlvdev);
+
+	lockdep_assert_held_write(&pci_tsm_rwsem);
+	*info->result = pa;
+	pci_attestation_update_locked(pa, false);
+	return 0;
+}
+
+void ctlv_attestation_pci_fini(struct ctlv_device *ctlvdev, void *priv)
+{
+	struct pci_tsm_attestation *pa = priv;
+
+	if (pa->attached) {
+		guard(rwsem_write)(&pci_tsm_rwsem);
+		pa->pdev->tsm_attestation = NULL;
+	}
+	pci_dev_put(pa->pdev);
+}
+
+int ctlv_attestation_pci_device_info_fill(void *priv, struct ctlv_op_ctx *ctx,
+		const struct ctlv_cursor *request, struct ctlv_cursor *nest)
+{
+	struct pci_tsm_attestation *pa = priv;
+
+	return ctlv_attestation_pci_pci_name_put(nest, pci_name(pa->pdev)) ? 0 : -EMSGSIZE;
+}
+
+void ctlv_attestation_pci_device_info_read(void *priv, struct ctlv_op_ctx *ctx,
+		const struct ctlv_cursor *nest)
+{
+}
+
+int pci_tsm_attestation_add(struct pci_dev *pdev)
+{
+	struct pci_tsm_attestation *pa;
+	struct pci_attestation_init info = { .pdev = pdev, .result = &pa };
+	struct ctlv_device *ctlvdev;
+
+	lockdep_assert_held_write(&pci_tsm_rwsem);
+	ctlvdev = ctlv_create(&pdev->dev, &ctlv_attestation_pci_family, &info);
+	if (IS_ERR(ctlvdev))
+		return PTR_ERR(ctlvdev);
+
+	pa->attached = true;
+	pdev->tsm_attestation = pa;
+	return 0;
+}
+
+MODULE_IMPORT_NS("ATTESTATION");
+MODULE_IMPORT_NS("CTLV");
diff --git a/drivers/pci/tsm/core.c b/drivers/pci/tsm/core.c
index c74b86dbf86b..170e043b1d3b 100644
--- a/drivers/pci/tsm/core.c
+++ b/drivers/pci/tsm/core.c
@@ -24,6 +24,9 @@
 #include <linux/unaligned.h>
 #include <linux/xarray.h>
 #include "../pci.h"
+#include "internal.h"
+
+DEFINE_FREE(pci_tsm_attestation_destroy, struct list_head *, pci_tsm_attestation_destroy(_T))
 
 /*
  * Provide a read/write lock against the init / exit of pdev tsm
@@ -225,8 +228,10 @@ static int probe_fn(struct pci_dev *pdev, void *dsm)
 	pdev->tsm = ops->probe(dsm_dev->tsm->tsm_dev, pdev);
 	pci_dbg(pdev, "setup TSM context: DSM: %s status: %s\n",
 		pci_name(dsm_dev), pdev->tsm ? "success" : "failed");
-	if (pdev->tsm)
+	if (pdev->tsm) {
+		pci_tsm_attestation_notify_state_change(pdev);
 		link_sysfs_enable(pdev);
+	}
 	return 0;
 }
 
@@ -251,10 +256,12 @@ static int pci_tsm_connect(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
 	if ((rc = ACQUIRE_ERR(mutex_intr, &lock)))
 		return rc;
 
+	pci_tsm->evidence.slot = 0;
 	rc = ops->connect(pdev);
 	if (rc)
 		return rc;
 
+	tsm_pf0->stream_connected = !ops->evidence_accept;
 	pdev->tsm = no_free_ptr(pci_tsm);
 
 	/*
@@ -285,7 +292,7 @@ static ssize_t connect_show(struct device *dev, struct device_attribute *attr,
 	if ((rc = ACQUIRE_ERR(rwsem_read_intr, &lock)))
 		return rc;
 
-	if (!pdev->tsm)
+	if (!pdev->tsm || !pdev->tsm->dsm_dev)
 		return sysfs_emit(buf, "\n");
 
 	tsm_dev = pdev->tsm->tsm_dev;
@@ -304,35 +311,77 @@ static bool is_devsec_tsm(struct tsm_dev *tsm_dev)
 	return tsm_dev && tsm_dev->pci_ops && tsm_dev->pci_ops->devsec_ops.lock;
 }
 
+static bool pci_tsm_can_connect(struct tsm_dev *tsm_dev)
+{
+	const struct pci_tsm_ops *ops = tsm_dev ? tsm_dev->pci_ops : NULL;
+
+	return is_link_tsm(tsm_dev) && ops->remove && ops->connect && ops->disconnect;
+}
+
 static ssize_t connect_store(struct device *dev, struct device_attribute *attr,
 			     const char *buf, size_t len)
 {
 	struct pci_dev *pdev = to_pci_dev(dev);
 	int rc, id;
 
-	rc = sscanf(buf, "tsm%d\n", &id);
-	if (rc != 1)
+	if (sscanf(buf, "tsm%d", &id) != 1 || id < 0)
 		return -EINVAL;
 
 	ACQUIRE(rwsem_write_kill, lock)(&pci_tsm_rwsem);
-	if ((rc = ACQUIRE_ERR(rwsem_write_kill, &lock)))
+	rc = ACQUIRE_ERR(rwsem_write_kill, &lock);
+	if (rc)
 		return rc;
-
-	if (pdev->tsm)
+	if (pci_dev_is_disconnected(pdev))
+		return -ENODEV;
+	if (pdev->tsm || pdev->tsm_attestation_removing)
 		return -EBUSY;
 
 	struct tsm_dev *tsm_dev __free(put_tsm_dev) = find_tsm_dev(id);
-	if (!is_link_tsm(tsm_dev))
+	if (!pci_tsm_can_connect(tsm_dev))
 		return -ENXIO;
-
+	if (!sysfs_streq(buf, dev_name(&tsm_dev->dev)))
+		return -EINVAL;
 	rc = pci_tsm_connect(pdev, tsm_dev);
 	if (rc)
 		return rc;
-
-	return len;
+	rc = pci_tsm_attestation_add(pdev);
+	if (rc)
+		pci_tsm_disconnect(pdev);
+	return rc ? rc : len;
 }
 static DEVICE_ATTR_RW(connect);
 
+int pci_tsm_evidence_accept(struct pci_dev *pdev, unsigned int slot)
+{
+	struct pci_tsm_pf0 *pf0 = to_pci_tsm_pf0(pdev->tsm);
+	const struct pci_tsm_ops *ops = to_pci_tsm_ops(pdev->tsm);
+	int ret;
+
+	lockdep_assert_held_write(&pci_tsm_rwsem);
+	if (pf0->removing)
+		return -ENODEV;
+
+	if (pf0->stream_connected)
+		return pdev->tsm->evidence.slot == slot ? 0 : -EBUSY;
+
+	if (slot >= 8)
+		return -EINVAL;
+
+	pdev->tsm->evidence.slot = slot;
+	ret = ops->evidence_accept(pdev);
+	if (!ret)
+		pf0->stream_connected = true;
+	else if (pdev->tsm->error)
+		pci_tsm_attestation_notify_state_change(pdev);
+
+	return ret;
+}
+
+bool pci_tsm_can_lock(struct tsm_dev *tsm_dev)
+{
+	return is_devsec_tsm(tsm_dev) && tsm_dev->pci_ops->unlock;
+}
+
 static int remove_fn(struct pci_dev *pdev, void *data)
 {
 	tsm_remove(pdev->tsm);
@@ -384,8 +433,15 @@ struct pci_tsm_context *pci_tsm_context_get(struct pci_dev *pdev,
 		kfree(context);
 		return ERR_PTR(-ENODEV);
 	}
+
+	if (pf0->base_tsm.error || !pf0->stream_connected) {
+		kfree(context);
+		return ERR_PTR(-ENOTCONN);
+	}
+
 	if (!pf0->context_users)
 		reinit_completion(&pf0->contexts_drained);
+
 	pf0->context_users++;
 	context->pf0 = pf0;
 	context->pdev = pci_dev_get(pdev);
@@ -459,7 +515,7 @@ static int __pci_tsm_disconnect(struct pci_dev *pdev)
 	return 0;
 }
 
-static int pci_tsm_disconnect(struct pci_dev *pdev)
+int pci_tsm_disconnect(struct pci_dev *pdev)
 {
 	int ret = __pci_tsm_disconnect(pdev);
 
@@ -474,27 +530,29 @@ static ssize_t disconnect_store(struct device *dev,
 				size_t len)
 {
 	struct pci_dev *pdev = to_pci_dev(dev);
-	struct tsm_dev *tsm_dev;
+	LIST_HEAD(retired);
+	struct list_head *cleanup __free(pci_tsm_attestation_destroy) = &retired;
 	int rc;
 
 	ACQUIRE(rwsem_write_kill, lock)(&pci_tsm_rwsem);
-	if ((rc = ACQUIRE_ERR(rwsem_write_kill, &lock)))
+	rc = ACQUIRE_ERR(rwsem_write_kill, &lock);
+	if (rc)
 		return rc;
 
-	if (!pdev->tsm)
+	if (!pdev->tsm || !pdev->tsm->dsm_dev)
 		return -ENXIO;
 
-	tsm_dev = pdev->tsm->tsm_dev;
-	if (!sysfs_streq(buf, dev_name(&tsm_dev->dev)))
+	if (!sysfs_streq(buf, dev_name(&pdev->tsm->tsm_dev->dev)))
 		return -EINVAL;
-	if (is_link_tsm(tsm_dev) && is_pci_tsm_pf0(pdev) &&
-	    to_pci_tsm_pf0(pdev->tsm)->removing)
+
+	if (to_pci_tsm_pf0(pdev->tsm)->removing)
 		return -ENODEV;
 
 	rc = pci_tsm_disconnect(pdev);
-	if (rc)
-		return rc;
-	return len;
+	if (!rc)
+		pci_tsm_attestation_remove(pdev, &retired);
+
+	return rc ? rc : len;
 }
 static DEVICE_ATTR_WO(disconnect);
 
@@ -510,7 +568,7 @@ static ssize_t dsm_show(struct device *dev, struct device_attribute *attr,
 		return rc;
 
 	tsm = pdev->tsm;
-	if (!tsm)
+	if (!tsm || !tsm->dsm_dev)
 		return sysfs_emit(buf, "\n");
 
 	return sysfs_emit(buf, "%s\n", pci_name(tsm->dsm_dev));
@@ -752,85 +810,67 @@ EXPORT_SYMBOL_GPL(pci_tsm_mmio_free);
  * that transition from a known DMA-idle (no active mappings) state. The "driver
  * detached" state is a coarse way to assert that requirement.
  */
-static int pci_tsm_accept(struct pci_dev *pdev)
+int pci_tsm_accept(struct pci_dev *pdev)
 {
 	int rc;
 
-	ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
-	if ((rc = ACQUIRE_ERR(rwsem_read_intr, &lock)))
-		return rc;
+	lockdep_assert_held_write(&pci_tsm_rwsem);
 
-	if (!pdev->tsm)
+	if (!is_devsec(pdev) || !to_pci_tsm_ops(pdev->tsm)->accept)
 		return -EINVAL;
 
 	ACQUIRE(device_intr, dev_lock)(&pdev->dev);
 	if ((rc = ACQUIRE_ERR(device_intr, &dev_lock)))
 		return rc;
 
-	if (pdev->dev.driver)
+	if (pdev->dev.driver || pdev->tsm->error ||
+	    device_cc_accepted(&pdev->dev))
 		return -EBUSY;
 
 	rc = to_pci_tsm_ops(pdev->tsm)->accept(pdev);
-	if (rc)
-		return rc;
-
-	return device_cc_accept(&pdev->dev);
-}
-
-static ssize_t accept_store(struct device *dev, struct device_attribute *attr,
-			    const char *buf, size_t len)
-{
-	struct pci_dev *pdev = to_pci_dev(dev);
-	bool accept;
-	int rc;
-
-	rc = kstrtobool(buf, &accept);
-	if (rc)
-		return rc;
-
-	/*
-	 * TDISP can only go from RUN to UNLOCKED/ERROR, so there is no
-	 * 'unaccept' verb.
-	 */
-	if (!accept)
-		return -EINVAL;
-
-	rc = pci_tsm_accept(pdev);
-	if (rc)
-		return rc;
-
-	return len;
+	if (!rc)
+		rc = device_cc_accept(&pdev->dev);
+	if (rc) {
+		pdev->tsm->error = true;
+		pci_tsm_attestation_notify_state_change(pdev);
+	}
+	return rc;
 }
 
-static ssize_t accept_show(struct device *dev, struct device_attribute *attr,
-			   char *buf)
+/* The backend retains its context when hardware teardown fails. */
+int pci_tsm_unlock(struct pci_dev *pdev)
 {
-	return sysfs_emit(buf, "%d\n", device_cc_accepted(dev));
-}
-static DEVICE_ATTR_RW(accept);
+	const struct pci_tsm_ops *ops;
+	struct tsm_dev *tsm_dev;
+	int ret;
 
-/**
- * pci_tsm_unlock() - Transition TDI from LOCKED/RUN to UNLOCKED
- * @pdev: TDI device to unlock
- *
- * Returns void, requires all callers to have satisfied dependencies like making
- * sure the device is locked and detached from its driver.
- */
-static void pci_tsm_unlock(struct pci_dev *pdev)
-{
 	lockdep_assert_held_write(&pci_tsm_rwsem);
 	device_lock_assert(&pdev->dev);
+	if (!is_devsec(pdev))
+		return -EINVAL;
 
-	if (dev_WARN_ONCE(&pdev->dev, pdev->dev.driver,
-			  "unlock attempted on driver attached device\n"))
-		return;
+	if (pdev->dev.driver)
+		return -EBUSY;
 
+	tsm_dev = pdev->tsm->tsm_dev;
+	ops = tsm_dev->pci_ops;
 	device_cc_reject(&pdev->dev);
-	to_pci_tsm_ops(pdev->tsm)->unlock(pdev->tsm);
-	pdev->tsm = NULL;
+	ret = ops->unlock(pdev->tsm);
+	if (ret)
+		pdev->tsm->error = true;
+	else
+		pdev->tsm = NULL;
+	if (ret)
+		pci_tsm_attestation_notify_state_change(pdev);
+	if (!ret) {
+		put_device(&tsm_dev->dev);
+		module_put(ops->owner);
+		pci_dev_put(pdev);
+	}
+	return ret;
 }
 
-static int pci_tsm_lock(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
+int pci_tsm_lock(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
 {
 	const struct pci_tsm_ops *ops = tsm_dev->pci_ops;
 	struct pci_tsm *tsm;
@@ -845,88 +885,101 @@ static int pci_tsm_lock(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
 	if (pdev->dev.driver)
 		return -EBUSY;
 
+	if (!try_module_get(ops->owner))
+		return -ENODEV;
+
 	tsm = ops->lock(tsm_dev, pdev);
-	if (IS_ERR(tsm))
+	if (IS_ERR(tsm)) {
+		module_put(ops->owner);
 		return PTR_ERR(tsm);
+	}
+	pci_dev_get(pdev);
+	get_device(&tsm_dev->dev);
 
 	pdev->tsm = tsm;
 	return 0;
 }
 
+static ssize_t lock_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct pci_dev *pdev = to_pci_dev(dev);
+
+	guard(rwsem_read)(&pci_tsm_rwsem);
+	if (!is_devsec(pdev))
+		return sysfs_emit(buf, "\n");
+
+	return sysfs_emit(buf, "%s\n", dev_name(&pdev->tsm->tsm_dev->dev));
+}
+
 static ssize_t lock_store(struct device *dev, struct device_attribute *attr,
 			  const char *buf, size_t len)
 {
 	struct pci_dev *pdev = to_pci_dev(dev);
-	int rc, id;
+	int ret, id;
 
-	rc = sscanf(buf, "tsm%d\n", &id);
-	if (rc != 1)
+	if (sscanf(buf, "tsm%d", &id) != 1 || id < 0)
 		return -EINVAL;
 
 	ACQUIRE(rwsem_write_kill, lock)(&pci_tsm_rwsem);
-	if ((rc = ACQUIRE_ERR(rwsem_write_kill, &lock)))
-		return rc;
+	ret = ACQUIRE_ERR(rwsem_write_kill, &lock);
+	if (ret)
+		return ret;
 
-	if (pdev->tsm)
+	if (pci_dev_is_disconnected(pdev))
+		return -ENODEV;
+
+	if (pdev->tsm || pdev->tsm_attestation_removing)
 		return -EBUSY;
 
 	struct tsm_dev *tsm_dev __free(put_tsm_dev) = find_tsm_dev(id);
-	if (!is_devsec_tsm(tsm_dev))
+	if (!pci_tsm_can_lock(tsm_dev))
 		return -ENXIO;
 
-	rc = pci_tsm_lock(pdev, tsm_dev);
-	if (rc)
-		return rc;
-	return len;
-}
-
-static ssize_t lock_show(struct device *dev, struct device_attribute *attr,
-			 char *buf)
-{
-	struct pci_dev *pdev = to_pci_dev(dev);
-	struct tsm_dev *tsm_dev;
-	int rc;
-
-	ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
-	if ((rc = ACQUIRE_ERR(rwsem_read_intr, &lock)))
-		return rc;
+	if (!sysfs_streq(buf, dev_name(&tsm_dev->dev)))
+		return -EINVAL;
 
-	if (!pdev->tsm)
-		return sysfs_emit(buf, "\n");
+	ret = pci_tsm_lock(pdev, tsm_dev);
+	if (ret)
+		return ret;
 
-	tsm_dev = pdev->tsm->tsm_dev;
-	return sysfs_emit(buf, "%s\n", dev_name(&tsm_dev->dev));
+	ret = pci_tsm_attestation_add(pdev);
+	if (ret) {
+		guard(device)(&pdev->dev);
+		if (pci_tsm_unlock(pdev))
+			pci_warn(pdev, "TSM unlock failed after CTLV registration failure\n");
+	}
+	return ret ? ret : len;
 }
 static DEVICE_ATTR_RW(lock);
 
 static ssize_t unlock_store(struct device *dev, struct device_attribute *attr,
-			  const char *buf, size_t len)
+			    const char *buf, size_t len)
 {
 	struct pci_dev *pdev = to_pci_dev(dev);
-	struct tsm_dev *tsm_dev;
-	int rc;
+	LIST_HEAD(retired);
+	struct list_head *cleanup __free(pci_tsm_attestation_destroy) = &retired;
+	int ret;
 
 	ACQUIRE(rwsem_write_kill, lock)(&pci_tsm_rwsem);
-	if ((rc = ACQUIRE_ERR(rwsem_write_kill, &lock)))
-		return rc;
-
-	if (!pdev->tsm)
-		return -EINVAL;
+	ret = ACQUIRE_ERR(rwsem_write_kill, &lock);
+	if (ret)
+		return ret;
 
-	tsm_dev = pdev->tsm->tsm_dev;
-	if (!sysfs_streq(buf, dev_name(&tsm_dev->dev)))
-		return -EINVAL;
+	if (pci_dev_is_disconnected(pdev))
+		return -ENODEV;
 
-	ACQUIRE(device_intr, dev_lock)(&pdev->dev);
-	if ((rc = ACQUIRE_ERR(device_intr, &dev_lock)))
-		return rc;
+	if (!is_devsec(pdev))
+		return -ENXIO;
 
-	if (pdev->dev.driver)
-		return -EBUSY;
+	if (!sysfs_streq(buf, dev_name(&pdev->tsm->tsm_dev->dev)))
+		return -EINVAL;
 
-	pci_tsm_unlock(pdev);
+	guard(device)(&pdev->dev);
+	ret = pci_tsm_unlock(pdev);
+	if (!ret)
+		pci_tsm_attestation_remove(pdev, &retired);
 
-	return len;
+	return ret ? ret : len;
 }
 static DEVICE_ATTR_WO(unlock);
 
@@ -948,85 +1001,68 @@ static bool pci_tsm_link_group_visible(struct kobject *kobj)
 	 * Show 'authenticated' and other attributes for the managed
 	 * sub-functions of a DSM.
 	 */
-	if (pdev->tsm)
+	if (pdev->tsm && pdev->tsm->dsm_dev)
 		return true;
 
 	return false;
 }
 DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(pci_tsm_link);
 
-static bool pci_tsm_devsec_group_visible(struct kobject *kobj)
+static umode_t pci_tsm_attr_visible(struct kobject *kobj,
+				  struct attribute *attr, int n)
 {
 	struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
 
-	return pci_tsm_devsec_count && has_tee(pdev);
-}
+	bool link = pci_tsm_link_group_visible(kobj);
+	bool devsec = pci_tsm_devsec_count && has_tee(pdev);
 
-/*
- * 'link' and 'devsec' TSMs share the same 'tsm/' sysfs group, so the TSM type
- * specific attributes need individual visibility checks.
- */
-static umode_t pci_tsm_attr_visible(struct kobject *kobj,
-				    struct attribute *attr, int n)
-{
-	if (pci_tsm_link_group_visible(kobj)) {
-		struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
+	if (n == 0 && !link && !devsec)
+		return SYSFS_GROUP_INVISIBLE;
 
-		if (attr == &dev_attr_dsm.attr) {
-			if (is_pci_tsm_pf0(pdev))
-				return attr->mode;
-			if (pdev->tsm && has_tee(pdev->tsm->dsm_dev))
-				return attr->mode;
-		}
+	if (attr == &dev_attr_connect.attr || attr == &dev_attr_disconnect.attr)
+		return link && is_pci_tsm_pf0(pdev) ? attr->mode : 0;
 
-		if (attr == &dev_attr_connect.attr ||
-		    attr == &dev_attr_disconnect.attr) {
-			if (is_pci_tsm_pf0(pdev))
-				return attr->mode;
-		}
-	}
-
-	if (pci_tsm_devsec_group_visible(kobj)) {
-		if (attr == &dev_attr_accept.attr ||
-		    attr == &dev_attr_lock.attr ||
-		    attr == &dev_attr_unlock.attr)
-			return attr->mode;
-	}
-
-	return 0;
-}
+	if (attr == &dev_attr_lock.attr || attr == &dev_attr_unlock.attr)
+		return devsec ? attr->mode : 0;
 
-static bool pci_tsm_group_visible(struct kobject *kobj)
-{
-	return pci_tsm_link_group_visible(kobj) ||
-	       pci_tsm_devsec_group_visible(kobj);
+	return link ? attr->mode : 0;
 }
-DEFINE_SYSFS_GROUP_VISIBLE(pci_tsm);
 
 static struct attribute *pci_tsm_attrs[] = {
 	&dev_attr_connect.attr,
 	&dev_attr_disconnect.attr,
-	&dev_attr_dsm.attr,
-	&dev_attr_accept.attr,
 	&dev_attr_lock.attr,
 	&dev_attr_unlock.attr,
+	&dev_attr_dsm.attr,
 	NULL
 };
 
 const struct attribute_group pci_tsm_attr_group = {
 	.name = "tsm",
 	.attrs = pci_tsm_attrs,
-	.is_visible = SYSFS_GROUP_VISIBLE(pci_tsm),
+	.is_visible = pci_tsm_attr_visible,
 };
 
 static ssize_t authenticated_show(struct device *dev,
 				  struct device_attribute *attr, char *buf)
 {
-	/*
-	 * When the SPDM session established via TSM the 'authenticated' state
-	 * of the device is identical to the connect state.
-	 */
-	return connect_show(dev, attr, buf);
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct pci_tsm_pf0 *pf0;
+	int rc;
+
+	ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
+	rc = ACQUIRE_ERR(rwsem_read_intr, &lock);
+	if (rc)
+		return rc;
+
+	if (!pdev->tsm || !pdev->tsm->dsm_dev)
+		return sysfs_emit(buf, "\n");
+
+	pf0 = to_pci_tsm_pf0(pdev->tsm);
+	if (!pf0 || !pf0->stream_connected || pf0->base_tsm.error)
+		return sysfs_emit(buf, "\n");
+
+	return sysfs_emit(buf, "%s\n", dev_name(&pdev->tsm->tsm_dev->dev));
 }
 static DEVICE_ATTR_RO(authenticated);
 
@@ -1127,6 +1163,7 @@ int pci_tsm_link_constructor(struct pci_dev *pdev, struct pci_tsm *tsm,
 	}
 	tsm->pdev = pdev;
 	tsm->tsm_dev = tsm_dev;
+	pci_tsm_init_evidence(&tsm->evidence, 0, HASH_ALGO__LAST);
 
 	return 0;
 }
@@ -1149,6 +1186,7 @@ int pci_tsm_devsec_constructor(struct pci_dev *pdev, struct pci_tsm_devsec *tsm,
 	pci_tsm->dsm_dev = NULL;
 	pci_tsm->pdev = pdev;
 	pci_tsm->tsm_dev = tsm_dev;
+	pci_tsm_init_evidence(&pci_tsm->evidence, 0, HASH_ALGO__LAST);
 
 	return 0;
 }
@@ -1167,6 +1205,7 @@ int pci_tsm_pf0_constructor(struct pci_dev *pdev, struct pci_tsm_pf0 *tsm,
 	init_completion(&tsm->contexts_drained);
 	tsm->context_users = 0;
 	tsm->removing = false;
+	tsm->stream_connected = false;
 	/*
 	 * Low-level TSM drivers decide whether they can operate without a DOE
 	 * mailbox; some use an alternate transport for TDISP coordination.
@@ -1218,6 +1257,8 @@ int pci_tsm_register(struct tsm_dev *tsm_dev)
 				devsec_sysfs_enable(pdev);
 	}
 
+	for_each_pci_dev(pdev)
+		pci_tsm_attestation_update(pdev);
 	return 0;
 }
 
@@ -1230,7 +1271,8 @@ int pci_tsm_register(struct tsm_dev *tsm_dev)
  * with the TSM is torn down. Additionally, if there are no more TSMs
  * registered, the PCI tsm/ sysfs attributes are hidden.
  */
-static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
+static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev,
+			      struct list_head *retired)
 {
 	struct pci_tsm *tsm = pdev->tsm;
 
@@ -1269,8 +1311,11 @@ static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
 
 	if (is_devsec_tsm(tsm_dev) && has_tee(pdev)) {
 		guard(device)(&pdev->dev);
-		pci_tsm_unlock(pdev);
+		if (pci_tsm_unlock(pdev))
+			pci_warn(pdev, "TSM unlock failed; retaining context\n");
 	}
+	if (!pdev->tsm)
+		pci_tsm_attestation_remove(pdev, retired);
 }
 
 void pci_tsm_destroy(struct pci_dev *pdev)
@@ -1279,8 +1324,10 @@ void pci_tsm_destroy(struct pci_dev *pdev)
 	struct completion *drained;
 	bool interrupted = false;
 	long rc;
+	LIST_HEAD(retired);
 
 	down_write(&pci_tsm_rwsem);
+	pci_tsm_attestation_remove(pdev, &retired);
 	if (pdev->tsm && is_link_tsm(pdev->tsm->tsm_dev) &&
 	    is_pci_tsm_pf0(pdev)) {
 		pf0 = to_pci_tsm_pf0(pdev->tsm);
@@ -1311,13 +1358,14 @@ void pci_tsm_destroy(struct pci_dev *pdev)
 		}
 		down_write(&pci_tsm_rwsem);
 	}
-	__pci_tsm_destroy(pdev, NULL);
+	__pci_tsm_destroy(pdev, NULL, &retired);
 	up_write(&pci_tsm_rwsem);
+	pci_tsm_attestation_destroy(&retired);
 }
 
 void pci_tsm_init(struct pci_dev *pdev)
 {
-	guard(rwsem_read)(&pci_tsm_rwsem);
+	guard(rwsem_write)(&pci_tsm_rwsem);
 
 	/*
 	 * Subfunctions are either probed synchronous with connect() or later
@@ -1337,10 +1385,9 @@ void pci_tsm_init(struct pci_dev *pdev)
 		 * The only path to init a Device Security Manager capable
 		 * device is via connect().
 		 */
-		if (!dsm->tsm)
+		if (!dsm->tsm || !is_link_tsm(dsm->tsm->tsm_dev))
 			return;
-		if (is_link_tsm(dsm->tsm->tsm_dev) &&
-		    to_pci_tsm_pf0(dsm->tsm)->removing)
+		if (to_pci_tsm_pf0(dsm->tsm)->removing)
 			return;
 		probe_fn(pdev, dsm);
 	}
@@ -1370,10 +1417,18 @@ static int pci_tsm_check_unregister(struct tsm_dev *tsm_dev)
 		}
 	}
 
+	if (is_devsec_tsm(tsm_dev)) {
+		for_each_pci_dev(pdev) {
+			if (pdev->tsm && pdev->tsm->tsm_dev == tsm_dev) {
+				pci_dev_put(pdev);
+				return -EBUSY;
+			}
+		}
+	}
 	return 0;
 }
 
-static void __pci_tsm_unregister(struct tsm_dev *tsm_dev)
+static void __pci_tsm_unregister(struct tsm_dev *tsm_dev, struct list_head *retired)
 {
 	struct pci_dev *pdev = NULL;
 
@@ -1382,19 +1437,26 @@ static void __pci_tsm_unregister(struct tsm_dev *tsm_dev)
 		pci_tsm_link_count--;
 	if (is_devsec_tsm(tsm_dev))
 		pci_tsm_devsec_count--;
-	for_each_pci_dev_reverse(pdev)
-		__pci_tsm_destroy(pdev, tsm_dev);
+	for_each_pci_dev_reverse(pdev) {
+		__pci_tsm_destroy(pdev, tsm_dev, retired);
+		pci_tsm_attestation_update(pdev);
+	}
 }
 
 void pci_tsm_unregister(struct tsm_dev *tsm_dev)
 {
+	LIST_HEAD(retired);
+	struct list_head *cleanup __free(pci_tsm_attestation_destroy) = &retired;
+
 	guard(rwsem_write)(&pci_tsm_rwsem);
-	__pci_tsm_unregister(tsm_dev);
+	__pci_tsm_unregister(tsm_dev, &retired);
 }
 
 /* Remove class lookup and PCI connections without admitting new users. */
 int pci_tsm_unregister_device(struct tsm_dev *tsm_dev)
 {
+	LIST_HEAD(retired);
+	struct list_head *cleanup __free(pci_tsm_attestation_destroy) = &retired;
 	int rc;
 
 	guard(rwsem_write)(&pci_tsm_rwsem);
@@ -1403,7 +1465,7 @@ int pci_tsm_unregister_device(struct tsm_dev *tsm_dev)
 		return rc;
 
 	device_del(&tsm_dev->dev);
-	__pci_tsm_unregister(tsm_dev);
+	__pci_tsm_unregister(tsm_dev, &retired);
 	return 0;
 }
 
diff --git a/drivers/pci/tsm/evidence.c b/drivers/pci/tsm/evidence.c
deleted file mode 100644
index 4d475b3dd6f6..000000000000
--- a/drivers/pci/tsm/evidence.c
+++ /dev/null
@@ -1,274 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/* Copyright (C) 2026 Intel Corporation */
-
-#include <crypto/hash_info.h>
-#include <linux/bitfield.h>
-#include <linux/init.h>
-#include <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/netlink.h>
-#include <linux/pci.h>
-#include <linux/pci-tsm.h>
-#include <linux/slab.h>
-#include <net/genetlink.h>
-#include <net/netlink.h>
-
-#include "netlink.h"
-
-struct pci_tsm_evidence_ctx {
-	struct pci_dev *pdev;
-	unsigned long type_mask;
-	unsigned long flags;
-	void *nonce;
-	int generation;
-	int type;
-	u32 offset;
-	u16 nonce_len;
-};
-
-#define PCI_TSM_EVIDENCE_START U32_MAX
-#define PCI_TSM_EVIDENCE_OBJECT_START (U32_MAX - 1)
-int pci_tsm_nl_evidence_read_pre(struct netlink_callback *cb)
-{
-	struct pci_tsm_evidence_ctx *ctx = (struct pci_tsm_evidence_ctx *)cb->ctx;
-	const struct genl_info *info = genl_info_dump(cb);
-	unsigned long type_mask_unknown;
-	struct nlattr *attr;
-	struct device *dev;
-	char name[32];
-
-	NL_ASSERT_CTX_FITS(struct pci_tsm_evidence_ctx);
-
-	if (GENL_REQ_ATTR_CHECK(info, PCI_TSM_A_EVIDENCE_OBJECT_TYPE_MASK)) {
-		NL_SET_ERR_MSG(info->extack, "missing object request mask");
-		return -EINVAL;
-	}
-
-	attr = info->attrs[PCI_TSM_A_EVIDENCE_OBJECT_TYPE_MASK];
-	ctx->type_mask = nla_get_u32(attr);
-	type_mask_unknown = ctx->type_mask & ~PCI_TSM_EVIDENCE_TYPE_FLAG_MASK;
-	if (type_mask_unknown) {
-		NL_SET_ERR_MSG_FMT(info->extack,
-				   "unsupported object request %#lx",
-				   type_mask_unknown);
-		return -EINVAL;
-	}
-
-	attr = info->attrs[PCI_TSM_A_EVIDENCE_OBJECT_FLAGS];
-	if (attr) {
-		ctx->flags = nla_get_u32(attr);
-		if (ctx->flags & ~PCI_TSM_EVIDENCE_FLAG_MASK) {
-			NL_SET_BAD_ATTR(info->extack, attr);
-			return -EINVAL;
-		}
-	}
-
-	if (GENL_REQ_ATTR_CHECK(info, PCI_TSM_A_EVIDENCE_OBJECT_DEV_NAME)) {
-		NL_SET_ERR_MSG(info->extack, "missing device name");
-		return -EINVAL;
-	}
-
-	attr = info->attrs[PCI_TSM_A_EVIDENCE_OBJECT_DEV_NAME];
-	if (nla_strscpy(name, attr, sizeof(name)) < 0) {
-		NL_SET_BAD_ATTR(info->extack, attr);
-		return -EINVAL;
-	}
-
-	dev = bus_find_device_by_name(&pci_bus_type, NULL, name);
-	if (!dev) {
-		NL_SET_ERR_MSG_FMT(info->extack, "device '%s' not found", name);
-		return -ENODEV;
-	}
-	ctx->pdev = to_pci_dev(dev);
-
-	ctx->type =
-		find_first_bit(&ctx->type_mask, PCI_TSM_EVIDENCE_TYPE_MAX + 1);
-	if (ctx->type > PCI_TSM_EVIDENCE_TYPE_MAX) {
-		NL_SET_ERR_MSG(info->extack, "no evidence type requested");
-		return -EINVAL;
-	}
-	ctx->offset = PCI_TSM_EVIDENCE_START;
-
-	return 0;
-}
-
-int pci_tsm_nl_evidence_read_post(struct netlink_callback *cb)
-{
-	struct pci_tsm_evidence_ctx *ctx =
-		(struct pci_tsm_evidence_ctx *)cb->ctx;
-
-	pci_dev_put(ctx->pdev);
-	return 0;
-}
-
-static size_t evidence_len(struct pci_tsm_evidence *evidence,
-			   struct pci_tsm_evidence_object *obj,
-			   unsigned long flags)
-{
-	if (flags & PCI_TSM_EVIDENCE_FLAG_DIGEST) {
-		if (obj->digest)
-			return hash_digest_size[evidence->digest_algo];
-		return 0;
-	}
-	return obj->len;
-}
-
-static void *evidence_data(struct pci_tsm_evidence_object *obj,
-			   unsigned long flags)
-{
-	if (flags & PCI_TSM_EVIDENCE_FLAG_DIGEST)
-		return obj->digest;
-	return obj->data;
-}
-
-static int __pci_tsm_evidence_read(struct sk_buff *skb,
-				   struct netlink_callback *cb)
-{
-	struct pci_tsm_evidence_ctx *ctx =
-		(struct pci_tsm_evidence_ctx *)cb->ctx;
-	struct pci_dev *pdev = ctx->pdev;
-	struct pci_tsm_evidence *evidence = &pdev->tsm->evidence;
-	struct pci_tsm_evidence_object *obj = &evidence->obj[ctx->type];
-	size_t object_len = evidence_len(evidence, obj, ctx->flags);
-	void *object_data = evidence_data(obj, ctx->flags);
-	size_t available, overhead, len;
-	void *hdr;
-	int rc;
-
-	hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
-			  &pci_tsm_nl_family, NLM_F_MULTI,
-			  PCI_TSM_CMD_EVIDENCE_READ);
-	if (!hdr)
-		return -EMSGSIZE;
-
-	if (ctx->offset == PCI_TSM_EVIDENCE_OBJECT_START) {
-		rc = nla_put_u32(skb, PCI_TSM_A_EVIDENCE_OBJECT_TYPE,
-				 ctx->type);
-		if (rc)
-			goto out_cancel;
-		ctx->offset = 0;
-	}
-
-	available = skb_tailroom(skb);
-	overhead = nla_total_size(0) + NLA_ALIGNTO;
-	if (available <= overhead) {
-		rc = -EMSGSIZE;
-		goto out_cancel;
-	}
-
-	if (object_len)
-		len = min(available - overhead, object_len - ctx->offset);
-	else
-		len = 0;
-
-	rc = nla_put(skb, PCI_TSM_A_EVIDENCE_OBJECT_VAL, len,
-		     object_data + ctx->offset);
-	if (rc)
-		goto out_end;
-
-	ctx->offset += len;
-	if (ctx->offset < object_len) {
-		rc = 1;
-		goto out_end;
-	}
-
-	ctx->type = find_next_bit(&ctx->type_mask,
-				  PCI_TSM_EVIDENCE_TYPE_MAX + 1, ctx->type + 1);
-	/* no more evidence types requested */
-	if (ctx->type > PCI_TSM_EVIDENCE_TYPE_MAX) {
-		rc = 0;
-		goto out_end;
-	}
-	ctx->offset = PCI_TSM_EVIDENCE_OBJECT_START;
-	rc = 1;
-
-out_end:
-	genlmsg_end(skb, hdr);
-	if (rc > 0)
-		return skb->len;
-	return rc;
-
-out_cancel:
-	genlmsg_cancel(skb, hdr);
-	return rc;
-}
-
-static int pci_tsm_evidence_read(struct sk_buff *skb,
-				 struct netlink_callback *cb)
-{
-	struct pci_tsm_evidence_ctx *ctx =
-		(struct pci_tsm_evidence_ctx *)cb->ctx;
-	const struct genl_info *info = genl_info_dump(cb);
-	struct pci_tsm_evidence *evidence;
-	struct pci_dev *pdev = ctx->pdev;
-	int rc;
-
-	ACQUIRE(rwsem_read_intr, tsm_lock)(&pci_tsm_rwsem);
-	if ((rc = ACQUIRE_ERR(rwsem_read_intr, &tsm_lock))) {
-		NL_SET_ERR_MSG(info->extack,
-			       "interrupted acquiring TSM context");
-		return rc;
-	}
-
-	if (!pdev->tsm) {
-		NL_SET_ERR_MSG(info->extack, "no TSM context");
-		return -ENXIO;
-	}
-
-	evidence = &pdev->tsm->evidence;
-	ACQUIRE(rwsem_read_intr, evidence_lock)(&evidence->lock);
-	if ((rc = ACQUIRE_ERR(rwsem_read_intr, &evidence_lock))) {
-		NL_SET_ERR_MSG(info->extack, "interrupted acquiring evidence");
-		return rc;
-	}
-
-	/* generation is only valid when non-zero */
-	if (ctx->offset == PCI_TSM_EVIDENCE_START) {
-		if (!evidence->generation) {
-			NL_SET_ERR_MSG(info->extack, "no evidence available");
-			return -ENXIO;
-		}
-		ctx->generation = evidence->generation;
-		ctx->offset = PCI_TSM_EVIDENCE_OBJECT_START;
-	}
-
-	if (ctx->generation != evidence->generation) {
-		NL_SET_ERR_MSG(info->extack, "evidence updated during read");
-		return -EAGAIN;
-	}
-
-	return __pci_tsm_evidence_read(skb, cb);
-}
-
-static int pci_tsm_evidence_refresh(struct pci_tsm_evidence_ctx *ctx)
-{
-	return -EOPNOTSUPP;
-}
-
-int pci_tsm_nl_evidence_read_dumpit(struct sk_buff *skb,
-				    struct netlink_callback *cb)
-{
-	struct pci_tsm_evidence_ctx *ctx =
-		(struct pci_tsm_evidence_ctx *)cb->ctx;
-	const struct genl_info *info = genl_info_dump(cb);
-
-	/* Attempt one refresh per dump request before reading */
-	if (ctx->offset == PCI_TSM_EVIDENCE_START && ctx->nonce) {
-		int rc = pci_tsm_evidence_refresh(ctx);
-
-		if (rc) {
-			NL_SET_ERR_MSG_FMT(info->extack,
-					   "evidence refresh failed: %d", rc);
-			return rc;
-		}
-		ctx->nonce = NULL;
-	}
-	return pci_tsm_evidence_read(skb, cb);
-}
-
-static int __init pci_tsm_nl_init(void)
-{
-	return genl_register_family(&pci_tsm_nl_family);
-}
-
-subsys_initcall(pci_tsm_nl_init);
diff --git a/drivers/pci/tsm/internal.h b/drivers/pci/tsm/internal.h
new file mode 100644
index 000000000000..613267a84b8f
--- /dev/null
+++ b/drivers/pci/tsm/internal.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _PCI_TSM_INTERNAL_H
+#define _PCI_TSM_INTERNAL_H
+
+#include <linux/types.h>
+
+struct pci_dev;
+struct list_head;
+struct tsm_dev;
+
+/* All helpers require pci_tsm_rwsem held for write. */
+bool pci_tsm_can_lock(struct tsm_dev *tsm_dev);
+void pci_tsm_attestation_update(struct pci_dev *pdev);
+int pci_tsm_attestation_add(struct pci_dev *pdev);
+void pci_tsm_attestation_remove(struct pci_dev *pdev, struct list_head *retired);
+int pci_tsm_evidence_accept(struct pci_dev *pdev, unsigned int slot);
+int pci_tsm_disconnect(struct pci_dev *pdev);
+int pci_tsm_lock(struct pci_dev *pdev, struct tsm_dev *tsm_dev);
+int pci_tsm_unlock(struct pci_dev *pdev);
+int pci_tsm_accept(struct pci_dev *pdev);
+void pci_tsm_attestation_notify_state_change(struct pci_dev *pdev);
+
+/* Call after dropping PCI and device locks to drain retired CTLV requests. */
+void pci_tsm_attestation_destroy(struct list_head *retired);
+
+#endif
diff --git a/drivers/pci/tsm/netlink.c b/drivers/pci/tsm/netlink.c
deleted file mode 100644
index 26fadb727666..000000000000
--- a/drivers/pci/tsm/netlink.c
+++ /dev/null
@@ -1,43 +0,0 @@
-// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
-/* Do not edit directly, auto-generated from: */
-/*	Documentation/netlink/specs/pci-tsm.yaml */
-/* YNL-GEN kernel source */
-/* To regenerate run: tools/net/ynl/ynl-regen.sh */
-
-#include <net/netlink.h>
-#include <net/genetlink.h>
-
-#include "netlink.h"
-
-#include <uapi/linux/pci-tsm-netlink.h>
-
-/* PCI_TSM_CMD_EVIDENCE_READ - dump */
-static const struct nla_policy pci_tsm_evidence_read_nl_policy[PCI_TSM_A_EVIDENCE_OBJECT_NONCE + 1] = {
-	[PCI_TSM_A_EVIDENCE_OBJECT_TYPE_MASK] = { .type = NLA_U32, },
-	[PCI_TSM_A_EVIDENCE_OBJECT_FLAGS] = { .type = NLA_U32, },
-	[PCI_TSM_A_EVIDENCE_OBJECT_DEV_NAME] = { .type = NLA_NUL_STRING, },
-	[PCI_TSM_A_EVIDENCE_OBJECT_NONCE] = NLA_POLICY_MAX_LEN(PCI_TSM_MAX_NONCE_SIZE),
-};
-
-/* Ops table for pci_tsm */
-static const struct genl_split_ops pci_tsm_nl_ops[] = {
-	{
-		.cmd		= PCI_TSM_CMD_EVIDENCE_READ,
-		.start		= pci_tsm_nl_evidence_read_pre,
-		.dumpit		= pci_tsm_nl_evidence_read_dumpit,
-		.done		= pci_tsm_nl_evidence_read_post,
-		.policy		= pci_tsm_evidence_read_nl_policy,
-		.maxattr	= PCI_TSM_A_EVIDENCE_OBJECT_NONCE,
-		.flags		= GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP,
-	},
-};
-
-struct genl_family pci_tsm_nl_family __ro_after_init = {
-	.name		= PCI_TSM_FAMILY_NAME,
-	.version	= PCI_TSM_FAMILY_VERSION,
-	.netnsok	= true,
-	.parallel_ops	= true,
-	.module		= THIS_MODULE,
-	.split_ops	= pci_tsm_nl_ops,
-	.n_split_ops	= ARRAY_SIZE(pci_tsm_nl_ops),
-};
diff --git a/drivers/pci/tsm/netlink.h b/drivers/pci/tsm/netlink.h
deleted file mode 100644
index 34f3fb6ba2b7..000000000000
--- a/drivers/pci/tsm/netlink.h
+++ /dev/null
@@ -1,23 +0,0 @@
-/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
-/* Do not edit directly, auto-generated from: */
-/*	Documentation/netlink/specs/pci-tsm.yaml */
-/* YNL-GEN kernel header */
-/* To regenerate run: tools/net/ynl/ynl-regen.sh */
-
-#ifndef _LINUX_PCI_TSM_GEN_H
-#define _LINUX_PCI_TSM_GEN_H
-
-#include <net/netlink.h>
-#include <net/genetlink.h>
-
-#include <uapi/linux/pci-tsm-netlink.h>
-
-int pci_tsm_nl_evidence_read_pre(struct netlink_callback *cb);
-int pci_tsm_nl_evidence_read_post(struct netlink_callback *cb);
-
-int pci_tsm_nl_evidence_read_dumpit(struct sk_buff *skb,
-				    struct netlink_callback *cb);
-
-extern struct genl_family pci_tsm_nl_family;
-
-#endif /* _LINUX_PCI_TSM_GEN_H */
diff --git a/drivers/virt/coco/arm-cca-guest/main.c b/drivers/virt/coco/arm-cca-guest/main.c
index 50a21317c941..f50f3e660bbc 100644
--- a/drivers/virt/coco/arm-cca-guest/main.c
+++ b/drivers/virt/coco/arm-cca-guest/main.c
@@ -340,8 +340,7 @@ static int cca_collect_dev_evidence(struct pci_dev *pdev, struct cca_guest_dsc *
 	down_write(&evidence->lock);
 	evidence->generation = dev_info->meas_seq;
 
-	/* we default to slot 0 in pdev_create */
-	obj = &evidence->obj[PCI_TSM_EVIDENCE_TYPE_CERT0];
+	obj = &evidence->obj[PCI_TSM_EVIDENCE_TYPE_CERT0 + evidence->slot];
 	WARN_ON(obj->data);
 	obj->data = certificate;
 	obj->len = certificate_size;
@@ -367,6 +366,11 @@ static int cca_collect_dev_evidence(struct pci_dev *pdev, struct cca_guest_dsc *
 	memcpy(dsc->dev_info.vca_digest, dev_info->protocol_data_digest, SHA512_DIGEST_SIZE);
 	memcpy(dsc->dev_info.meas_digest, dev_info->meas_digest, SHA512_DIGEST_SIZE);
 	memcpy(dsc->dev_info.report_digest, dev_info->report_digest, SHA512_DIGEST_SIZE);
+	evidence->obj[PCI_TSM_EVIDENCE_TYPE_CERT0 + evidence->slot].digest =
+		dsc->dev_info.cert_digest;
+	evidence->obj[PCI_TSM_EVIDENCE_TYPE_VCA].digest = dsc->dev_info.vca_digest;
+	evidence->obj[PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS].digest = dsc->dev_info.meas_digest;
+	evidence->obj[PCI_TSM_EVIDENCE_TYPE_REPORT].digest = dsc->dev_info.report_digest;
 	up_write(&evidence->lock);
 
 	kfree(dev_info);
@@ -416,6 +420,11 @@ static struct pci_tsm *cca_tsm_lock(struct tsm_dev *tsm_dev, struct pci_dev *pde
 		goto free_dev_info;
 	}
 
+	if (dev_info->id_index) {
+		ret = -EOPNOTSUPP;
+		goto free_dev_info;
+	}
+
 	/* Collect the lock sequence number. */
 	cca_dsc->dev_info.lock_seq = dev_info->lock_seq;
 
@@ -450,7 +459,7 @@ static struct pci_tsm *cca_tsm_lock(struct tsm_dev *tsm_dev, struct pci_dev *pde
 	return ERR_PTR(ret);
 }
 
-static void cca_tsm_unlock(struct pci_tsm *tsm)
+static int cca_tsm_unlock(struct pci_tsm *tsm)
 {
 	long ret;
 	struct pci_dev *pdev = tsm->pdev;
@@ -460,20 +469,22 @@ static void cca_tsm_unlock(struct pci_tsm *tsm)
 	ret = cca_unmap_evidence_report_range(tsm->pdev);
 	if (ret) {
 		pci_err(tsm->pdev, "failed to invalidate the interface report\n");
-		goto err_out;
+		return ret;
 	}
 
-	cca_device_unlock(tsm->pdev);
+	ret = cca_device_unlock(tsm->pdev);
+	if (ret)
+		return ret;
+
 	if (cca_dsc->pci.mmio)
 		pci_tsm_mmio_teardown(cca_dsc->pci.mmio);
 
-err_out:
-	/*
-	 * No error handling from this function. Leave the device locked
-	 */
 	if (cca_dsc->pci.mmio)
 		pci_tsm_mmio_free(tsm->pdev, cca_dsc->pci.mmio);
+	for (int i = 0; i <= PCI_TSM_EVIDENCE_TYPE_MAX; i++)
+		kvfree(tsm->evidence.obj[i].data);
 	kfree(cca_dsc);
+	return 0;
 }
 
 static int __cca_tsm_accept(struct pci_dev *pdev, unsigned long lock_seq)
@@ -496,6 +507,11 @@ static int cca_tsm_accept(struct pci_dev *pdev)
 }
 
 static struct pci_tsm_ops cca_devsec_pci_ops = {
+	.owner = THIS_MODULE,
+	.evidence_types = BIT(PCI_TSM_EVIDENCE_TYPE_CERT0) |
+			  BIT(PCI_TSM_EVIDENCE_TYPE_VCA) |
+			  BIT(PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS) |
+			  BIT(PCI_TSM_EVIDENCE_TYPE_REPORT),
 	.lock = cca_tsm_lock,
 	.unlock = cca_tsm_unlock,
 	.accept	 = cca_tsm_accept,
diff --git a/drivers/virt/coco/arm-cca-guest/rsi-da.c b/drivers/virt/coco/arm-cca-guest/rsi-da.c
index b83b0b654ac3..f7a3c24817bb 100644
--- a/drivers/virt/coco/arm-cca-guest/rsi-da.c
+++ b/drivers/virt/coco/arm-cca-guest/rsi-da.c
@@ -268,28 +268,26 @@ int cca_device_accept(struct pci_dev *pdev, unsigned long lock_seq)
 		goto err_mmio_setup;
 	}
 
+	/* Keep partial mappings reachable until a successful UNLOCK. */
+	dsc->pci.mmio = tsm_mmio;
 	ret = cca_map_evidence_report_range(pdev, tsm_mmio);
 	if (ret) {
 		pci_err(pdev, "failed to validate the interface report\n");
-		goto err_evidence_report_map;
+		return ret;
 	}
 
 	ret = rhi_vdev_set_tdi_state(pdev, RHI_DA_TDI_CONFIG_RUN);
 	if (ret) {
 		pci_err(pdev, "failed to switch the device (%u) to RUN state\n", ret);
-		goto err_tdi_config_run;
+		return ret;
 	}
 
-	if (rsi_vdev_enable_dma(vdev_id, &dsc->dev_info))
+	if (rsi_vdev_enable_dma(vdev_id, &dsc->dev_info)) {
 		pci_err(pdev, "failed to enable DMA from the device\n");
-
-	dsc->pci.mmio = no_free_ptr(tsm_mmio);
+		return -EIO;
+	}
 	return 0;
 
-err_tdi_config_run:
-	cca_unmap_evidence_report_range(pdev);
-err_evidence_report_map:
-	pci_tsm_mmio_teardown(tsm_mmio);
 err_mmio_setup:
 	pci_tsm_mmio_free(pdev, tsm_mmio);
 	return ret;
diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
index ac8895925500..8fd209a2c32b 100644
--- a/include/linux/pci-tsm.h
+++ b/include/linux/pci-tsm.h
@@ -5,9 +5,45 @@
 #include <linux/mutex.h>
 #include <linux/pci.h>
 #include <linux/rwsem.h>
+#include <linux/sizes.h>
 #include <uapi/linux/hash_info.h>
 #include <uapi/linux/iommufd.h>
-#include <uapi/linux/pci-tsm-netlink.h>
+
+
+#define PCI_TSM_MAX_OBJECT_SIZE SZ_16M
+
+/**
+ * enum pci_tsm_evidence_type - PCI device security evidence objects
+ * @PCI_TSM_EVIDENCE_TYPE_CERT0: SPDM certificate chain from device slot0
+ * @PCI_TSM_EVIDENCE_TYPE_CERT1: SPDM certificate chain from device slot1
+ * @PCI_TSM_EVIDENCE_TYPE_CERT2: SPDM certificate chain from device slot2
+ * @PCI_TSM_EVIDENCE_TYPE_CERT3: SPDM certificate chain from device slot3
+ * @PCI_TSM_EVIDENCE_TYPE_CERT4: SPDM certificate chain from device slot4
+ * @PCI_TSM_EVIDENCE_TYPE_CERT5: SPDM certificate chain from device slot5
+ * @PCI_TSM_EVIDENCE_TYPE_CERT6: SPDM certificate chain from device slot6
+ * @PCI_TSM_EVIDENCE_TYPE_CERT7: SPDM certificate chain from device slot7
+ * @PCI_TSM_EVIDENCE_TYPE_VCA: SPDM transcript of version, capabilities, and
+ *   algorithms negotiation
+ * @PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS: SPDM GET_MEASUREMENTS response
+ * @PCI_TSM_EVIDENCE_TYPE_REPORT: TDISP GET_DEVICE_INTERFACE_REPORT response
+ */
+enum pci_tsm_evidence_type {
+	PCI_TSM_EVIDENCE_TYPE_CERT0,
+	PCI_TSM_EVIDENCE_TYPE_CERT1,
+	PCI_TSM_EVIDENCE_TYPE_CERT2,
+	PCI_TSM_EVIDENCE_TYPE_CERT3,
+	PCI_TSM_EVIDENCE_TYPE_CERT4,
+	PCI_TSM_EVIDENCE_TYPE_CERT5,
+	PCI_TSM_EVIDENCE_TYPE_CERT6,
+	PCI_TSM_EVIDENCE_TYPE_CERT7,
+	PCI_TSM_EVIDENCE_TYPE_VCA,
+	PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS,
+	PCI_TSM_EVIDENCE_TYPE_REPORT,
+
+	/* private: */
+	__PCI_TSM_EVIDENCE_TYPE_MAX,
+	PCI_TSM_EVIDENCE_TYPE_MAX = (__PCI_TSM_EVIDENCE_TYPE_MAX - 1)
+};
 
 struct iommufd_viommu_ops;
 struct module;
@@ -26,6 +62,8 @@ struct tsm_dev;
  * @viommu_get_ops: Return operations for a supported TSM-owned vIOMMU type,
  *		  or NULL if unsupported
  * @owner: Module providing the vIOMMU operations, if built as a module
+ * @evidence_types: bitmap of PCI_TSM_EVIDENCE_TYPE_* objects the backend
+ *		   makes available through the evidence cache
  *
  * The vIOMMU owner holds a TSM device reference and pins the backend module
  * before querying @viommu_get_ops, releasing both after vIOMMU destruction.
@@ -41,6 +79,7 @@ struct tsm_dev;
  */
 struct pci_tsm_ops {
 	struct module *owner;
+	unsigned long evidence_types;
 	const struct iommufd_viommu_ops *(*viommu_get_ops)(
 		struct device *dev, enum iommu_viommu_type type);
 
@@ -49,21 +88,26 @@ struct pci_tsm_ops {
 	 * @probe: establish context with the TSM (allocate / wrap 'struct
 	 *	   pci_tsm') for follow-on link operations
 	 * @remove: destroy link operations context
-	 * @connect: establish / validate a secure connection (e.g. IDE)
-	 *	     with the device
+	 * @connect: collect device identity; required for link backends; legacy
+	 *	     backends may also establish IDE
+	 * @evidence_accept: establish the secure session and IDE after evidence
+	 *		  acceptance, using pci_tsm::evidence.slot as the selected
+	 *		  certificate slot; optional for legacy backends
 	 * @disconnect: teardown the secure link
 	 *
-	 * Context: @probe, @remove, @connect, and @disconnect run under
-	 * pci_tsm_rwsem held for write to sync with TSM unregistration and
-	 * mutual exclusion of @connect and @disconnect. @connect and
-	 * @disconnect additionally run under the DSM lock (struct
-	 * pci_tsm_pf0::lock) as well as @probe and @remove of the subfunctions.
+	 * Context: All callbacks run under pci_tsm_rwsem held for write to sync
+	 * with TSM unregistration and exclude concurrent link operations.
+	 * @connect and @disconnect additionally run under the DSM lock (struct
+	 * pci_tsm_pf0::lock), as do @probe and @remove of the subfunctions.
+	 * @evidence_accept runs under the attestation instance lock; the write
+	 * semaphore also excludes IOMMUFD consumers that take the DSM lock.
 	 */
 	struct_group_tagged(pci_tsm_link_ops, link_ops,
 		struct pci_tsm *(*probe)(struct tsm_dev *tsm_dev,
 					 struct pci_dev *pdev);
 		void (*remove)(struct pci_tsm *tsm);
 		int (*connect)(struct pci_dev *pdev);
+		int (*evidence_accept)(struct pci_dev *pdev);
 		void (*disconnect)(struct pci_dev *pdev);
 	);
 
@@ -72,18 +116,19 @@ struct pci_tsm_ops {
 	 * @lock: establish context with the TSM (allocate / wrap 'struct
 	 *	  pci_tsm') for follow-on security state transitions from the
 	 *	  LOCKED state
-	 * @unlock: destroy TSM context and return device to UNLOCKED state
+	 * @unlock: return device to UNLOCKED and free context on success; retain
+	 *          context on failure and return a negative errno
 	 * @accept: accept a locked TDI for use, move it to RUN state
 	 *
 	 * Context: @lock and @unlock run under pci_tsm_rwsem held for write to
 	 * sync with TSM unregistration and each other. @accept runs under
-	 * pci_tsm_rwsem held for read. All operations run under the device lock
+	 * pci_tsm_rwsem held for write. All operations run under the device lock
 	 * for mutual exclusion with driver attach and detach.
 	 */
 	struct_group_tagged(pci_tsm_devsec_ops, devsec_ops,
 		struct pci_tsm *(*lock)(struct tsm_dev *tsm_dev,
 					struct pci_dev *pdev);
-		void (*unlock)(struct pci_tsm *tsm);
+		int (*unlock)(struct pci_tsm *tsm);
 		int (*accept)(struct pci_dev *pdev);
 	);
 
@@ -101,15 +146,14 @@ struct pci_tsm_ops {
  *
  * There are multiple population and verification models for these blobs
  * depending on TSM policy. Some examples:
- * 1/ Host (link) TSM: populates certs and provides a device signed measurement
- *    transcript PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS
+ * 1/ Host (link) TSM: exposes supported certificate chains for
+ *    userspace verification before evidence acceptance establishes the stream.
  * 2/ Guest (devsec) TSM: receives untrusted blobs via guest-to-host shared
  *    memory protocol and then requests digests of the same via guest-to-host
  *    encrypted protocol.
- * The expectation is that all of these blobs are received in an SPDM session
- * with a signed transcript, however not all TSMs provide the full transcript of
- * these objects' retrieval and instead require asking the TSM for cached
- * digests of the blobs over trusted TSM channels.
+ * Host certificate retrieval precedes SPDM session establishment. Not all TSMs
+ * provide full transcripts; devsec TSMs may instead supply cached evidence
+ * digests over trusted TSM channels.
  */
 struct pci_tsm_evidence_object {
 	void *data;
@@ -119,9 +163,9 @@ struct pci_tsm_evidence_object {
 
 /**
  * struct pci_tsm_evidence - Retrieved evidence for SPDM session GET commands
- * @slot: certificate slot used by a link TSM for connect.
+ * @slot: certificate slot selected when accepting host evidence.
  * @generation: refresh_evidence() invocation detection
- * @digest_algo: payload size of PCI_TSM_EVIDENCE_FLAG_DIGEST requests
+ * @digest_algo: algorithm of native trusted evidence digests
  * @lock: synchronize dumps vs refresh_evidence()
  * @obj: array of evidence objects a TSM might populate
  *
@@ -145,6 +189,7 @@ struct pci_tsm_evidence {
  * @dsm_dev: PCI Device Security Manager for link operations on @pdev
  * @tsm_dev: PCI TEE Security Manager device for Link Confidentiality or Device
  *	     Function Security operations
+ * @error: hardware transition failed; require disconnect/unlock before reuse
  * @evidence: cached evidence from SPDM session establishment (connect), or
  *	      TDISP bind (lock)
  *
@@ -170,6 +215,7 @@ struct pci_tsm {
 	struct pci_dev *dsm_dev;
 	struct tsm_dev *tsm_dev;
 	struct pci_tsm_evidence evidence;
+	bool error;
 };
 
 /**
@@ -180,6 +226,7 @@ struct pci_tsm {
  *	blocks link disconnect
  * @contexts_drained: completed when the last context is released
  * @removing: reject new contexts while the DSM is being removed
+ * @stream_connected: secure session and stream setup completed
  * @doe_mb: PCIe Data Object Exchange mailbox
  */
 struct pci_tsm_pf0 {
@@ -188,6 +235,7 @@ struct pci_tsm_pf0 {
 	unsigned int context_users;
 	struct completion contexts_drained;
 	bool removing;
+	bool stream_connected;
 	struct pci_doe_mb *doe_mb;
 };
 
diff --git a/include/linux/pci.h b/include/linux/pci.h
index d31a8d107b1e..39541d217fac 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -580,6 +580,8 @@ struct pci_dev {
 #endif
 #ifdef CONFIG_PCI_TSM
 	struct pci_tsm *tsm;		/* TSM operation state */
+	struct pci_tsm_attestation *tsm_attestation; /* Connected/locked CTLV instance */
+	bool tsm_attestation_removing; /* CTLV teardown is draining requests */
 #endif
 	u16		acs_cap;	/* ACS Capability offset */
 	u16		acs_capabilities; /* ACS Capabilities */
diff --git a/include/uapi/linux/pci-tsm-netlink.h b/include/uapi/linux/pci-tsm-netlink.h
deleted file mode 100644
index a0e8b044e1b8..000000000000
--- a/include/uapi/linux/pci-tsm-netlink.h
+++ /dev/null
@@ -1,101 +0,0 @@
-/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
-/* Do not edit directly, auto-generated from: */
-/*	Documentation/netlink/specs/pci-tsm.yaml */
-/* YNL-GEN uapi header */
-/* To regenerate run: tools/net/ynl/ynl-regen.sh */
-
-#ifndef _UAPI_LINUX_PCI_TSM_NETLINK_H
-#define _UAPI_LINUX_PCI_TSM_NETLINK_H
-
-#define PCI_TSM_FAMILY_NAME	"pci-tsm"
-#define PCI_TSM_FAMILY_VERSION	1
-
-#define PCI_TSM_MAX_OBJECT_SIZE	16777216
-#define PCI_TSM_MAX_NONCE_SIZE	256
-#define PCI_TSM_MAX_OBJ_TYPE	4
-
-/**
- * enum pci_tsm_evidence_type - PCI device security evidence objects
- * @PCI_TSM_EVIDENCE_TYPE_CERT0: SPDM certificate chain from device slot0
- * @PCI_TSM_EVIDENCE_TYPE_CERT1: SPDM certificate chain from device slot1
- * @PCI_TSM_EVIDENCE_TYPE_CERT2: SPDM certificate chain from device slot2
- * @PCI_TSM_EVIDENCE_TYPE_CERT3: SPDM certificate chain from device slot3
- * @PCI_TSM_EVIDENCE_TYPE_CERT4: SPDM certificate chain from device slot4
- * @PCI_TSM_EVIDENCE_TYPE_CERT5: SPDM certificate chain from device slot5
- * @PCI_TSM_EVIDENCE_TYPE_CERT6: SPDM certificate chain from device slot6
- * @PCI_TSM_EVIDENCE_TYPE_CERT7: SPDM certificate chain from device slot7
- * @PCI_TSM_EVIDENCE_TYPE_VCA: SPDM transcript of version, capabilities, and
- *   algorithms negotiation
- * @PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS: SPDM GET_MEASUREMENTS response
- * @PCI_TSM_EVIDENCE_TYPE_REPORT: TDISP GET_DEVICE_INTERFACE_REPORT response
- */
-enum pci_tsm_evidence_type {
-	PCI_TSM_EVIDENCE_TYPE_CERT0,
-	PCI_TSM_EVIDENCE_TYPE_CERT1,
-	PCI_TSM_EVIDENCE_TYPE_CERT2,
-	PCI_TSM_EVIDENCE_TYPE_CERT3,
-	PCI_TSM_EVIDENCE_TYPE_CERT4,
-	PCI_TSM_EVIDENCE_TYPE_CERT5,
-	PCI_TSM_EVIDENCE_TYPE_CERT6,
-	PCI_TSM_EVIDENCE_TYPE_CERT7,
-	PCI_TSM_EVIDENCE_TYPE_VCA,
-	PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS,
-	PCI_TSM_EVIDENCE_TYPE_REPORT,
-
-	/* private: */
-	__PCI_TSM_EVIDENCE_TYPE_MAX,
-	PCI_TSM_EVIDENCE_TYPE_MAX = (__PCI_TSM_EVIDENCE_TYPE_MAX - 1)
-};
-
-/*
- * PCI device security evidence request flags
- */
-enum pci_tsm_evidence_type_flag {
-	PCI_TSM_EVIDENCE_TYPE_FLAG_CERT0 = 1,
-	PCI_TSM_EVIDENCE_TYPE_FLAG_CERT1 = 2,
-	PCI_TSM_EVIDENCE_TYPE_FLAG_CERT2 = 4,
-	PCI_TSM_EVIDENCE_TYPE_FLAG_CERT3 = 8,
-	PCI_TSM_EVIDENCE_TYPE_FLAG_CERT4 = 16,
-	PCI_TSM_EVIDENCE_TYPE_FLAG_CERT5 = 32,
-	PCI_TSM_EVIDENCE_TYPE_FLAG_CERT6 = 64,
-	PCI_TSM_EVIDENCE_TYPE_FLAG_CERT7 = 128,
-	PCI_TSM_EVIDENCE_TYPE_FLAG_VCA = 256,
-	PCI_TSM_EVIDENCE_TYPE_FLAG_MEASUREMENTS = 512,
-	PCI_TSM_EVIDENCE_TYPE_FLAG_REPORT = 1024,
-
-	/* private: */
-	PCI_TSM_EVIDENCE_TYPE_FLAG_MASK = 2047,
-};
-
-/**
- * enum pci_tsm_evidence_flag - Flags to control evidence retrieval
- * @PCI_TSM_EVIDENCE_FLAG_DIGEST: Request the TSM's private digest of an
- *   evidence object
- */
-enum pci_tsm_evidence_flag {
-	PCI_TSM_EVIDENCE_FLAG_DIGEST = 1,
-
-	/* private: */
-	PCI_TSM_EVIDENCE_FLAG_MASK = 1,
-};
-
-enum {
-	PCI_TSM_A_EVIDENCE_OBJECT_TYPE = 1,
-	PCI_TSM_A_EVIDENCE_OBJECT_TYPE_MASK,
-	PCI_TSM_A_EVIDENCE_OBJECT_FLAGS,
-	PCI_TSM_A_EVIDENCE_OBJECT_DEV_NAME,
-	PCI_TSM_A_EVIDENCE_OBJECT_NONCE,
-	PCI_TSM_A_EVIDENCE_OBJECT_VAL,
-
-	__PCI_TSM_A_EVIDENCE_OBJECT_MAX,
-	PCI_TSM_A_EVIDENCE_OBJECT_MAX = (__PCI_TSM_A_EVIDENCE_OBJECT_MAX - 1)
-};
-
-enum {
-	PCI_TSM_CMD_EVIDENCE_READ = 1,
-
-	__PCI_TSM_CMD_MAX,
-	PCI_TSM_CMD_MAX = (__PCI_TSM_CMD_MAX - 1)
-};
-
-#endif /* _UAPI_LINUX_PCI_TSM_NETLINK_H */
diff --git a/samples/devsec/common.c b/samples/devsec/common.c
index 5dc4152e8b99..641266ca0b7f 100644
--- a/samples/devsec/common.c
+++ b/samples/devsec/common.c
@@ -5,7 +5,6 @@
 #include <linux/pci.h>
 #include <linux/pci-tsm.h>
 #include <linux/vmalloc.h>
-#include <uapi/linux/pci-tsm-netlink.h>
 
 #include "devsec.h"
 
diff --git a/samples/devsec/link_tsm.c b/samples/devsec/link_tsm.c
index 864ddf592db4..61cf6c16e1b5 100644
--- a/samples/devsec/link_tsm.c
+++ b/samples/devsec/link_tsm.c
@@ -231,6 +231,7 @@ static void devsec_link_tsm_disconnect(struct pci_dev *pdev)
 }
 
 static struct pci_tsm_ops devsec_link_pci_ops = {
+	.evidence_types = BIT(PCI_TSM_EVIDENCE_TYPE_CERT0),
 	.probe = devsec_link_tsm_pci_probe,
 	.remove = devsec_link_tsm_pci_remove,
 	.connect = devsec_link_tsm_connect,
diff --git a/samples/devsec/tsm.c b/samples/devsec/tsm.c
index 7d480a306e13..6ff88ca83906 100644
--- a/samples/devsec/tsm.c
+++ b/samples/devsec/tsm.c
@@ -2,6 +2,7 @@
 /* Copyright (C) 2024 - 2026 Intel Corporation */
 
 #define dev_fmt(fmt) "devsec: " fmt
+#include <crypto/hash_info.h>
 #include <linux/device/faux.h>
 #include <linux/pci-tsm.h>
 #include <linux/module.h>
@@ -10,6 +11,13 @@
 #include <linux/tsm.h>
 #include "devsec.h"
 
+static bool fail_accept;
+module_param(fail_accept, bool, 0600);
+MODULE_PARM_DESC(fail_accept, "Fail native ACCEPT for recovery testing");
+static bool fail_unlock;
+module_param(fail_unlock, bool, 0600);
+MODULE_PARM_DESC(fail_unlock, "Fail native UNLOCK while retaining its context");
+
 struct devsec_dev_data {
 	struct pci_tsm_devsec pci;
 };
@@ -32,17 +40,30 @@ static struct pci_tsm *devsec_tsm_lock(struct tsm_dev *tsm_dev, struct pci_dev *
 	if (rc)
 		return ERR_PTR(rc);
 
+	devsec_evidence_busy();
+	pci_tsm_init_evidence(&devsec_data->pci.base_tsm.evidence, 0, HASH_ALGO_SHA384);
+	devsec_init_evidence(&devsec_data->pci.base_tsm.evidence);
+	/* The sample uses the same synthetic payload for all guest reports. */
+	devsec_data->pci.base_tsm.evidence.obj[PCI_TSM_EVIDENCE_TYPE_VCA] =
+		devsec_data->pci.base_tsm.evidence.obj[PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS];
+	devsec_data->pci.base_tsm.evidence.obj[PCI_TSM_EVIDENCE_TYPE_REPORT] =
+		devsec_data->pci.base_tsm.evidence.obj[PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS];
 	return &no_free_ptr(devsec_data)->pci.base_tsm;
 }
 
-static void devsec_tsm_unlock(struct pci_tsm *tsm)
+static int devsec_tsm_unlock(struct pci_tsm *tsm)
 {
 	struct devsec_dev_data *devsec_data = to_devsec_data(tsm);
 	struct pci_tsm_devsec *devsec_tsm = to_pci_tsm_devsec(tsm);
 
-	pci_tsm_mmio_teardown(devsec_tsm->mmio);
+	if (READ_ONCE(fail_unlock))
+		return -EIO;
+	if (devsec_tsm->mmio)
+		pci_tsm_mmio_teardown(devsec_tsm->mmio);
 	kfree(devsec_tsm->mmio);
+	devsec_evidence_idle();
 	kfree(devsec_data);
+	return 0;
 }
 
 static int devsec_tsm_accept(struct pci_dev *pdev)
@@ -54,6 +75,8 @@ static int devsec_tsm_accept(struct pci_dev *pdev)
 		kzalloc(struct_size(mmio, mmio, PCI_NUM_RESOURCES), GFP_KERNEL);
 	if (!mmio)
 		return -ENOMEM;
+	if (READ_ONCE(fail_accept))
+		return -EIO;
 
 	/*
 	 * Typically this range request would come from the TDISP Interface
@@ -78,6 +101,11 @@ static int devsec_tsm_accept(struct pci_dev *pdev)
 }
 
 static struct pci_tsm_ops devsec_pci_ops = {
+	.owner = THIS_MODULE,
+	.evidence_types = BIT(PCI_TSM_EVIDENCE_TYPE_CERT0) |
+			  BIT(PCI_TSM_EVIDENCE_TYPE_VCA) |
+			  BIT(PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS) |
+			  BIT(PCI_TSM_EVIDENCE_TYPE_REPORT),
 	.lock = devsec_tsm_lock,
 	.unlock = devsec_tsm_unlock,
 	.accept = devsec_tsm_accept,
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [RFC PATCH v1 09/11] crypto/ccp: Defer SEV-TIO link setup until evidence acceptance
  2026-10-28 15:26 [RFC PATCH v1 00/11] PCI/TSM: Add CTLV attestation Aneesh Kumar K.V (Arm)
                   ` (7 preceding siblings ...)
  2026-10-28 15:26 ` [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts Aneesh Kumar K.V (Arm)
@ 2026-10-28 15:26 ` Aneesh Kumar K.V (Arm)
  2026-10-28 15:26 ` [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance Aneesh Kumar K.V (Arm)
  2026-10-28 15:26 ` [RFC PATCH v1 11/11] virt: coco: arm-cca-guest: Refresh selected evidence against trusted digests Aneesh Kumar K.V (Arm)
  10 siblings, 0 replies; 17+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-10-28 15:26 UTC (permalink / raw)
  To: linux-coco
  Cc: Aneesh Kumar K.V (Arm), Jiri Pirko, Kevin Tian,
	Alexey Kardashevskiy, Xu Yilun, Catalin Marinas, Suzuki K Poulose,
	Steven Price, Kiryl Shutsemau, Rick Edgecombe, Dave Hansen,
	Jonathan Cameron, Jason Gunthorpe, Bjorn Helgaas, Ashish Kalra,
	Tom Lendacky, John Allen, Will Deacon, Mark Rutland, patches

AMD SEV-TIO currently establishes its SPDM session and enables IDE during
connect, before userspace can retrieve or accept the device identity.
Move AMD link setup behind its evidence_accept callback.

During connect, reserve TC0's stream ID, create the firmware context,
and forward only unencrypted identity discovery requests. Cache the
complete slot-zero certificate chain as firmware receives it. Once
firmware consumes that chain and yields its next request, pause the
exchange without transmitting an authentication or session-establishment
request. No IDE registers are programmed during this identity
collection.

Keep the generic callback optional until the following patch converts
CCA, the remaining legacy link backend, and requires evidence_accept for
every link backend.

Cc: Ashish Kalra <ashish.kalra@amd.com>
Cc: Tom Lendacky <thomas.lendacky@amd.com>
Cc: John Allen <john.allen@amd.com>
Cc: Jonathan Cameron <jic23@kernel.org>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 drivers/crypto/ccp/sev-dev-tio.c |  17 ++-
 drivers/crypto/ccp/sev-dev-tio.h |   5 +
 drivers/crypto/ccp/sev-dev-tsm.c | 189 ++++++++++++++++++++++++++-----
 3 files changed, 176 insertions(+), 35 deletions(-)

diff --git a/drivers/crypto/ccp/sev-dev-tio.c b/drivers/crypto/ccp/sev-dev-tio.c
index c84e23982b43..ff51f09961c4 100644
--- a/drivers/crypto/ccp/sev-dev-tio.c
+++ b/drivers/crypto/ccp/sev-dev-tio.c
@@ -639,25 +639,23 @@ static void spdm_ctrl_init(struct spdm_ctrl *ctrl, struct tsm_dsm_tio *dev_data)
 static void spdm_ctrl_free(struct tsm_dsm_tio *dev_data)
 {
 	struct sev_tio_status *tio_status = to_tio_status(dev_data);
-	size_t len = tio_status->spdm_req_size_max -
-		(sla_dobj_id_to_size(SPDM_DOBJ_ID_RESP) +
-		 sizeof(struct sla_buffer_hdr));
 	struct tsm_spdm *spdm = &dev_data->spdm;
 
 	sla_buffer_unmap(dev_data->resp, dev_data->respbuf);
 	sla_buffer_unmap(dev_data->req, dev_data->reqbuf);
 	spdm->rsp = NULL;
 	spdm->req = NULL;
-	sla_free(dev_data->req, len, true);
-	sla_free(dev_data->resp, len, false);
-	sla_free(dev_data->scratch, tio_status->spdm_scratch_size_max, true);
+	sla_free(dev_data->req, tio_status->spdm_req_size_max, true);
+	sla_free(dev_data->resp, tio_status->spdm_req_size_max, false);
+	sla_free(dev_data->scratch, dev_data->scratch_len, true);
 
 	dev_data->req.sla = 0;
 	dev_data->resp.sla = 0;
 	dev_data->scratch.sla = 0;
 	dev_data->respbuf = NULL;
 	dev_data->reqbuf = NULL;
-	sla_free(dev_data->output, tio_status->spdm_out_size_max, true);
+	sla_free(dev_data->output, dev_data->output_len, true);
+	dev_data->output = SLA_NULL;
 }
 
 static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data)
@@ -674,7 +672,7 @@ static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data)
 	dev_data->output = sla_alloc(dev_data->output_len, true);
 
 	if (IS_SLA_NULL(dev_data->req) || IS_SLA_NULL(dev_data->resp) ||
-	    IS_SLA_NULL(dev_data->scratch) || IS_SLA_NULL(dev_data->dev_ctx)) {
+	    IS_SLA_NULL(dev_data->scratch) || IS_SLA_NULL(dev_data->output)) {
 		ret = -ENOMEM;
 		goto free_spdm_exit;
 	}
@@ -773,7 +771,8 @@ int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id,
 free_data_pg_exit:
 	snp_free_firmware_page(data_pg);
 free_ctx_exit:
-	sla_free(create.dev_ctx_sla, tio_status->devctx_size, true);
+	sla_free(dev_data->dev_ctx, tio_status->devctx_size, true);
+	dev_data->dev_ctx = SLA_NULL;
 	return ret;
 }
 
diff --git a/drivers/crypto/ccp/sev-dev-tio.h b/drivers/crypto/ccp/sev-dev-tio.h
index 67512b3dbc53..237da723483c 100644
--- a/drivers/crypto/ccp/sev-dev-tio.h
+++ b/drivers/crypto/ccp/sev-dev-tio.h
@@ -58,6 +58,11 @@ struct tio_dsm {
 	struct pci_tsm_pf0 tsm;
 	struct tsm_dsm_tio data;
 	struct sev_device *sev;
+	bool session_connected;
+	bool certificate_pending;
+	u16 certificate_request_len;
+	size_t certificate_offset;
+	int pending_spdm;
 };
 
 /* Data object IDs */
diff --git a/drivers/crypto/ccp/sev-dev-tsm.c b/drivers/crypto/ccp/sev-dev-tsm.c
index e905ed9de9b5..14d7ef84ee36 100644
--- a/drivers/crypto/ccp/sev-dev-tsm.c
+++ b/drivers/crypto/ccp/sev-dev-tsm.c
@@ -9,6 +9,7 @@
 #include <linux/pci-doe.h>
 #include <linux/bitfield.h>
 #include <linux/module.h>
+#include <linux/unaligned.h>
 
 #include <asm/sev-common.h>
 #include <asm/sev.h>
@@ -163,7 +164,7 @@ static int streams_register(struct pci_ide **ide)
 static void streams_unregister(struct pci_ide **ide)
 {
 	for (int i = 0; i < TIO_IDE_MAX_TC; ++i)
-		if (ide[i])
+		if (ide[i] && ide[i]->name)
 			pci_ide_stream_unregister(ide[i]);
 }
 
@@ -253,6 +254,7 @@ static void dsm_remove(struct pci_tsm *tsm)
 	if (is_pci_tsm_pf0(pdev)) {
 		struct tio_dsm *dsm = container_of(tsm, struct tio_dsm, tsm.base_tsm);
 
+		kvfree(tsm->evidence.obj[PCI_TSM_EVIDENCE_TYPE_CERT0].data);
 		pci_tsm_pf0_destructor(&dsm->tsm);
 		kfree(dsm);
 	}
@@ -283,12 +285,120 @@ static int dsm_create(struct tio_dsm *dsm)
 	return sev_tio_dev_create(&dsm->data, device_id, root_port_id, segment_id);
 }
 
+/* Only identity discovery requests may leave the host before acceptance. */
+static int dsm_identity_request(struct tio_dsm *dsm)
+{
+	struct tsm_spdm *spdm = &dsm->data.spdm;
+	const u8 *req = spdm->req;
+
+	if (spdm->req_len < 4)
+		return -EPROTO;
+
+	switch (req[1]) {
+	case 0x84: /* GET_VERSION */
+	case 0xe1: /* GET_CAPABILITIES */
+	case 0xe3: /* NEGOTIATE_ALGORITHMS */
+	case 0x81: /* GET_DIGESTS */
+		dsm->certificate_pending = false;
+		return 0;
+	case 0x82: /* GET_CERTIFICATE */
+		if (spdm->req_len < 8 || (req[2] & 0xf) ||
+		    get_unaligned_le16(req + 4) != dsm->certificate_offset)
+			return -EPROTO;
+		dsm->certificate_request_len = get_unaligned_le16(req + 6);
+		if (!dsm->certificate_request_len)
+			return -EPROTO;
+		dsm->certificate_pending = true;
+		return 0;
+	case 0xff: /* RESPOND_IF_READY preserves the pending certificate request. */
+		return 0;
+	default:
+		return -EPROTO;
+	}
+}
+
+static int dsm_cache_certificate(struct tio_dsm *dsm)
+{
+	struct pci_tsm_evidence_object *object =
+		&dsm->tsm.base_tsm.evidence.obj[PCI_TSM_EVIDENCE_TYPE_CERT0];
+	struct tsm_spdm *spdm = &dsm->data.spdm;
+	const u8 *rsp = spdm->rsp;
+	size_t portion, remainder, total;
+
+	if (spdm->rsp_len < 4)
+		return -EPROTO;
+	if (rsp[1] != 0x02) /* CERTIFICATE */
+		return 0;
+	if (!dsm->certificate_pending || spdm->rsp_len < 8 || (rsp[2] & 0xf))
+		return -EPROTO;
+
+	portion = get_unaligned_le16(rsp + 4);
+	remainder = get_unaligned_le16(rsp + 6);
+	total = dsm->certificate_offset + portion + remainder;
+	if (!portion || portion > dsm->certificate_request_len ||
+	    portion > spdm->rsp_len - 8 || total > U16_MAX)
+		return -EPROTO;
+
+	if (!object->data) {
+		object->data = kvmalloc(total, GFP_KERNEL);
+		if (!object->data)
+			return -ENOMEM;
+		object->len = total;
+	} else if (total != object->len) {
+		return -EPROTO;
+	}
+
+	memcpy((u8 *)object->data + dsm->certificate_offset, rsp + 8, portion);
+	dsm->certificate_offset += portion;
+	dsm->certificate_pending = false;
+	if (!remainder && (object->len < 4 ||
+			   get_unaligned_le16(object->data) != object->len))
+		return -EPROTO;
+
+	return 0;
+}
+
+static int dsm_collect_identity(struct tio_dsm *dsm, int ret)
+{
+	struct pci_tsm_evidence_object *object =
+		&dsm->tsm.base_tsm.evidence.obj[PCI_TSM_EVIDENCE_TYPE_CERT0];
+	struct tsm_spdm *spdm = &dsm->data.spdm;
+
+	while (ret == PCI_DOE_FEATURE_CMA || ret == PCI_DOE_FEATURE_SSESSION) {
+		/* Firmware consumed this exact chain; resume only after acceptance. */
+		if (object->len && dsm->certificate_offset == object->len) {
+			dsm->pending_spdm = ret;
+			return 0;
+		}
+		if (ret != PCI_DOE_FEATURE_CMA)
+			return -EPROTO;
+		ret = dsm_identity_request(dsm);
+		if (ret)
+			return ret;
+
+		ret = pci_doe(dsm->tsm.doe_mb, PCI_VENDOR_ID_PCI_SIG,
+			      PCI_DOE_FEATURE_CMA, spdm->req, spdm->req_len,
+			      spdm->rsp, spdm->rsp_len);
+		if (ret < 0)
+			return ret;
+		spdm->rsp_len = ret;
+		ret = dsm_cache_certificate(dsm);
+		if (ret)
+			return ret;
+		ret = sev_tio_continue(&dsm->data);
+	}
+
+	/* Connect must stop before session establishment, with a complete chain. */
+	return ret < 0 ? ret : -EPROTO;
+}
+
+static void dsm_disconnect(struct pci_dev *pdev);
+
 static int dsm_connect(struct pci_dev *pdev)
 {
 	struct tio_dsm *dsm = pdev_to_tio_dsm(pdev);
 	struct tsm_dsm_tio *dev_data = &dsm->data;
 	u8 ids[TIO_IDE_MAX_TC];
-	u8 tc_mask;
 	int ret;
 
 	if (pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
@@ -300,35 +410,54 @@ static int dsm_connect(struct pci_dev *pdev)
 	ret = stream_alloc(pdev, dev_data->ide, 0);
 	if (ret)
 		return ret;
-
 	ret = dsm_create(dsm);
 	if (ret)
-		goto ide_free_exit;
+		goto disconnect;
+
+	/* Reserve TC0's stream ID without programming or enabling IDE. */
+	memset(ids, 0xff, sizeof(ids));
+	ids[0] = dev_data->ide[0]->stream_id;
+	dev_data->cert_slot = 0;
+	ret = sev_tio_dev_connect(dev_data, BIT(0), ids, dev_data->cert_slot);
+	ret = dsm_collect_identity(dsm, ret);
+	if (!ret)
+		return 0;
+
+disconnect:
+	dsm_disconnect(pdev);
+	return ret;
+}
 
-	tc_mask = streams_setup(dev_data->ide, ids);
+static int dsm_evidence_accept(struct pci_dev *pdev)
+{
+	struct tio_dsm *dsm = pdev_to_tio_dsm(pdev);
+	struct tsm_dsm_tio *dev_data = &dsm->data;
+	u8 ids[TIO_IDE_MAX_TC];
+	int ret;
 
-	dev_data->cert_slot = pdev->tsm->evidence.slot;
-	ret = sev_tio_dev_connect(dev_data, tc_mask, ids, dev_data->cert_slot);
-	ret = sev_tio_spdm_cmd(dsm, ret);
-	if (ret)
-		goto free_exit;
+	if (pdev->tsm->evidence.slot)
+		return -EOPNOTSUPP;
+	if (!dsm->pending_spdm)
+		return -ENODATA;
 
-	streams_enable(dev_data->ide);
+	streams_setup(dev_data->ide, ids);
+	ret = sev_tio_spdm_cmd(dsm, dsm->pending_spdm);
+	dsm->pending_spdm = 0;
+	if (ret)
+		goto err_session;
+	dsm->session_connected = true;
 
+	ret = streams_enable(dev_data->ide);
+	if (ret)
+		goto err_session;
 	ret = streams_register(dev_data->ide);
 	if (ret)
-		goto free_exit;
-
+		goto err_session;
 	return 0;
 
-free_exit:
-	sev_tio_dev_reclaim(dev_data);
-
-	streams_disable(dev_data->ide);
-ide_free_exit:
-
-	streams_teardown(dev_data->ide);
-
+err_session:
+	/* Disconnect owns cleanup after firmware or IDE setup has started. */
+	pdev->tsm->error = true;
 	return ret;
 }
 
@@ -339,14 +468,20 @@ static void dsm_disconnect(struct pci_dev *pdev)
 	struct tsm_dsm_tio *dev_data = &dsm->data;
 	int ret;
 
-	ret = sev_tio_dev_disconnect(dev_data, force);
-	ret = sev_tio_spdm_cmd(dsm, ret);
-	if (ret && !force) {
-		ret = sev_tio_dev_disconnect(dev_data, true);
-		sev_tio_spdm_cmd(dsm, ret);
+	/* A paused or failed connect needs forced firmware cleanup. */
+	if (dev_data->reqbuf && dev_data->respbuf) {
+		force |= !dsm->session_connected;
+		ret = sev_tio_dev_disconnect(dev_data, force);
+		ret = sev_tio_spdm_cmd(dsm, ret);
+		if (ret && !force) {
+			ret = sev_tio_dev_disconnect(dev_data, true);
+			sev_tio_spdm_cmd(dsm, ret);
+		}
 	}
 
 	sev_tio_dev_reclaim(dev_data);
+	dsm->session_connected = false;
+	dsm->pending_spdm = 0;
 
 	streams_disable(dev_data->ide);
 	streams_unregister(dev_data->ide);
@@ -357,6 +492,8 @@ static struct pci_tsm_ops sev_tsm_ops = {
 	.probe = dsm_probe,
 	.remove = dsm_remove,
 	.connect = dsm_connect,
+	.evidence_accept = dsm_evidence_accept,
+	.evidence_types = BIT(PCI_TSM_EVIDENCE_TYPE_CERT0),
 	.disconnect = dsm_disconnect,
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance
  2026-10-28 15:26 [RFC PATCH v1 00/11] PCI/TSM: Add CTLV attestation Aneesh Kumar K.V (Arm)
                   ` (8 preceding siblings ...)
  2026-10-28 15:26 ` [RFC PATCH v1 09/11] crypto/ccp: Defer SEV-TIO link setup until evidence acceptance Aneesh Kumar K.V (Arm)
@ 2026-10-28 15:26 ` Aneesh Kumar K.V (Arm)
  2026-10-29 16:56   ` Dave Jiang
  2026-10-28 15:26 ` [RFC PATCH v1 11/11] virt: coco: arm-cca-guest: Refresh selected evidence against trusted digests Aneesh Kumar K.V (Arm)
  10 siblings, 1 reply; 17+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-10-28 15:26 UTC (permalink / raw)
  To: linux-coco
  Cc: Aneesh Kumar K.V (Arm), Jiri Pirko, Kevin Tian,
	Alexey Kardashevskiy, Xu Yilun, Catalin Marinas, Suzuki K Poulose,
	Steven Price, Kiryl Shutsemau, Rick Edgecombe, Dave Hansen,
	Jonathan Cameron, Jason Gunthorpe, Bjorn Helgaas, Ashish Kalra,
	Tom Lendacky, John Allen, Will Deacon, Mark Rutland, patches

Declare certificate slot zero as the CCA link backend evidence
capability. Limit connect to PDEV creation and slot-zero identity
collection. Copy the chain into the PCI evidence cache before publishing
the CTLV device.

Complete public-key submission, secure-session setup and stream
connection through evidence_accept after userspace verifies the chain.
Keep the evidence snapshot separate from the RMM communication cache and
release it on context removal.

With AMD converted in the previous patch, CCA is the last link backend
that still establishes streams during connect. Now require
evidence_accept for every link backend and initialize newly connected
contexts without active streams. Reject link providers that omit the
callback, and remove the legacy connect-time session and IDE exception
from the API comments and documentation. Connect remains required at
this point; a later patch allows shared-discovery backends to omit it.

Cc: Jiri Pirko <jiri@resnulli.us>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Jonathan Cameron <jic23@kernel.org>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 Documentation/ABI/testing/sysfs-bus-pci  |   2 +-
 Documentation/driver-api/attestation.rst |  30 +++++
 Documentation/driver-api/pci/tsm.rst     |  70 +++++++++--
 drivers/pci/tsm/core.c                   |   5 +-
 drivers/virt/coco/arm-cca-host/main.c    | 143 ++++++++++++++++-------
 drivers/virt/coco/arm-cca-host/rmi-da.c  |   7 +-
 drivers/virt/coco/arm-cca-host/rmi-da.h  |   2 +
 include/linux/pci-tsm.h                  |   6 +-
 samples/devsec/link_tsm.c                |  16 ++-
 9 files changed, 222 insertions(+), 59 deletions(-)

diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
index 56b1d92427ef..391807e609f9 100644
--- a/Documentation/ABI/testing/sysfs-bus-pci
+++ b/Documentation/ABI/testing/sysfs-bus-pci
@@ -639,7 +639,7 @@ Description:
 		device with that TSM and collect its supported identity evidence.
 		Reads return the associated TSM name, or an empty string.
 		Successful connect creates the CTLV attestation instance.
-		Backends may defer secure-session and stream setup until
+		Link backends defer secure-session and stream setup until
 		userspace verifies the chain and issues CTLV evidence-accept.
 
 What:		/sys/bus/pci/devices/.../tsm/disconnect
diff --git a/Documentation/driver-api/attestation.rst b/Documentation/driver-api/attestation.rst
index d23b7c65d35b..2ab1d2bf031e 100644
--- a/Documentation/driver-api/attestation.rst
+++ b/Documentation/driver-api/attestation.rst
@@ -130,6 +130,36 @@ The next kernel requires the same selector and restored lifetime, rebuilds
 deduplication from committed IMA journal entries, and never imports the legacy
 IMA measurement-list buffer for this mode.
 
+PCI host identity and acceptance
+================================
+
+Host connections use ``tsm/connect`` and ``tsm/disconnect`` in the PCI device's
+sysfs directory. Write the link TSM name, such as ``tsm0``, to either file.
+Backends declare the evidence objects and certificate slots they expose;
+DEVICE_GET advertises these capabilities and EVIDENCE_GET exports the cached
+evidence. The common provider does not restrict host certificates to a fixed
+slot.
+
+After verifying a chain, issue EVIDENCE_ACCEPT with the device and evidence
+generations returned by EVIDENCE_GET. The ``evidence.accept-object`` selector
+identifies the certificate slot and must belong to the retrieved snapshot.
+The link backend's ``evidence_accept`` callback receives the selected slot in
+``pci_tsm.evidence.slot`` and establishes the secure session and streams.
+Every link backend must provide this callback; connect only prepares identity
+evidence and must defer secure-session and IDE setup until acceptance.
+Successful acceptance makes the connection available to IOMMUFD consumers.
+Repeating acceptance does not allocate another stream, and selecting a
+different slot after stream setup returns EBUSY.
+
+EVIDENCE_ACCEPT_CLEAR clears the recorded verifier verdict only. It leaves an
+established stream active; accepting the evidence again reuses that stream.
+Writing the TSM name to ``tsm/disconnect`` releases either an identity-only
+connection or its stream and identity resources. It returns EBUSY while
+IOMMUFD vdevices hold connection contexts.
+
+See Documentation/driver-api/pci/tsm.rst for backend-specific behavior and
+``ctlvctl`` examples of evidence retrieval and acceptance.
+
 Simulation provider
 ===================
 
diff --git a/Documentation/driver-api/pci/tsm.rst b/Documentation/driver-api/pci/tsm.rst
index 28c9ec5dd4e6..03bee3098d19 100644
--- a/Documentation/driver-api/pci/tsm.rst
+++ b/Documentation/driver-api/pci/tsm.rst
@@ -30,6 +30,11 @@ backend registration alone do not create instances.
 Operation availability
 ======================
 
+Every link backend must provide ``evidence_accept`` for secure-session and
+IDE setup after userspace verification. The ``connect`` callback prepares
+identity evidence without establishing the secure link. It is required for
+link backends.
+
 CTLV provides DEVICE_GET, PCI STATE_GET, EVIDENCE_GET, EVIDENCE_ACCEPT,
 EVIDENCE_ACCEPT_CLEAR and DEVSEC_STATE_GET on established contexts. Backends
 declare the evidence objects and certificate slots they expose. The common
@@ -70,13 +75,64 @@ DSM BDF. The TSM ID is the numeric suffix of /sys/class/tsm/tsmN. PCI
 EVIDENCE_REFRESH requires ``pci.generation`` from STATE_GET; a mismatch returns
 ESTALE before invoking the native backend.
 
-For a link backend that defers secure-link setup until evidence acceptance,
-write its TSM name to the DSM's ``tsm/connect`` sysfs file to collect identity
-evidence. DEVICE_GET advertises the backend's available objects and certificate
-slots; EVIDENCE_GET retrieves the cached chain. After verifying it, issue
-EVIDENCE_ACCEPT with the returned device and evidence generations to complete
-session and IDE setup. Writing the same TSM name to ``tsm/disconnect`` releases
-the context, including an identity context that has not been accepted.
+The host writes a link TSM name to its DSM's ``tsm/connect`` sysfs file.
+Connect prepares an identity context and caches the evidence objects
+declared by the backend, without establishing a secure session or IDE
+streams. Use DEVICE_GET to discover the available objects and certificate
+slots, then use EVIDENCE_GET to retrieve an advertised ``cert-chain``
+instance. Verify the chain under the relying party's policy before issuing
+EVIDENCE_ACCEPT with the returned device and evidence generations.
+Acceptance completes session and stream setup. The authenticated attribute
+remains empty until setup succeeds.
+
+Use EVIDENCE_ACCEPT with ``evidence.accept-object`` selecting ``cert-chain``,
+format ``spdm-cert-chain``, and an advertised certificate slot as
+``evidence.blob-instance``. The selected object must have been retrieved in the
+referenced evidence generation; otherwise acceptance returns ESTALE.
+
+For example, use ``ctlvctl`` to export an advertised certificate chain (slot 0
+in this example)::
+
+  ctlvctl --spec-dir=./specs exec ctlv0 evidence-get --json '{
+    "evidence.object": [{
+      "evidence.blob-class": "cert-chain",
+      "evidence.format": "spdm-cert-chain",
+      "evidence.blob-instance": 0,
+      "evidence.representation": "raw",
+      "evidence.data": {
+        "file": "./cert0.bin", "data-buf-size": 16777216
+      }
+    }]
+  }'
+
+Verify ``cert0.bin`` under the relying party's policy before accepting it.
+Use the ``device.generation`` and ``evidence.generation`` values returned by
+EVIDENCE_GET. If they are 0 and 1 respectively::
+
+  ctlvctl --spec-dir=./specs exec ctlv0 evidence-accept --json '{
+    "device.generation": 0,
+    "evidence.generation": 1,
+    "evidence.accept-object": {
+      "evidence.blob-class": "cert-chain",
+      "evidence.format": "spdm-cert-chain",
+      "evidence.blob-instance": 0
+    }
+  }'
+
+The commands use ``ctlv0`` and ``./specs`` as examples; use the discovered CTLV
+instance and the directory containing the matching specification files.
+Generation pairs are scoped to an instance lifetime. Keep the same CTLV file
+descriptor open from evidence collection through acceptance in a production
+verifier, and discard its generation pair on close or ENODEV. The separate
+``ctlvctl`` invocations illustrate the request format and assume the instance
+remains unchanged between commands. ``ctlvctl`` exports evidence and submits
+the verdict; certificate verification is the relying party's responsibility.
+
+Write the same TSM name to ``tsm/disconnect`` to release identity and stream
+resources. Disconnect works before evidence acceptance too, and returns EBUSY
+while an IOMMUFD vdevice holds a connection context. EVIDENCE_ACCEPT_CLEAR only
+clears the verdict; disconnect owns stream teardown. VM assignment and native
+TDISP transport remain in IOMMUFD and the platform TSM driver.
 
 A guest verifier follows this sequence:
 
diff --git a/drivers/pci/tsm/core.c b/drivers/pci/tsm/core.c
index 170e043b1d3b..983463dd0811 100644
--- a/drivers/pci/tsm/core.c
+++ b/drivers/pci/tsm/core.c
@@ -261,7 +261,7 @@ static int pci_tsm_connect(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
 	if (rc)
 		return rc;
 
-	tsm_pf0->stream_connected = !ops->evidence_accept;
+	tsm_pf0->stream_connected = false;
 	pdev->tsm = no_free_ptr(pci_tsm);
 
 	/*
@@ -315,7 +315,8 @@ static bool pci_tsm_can_connect(struct tsm_dev *tsm_dev)
 {
 	const struct pci_tsm_ops *ops = tsm_dev ? tsm_dev->pci_ops : NULL;
 
-	return is_link_tsm(tsm_dev) && ops->remove && ops->connect && ops->disconnect;
+	return is_link_tsm(tsm_dev) && ops->remove && ops->connect &&
+		ops->evidence_accept && ops->disconnect;
 }
 
 static ssize_t connect_store(struct device *dev, struct device_attribute *attr,
diff --git a/drivers/virt/coco/arm-cca-host/main.c b/drivers/virt/coco/arm-cca-host/main.c
index 0bad6001654b..0f12353322fa 100644
--- a/drivers/virt/coco/arm-cca-host/main.c
+++ b/drivers/virt/coco/arm-cca-host/main.c
@@ -13,6 +13,8 @@
 #include <linux/tsm.h>
 #include <linux/cleanup.h>
 #include <linux/pci-doe.h>
+#include <linux/slab.h>
+#include <linux/string.h>
 
 #include "rmi-da.h"
 
@@ -66,6 +68,7 @@ static void cca_tsm_pci_remove(struct pci_tsm *tsm)
 	if (is_pci_tsm_pf0(pdev)) {
 		struct cca_host_pf0_ep_dsc *pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
 
+		kvfree(tsm->evidence.obj[PCI_TSM_EVIDENCE_TYPE_CERT0].data);
 		pci_tsm_pf0_destructor(&pf0_ep_dsc->pci);
 		kfree(pf0_ep_dsc);
 	} else {
@@ -212,7 +215,6 @@ static int pci_dev_addr_range(struct pci_dev *pdev, struct rmi_addr_range *pdev_
 static int cca_pdev_create_ncoh_stream(struct pci_dev *pdev, unsigned long stream_id)
 {
 	int ret;
-	long stream_handle;
 	struct cca_host_rp_dsc *rp_dsc;
 	struct rmi_pdev_stream_params *params;
 	struct pci_dev *rp = pcie_find_root_port(pdev);
@@ -245,11 +247,9 @@ static int cca_pdev_create_ncoh_stream(struct pci_dev *pdev, unsigned long strea
 	params->ide_sid = stream_id;
 	params->num_addr_range = pci_dev_addr_range(pdev, params->addr_range);
 
-	ret = cca_pdev_stream_connect(pdev, rp, params, &stream_handle);
-	if (ret)
+	ret = cca_pdev_stream_connect(pdev, rp, params, &pf0_ep_dsc->stream_handle);
+	if (ret && !pf0_ep_dsc->stream_created)
 		cca_root_port_pdev_put(rp_dsc);
-	else
-		pf0_ep_dsc->stream_handle = stream_handle;
 
 	free_page((unsigned long)params);
 	return ret;
@@ -258,7 +258,6 @@ static int cca_pdev_create_ncoh_stream(struct pci_dev *pdev, unsigned long strea
 static int cca_pdev_create_ncoh_sys_stream(struct pci_dev *pdev)
 {
 	int ret;
-	long stream_handle;
 	struct rmi_pdev_stream_params *params;
 	struct cca_host_pf0_ep_dsc *pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
 
@@ -273,9 +272,7 @@ static int cca_pdev_create_ncoh_sys_stream(struct pci_dev *pdev)
 	params->ide_sid = 0; /* ignored */
 	params->num_addr_range = pci_dev_addr_range(pdev, params->addr_range);
 
-	ret = cca_pdev_stream_connect(pdev, NULL, params, &stream_handle);
-	if (!ret)
-		pf0_ep_dsc->stream_handle = stream_handle;
+	ret = cca_pdev_stream_connect(pdev, NULL, params, &pf0_ep_dsc->stream_handle);
 
 	free_page((unsigned long)params);
 	return ret;
@@ -298,7 +295,65 @@ static inline bool cca_pdev_need_sel_ide_streams(struct pci_dev *pdev)
 	return pci_pcie_type(pdev) == PCI_EXP_TYPE_ENDPOINT;
 }
 
+static int cca_tsm_publish_certificate(struct pci_dev *pdev)
+{
+	struct cca_host_pf0_ep_dsc *pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
+	struct pci_tsm_evidence *evidence = &pdev->tsm->evidence;
+	struct pci_tsm_evidence_object *obj = &evidence->obj[PCI_TSM_EVIDENCE_TYPE_CERT0];
+	struct cache_object *cache;
+	void *data;
+
+	/* On-chip endpoints can connect without SPDM identity collection. */
+	if (!pf0_ep_dsc->pci.doe_mb)
+		return 0;
+
+	guard(mutex)(&pf0_ep_dsc->pdev.object_lock);
+	cache = pf0_ep_dsc->cert_chain.cache;
+	if (!cache || !cache->offset)
+		return -ENODATA;
+
+	/* Later RMM communication may reallocate the original cache. */
+	data = kvmemdup(cache->buf, cache->offset, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	guard(rwsem_write)(&evidence->lock);
+	obj->data = data;
+	obj->len = cache->offset;
+	return 0;
+}
+
 static int cca_tsm_connect(struct pci_dev *pdev)
+{
+	struct cca_host_pf0_ep_dsc *pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
+	int ret;
+
+	ret = init_dev_communication_buffers(pdev, &pf0_ep_dsc->pdev.comm_data);
+	if (ret)
+		return ret;
+
+	ret = cca_pdev_create(pdev);
+	if (ret)
+		goto err_pdev_create;
+
+	ret = cca_pdev_collect_identity(pdev);
+	if (ret)
+		goto pdev_destroy;
+
+	ret = cca_tsm_publish_certificate(pdev);
+	if (ret)
+		goto pdev_destroy;
+
+	return 0;
+
+pdev_destroy:
+	cca_pdev_stop_and_destroy(pdev);
+err_pdev_create:
+	free_dev_communication_buffers(&pf0_ep_dsc->pdev.comm_data);
+	return ret;
+}
+
+static int cca_tsm_evidence_accept(struct pci_dev *pdev)
 {
 	struct pci_dev *rp = pcie_find_root_port(pdev);
 	struct cca_host_pf0_ep_dsc *pf0_ep_dsc;
@@ -309,9 +364,18 @@ static int cca_tsm_connect(struct pci_dev *pdev)
 	if (WARN_ON(!is_pci_tsm_pf0(pdev)))
 		return -EIO;
 
+	/* Connect collected identity from slot zero for this PDEV. */
+	if (pdev->tsm->evidence.slot != 0)
+		return -EOPNOTSUPP;
+
 	pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
-	if (cca_pdev_need_sel_ide_streams(pdev)) {
+	if (cca_pdev_needs_key(pdev)) {
+		ret = cca_pdev_set_public_key(pdev);
+		if (ret)
+			goto err_key;
+	}
 
+	if (cca_pdev_need_sel_ide_streams(pdev)) {
 		ide = pci_ide_stream_alloc(pdev);
 		if (!ide) {
 			ret = -ENOMEM;
@@ -344,42 +408,26 @@ static int cca_tsm_connect(struct pci_dev *pdev)
 			goto err_tsm;
 	}
 
-	ret = init_dev_communication_buffers(pdev, &pf0_ep_dsc->pdev.comm_data);
-	if (ret)
-		goto err_comm_buff;
-	ret = cca_pdev_create(pdev);
-	if (ret)
-		goto err_pdev_create;
-
-	ret = cca_pdev_collect_identity(pdev);
-	if (ret)
-		goto pdev_destroy;
-
-	if (cca_pdev_needs_key(pdev)) {
-		ret = cca_pdev_set_public_key(pdev);
-		if (ret)
-			goto pdev_destroy;
-	}
 	/* Create IDE streams */
 	ret = cca_pdev_create_streams(pdev, stream_id);
 	if (ret)
-		goto pdev_destroy;
+		goto err_stream_connect;
 	/*
 	 * Once ide is setup, enable the stream at the endpoint
 	 * Root port will be done by RMM
 	 */
-	if (cca_pdev_need_sel_ide_streams(pdev))
-		pci_ide_stream_enable(pdev, ide);
+	if (cca_pdev_need_sel_ide_streams(pdev)) {
+		ret = pci_ide_stream_enable(pdev, ide);
+		if (ret)
+			goto err_stream_connect;
+	}
 
 	return 0;
 
-pdev_destroy:
-	cca_pdev_stop_and_destroy(pdev);
-err_pdev_create:
-	free_dev_communication_buffers(&pf0_ep_dsc->pdev.comm_data);
-err_comm_buff:
-	if (cca_pdev_need_sel_ide_streams(pdev))
-		tsm_ide_stream_unregister(ide);
+err_stream_connect:
+	/* Retain partial RMM/IDE state for sysfs disconnect to release. */
+	pdev->tsm->error = true;
+	return ret;
 err_tsm:
 	if (cca_pdev_need_sel_ide_streams(pdev)) {
 		pci_ide_stream_teardown(rp, ide);
@@ -391,7 +439,9 @@ static int cca_tsm_connect(struct pci_dev *pdev)
 		pci_ide_stream_free(ide);
 	pf0_ep_dsc->sel_stream = NULL;
 err_stream_alloc:
-
+	return ret;
+err_key:
+	pdev->tsm->error = true;
 	return ret;
 }
 
@@ -405,26 +455,33 @@ static void cca_tsm_disconnect(struct pci_dev *pdev)
 	if (!pf0_ep_dsc)
 		return;
 
-	if (cca_pdev_need_sel_ide_streams(pdev))
-		ide = pf0_ep_dsc->sel_stream;
+	ide = pf0_ep_dsc->sel_stream;
+	if (pf0_ep_dsc->stream_created) {
+		if (!cca_pdev_need_sel_ide_streams(pdev))
+			rp = NULL;
 
-	cca_pdev_disconnect_stream(pdev, rp, pf0_ep_dsc->stream_handle);
-	if (rp)
-		cca_root_port_pdev_put(to_cca_rp_dsc(rp));
+		cca_pdev_disconnect_stream(pdev, rp, pf0_ep_dsc->stream_handle);
+		if (rp)
+			cca_root_port_pdev_put(to_cca_rp_dsc(rp));
+
+		pf0_ep_dsc->stream_created = false;
+	}
 
 	cca_pdev_stop_and_destroy(pdev);
 	free_dev_communication_buffers(&pf0_ep_dsc->pdev.comm_data);
 
-	if (cca_pdev_need_sel_ide_streams(pdev)) {
+	if (ide) {
 		pci_ide_stream_release(ide);
 		pf0_ep_dsc->sel_stream = NULL;
 	}
 }
 
 static struct pci_tsm_ops cca_link_pci_ops = {
+	.evidence_types = BIT(PCI_TSM_EVIDENCE_TYPE_CERT0),
 	.probe = cca_tsm_pci_probe,
 	.remove = cca_tsm_pci_remove,
 	.connect = cca_tsm_connect,
+	.evidence_accept = cca_tsm_evidence_accept,
 	.disconnect = cca_tsm_disconnect,
 	.owner = THIS_MODULE,
 	.viommu_get_ops = cca_viommu_get_ops,
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.c b/drivers/virt/coco/arm-cca-host/rmi-da.c
index c4f505753bf9..d18812b91c31 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.c
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.c
@@ -92,8 +92,9 @@ static int init_pdev_params(struct pci_dev *pdev, struct rmi_pdev_params *params
 	params->pdev_id = rid;
 	params->hb_base = cfg->res.start;
 	params->routing_id = pci_ide_segment(pdev);
-	/* slot number for certificate chain default to zero */
-	params->id_index = 0;
+	/* Root ports do not use an SPDM certificate chain. */
+	if (params->flags & RMI_PDEV_FLAGS_SPDM)
+		params->id_index = 0;
 	params->hash_algo = RMI_HASH_SHA_256;
 	/* no multi function device here. */
 	params->rid_base = rid;
@@ -921,6 +922,8 @@ int cca_pdev_stream_connect(struct pci_dev *pdev1, struct pci_dev *pdev2,
 	if (rmi_pdev_stream_connect(stream_params_phys, stream_handle))
 		return -EIO;
 
+	/* Keep the handle and peer alive even if communication fails below. */
+	to_cca_pf0_ep_dsc(pdev1)->stream_created = true;
 	return submit_stream_work(pdev1, pdev2, *stream_handle);
 }
 
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.h b/drivers/virt/coco/arm-cca-host/rmi-da.h
index 00a68afc0cb8..2024ca6cb9f3 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.h
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.h
@@ -87,6 +87,7 @@ struct cca_host_pdev_dsc {
  * @pdev: pdev communication context
  * @sel_stream: Selective IDE Stream descriptor
  * @stream_handle: Stream handle returned by stream connect
+ * @stream_created: RMM allocated a stream, including incomplete setup
  * @rmi_signature_algorithm: Signature algorithm used for public key
  * @cert_chain: cetrificate chain
  * @vca: SPDM's Version-Capabilities-Algorithms cache object
@@ -96,6 +97,7 @@ struct cca_host_pf0_ep_dsc {
 	struct cca_host_pdev_dsc pdev;
 	struct pci_ide *sel_stream;
 	unsigned long stream_handle;
+	bool stream_created;
 
 	char *rmi_signature_algorithm;
 	struct {
diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
index 8fd209a2c32b..a2b439edbd0c 100644
--- a/include/linux/pci-tsm.h
+++ b/include/linux/pci-tsm.h
@@ -88,11 +88,11 @@ struct pci_tsm_ops {
 	 * @probe: establish context with the TSM (allocate / wrap 'struct
 	 *	   pci_tsm') for follow-on link operations
 	 * @remove: destroy link operations context
-	 * @connect: collect device identity; required for link backends; legacy
-	 *	     backends may also establish IDE
+	 * @connect: collect device identity; required for link backends;
+	 *	     session and IDE setup must wait for @evidence_accept
 	 * @evidence_accept: establish the secure session and IDE after evidence
 	 *		  acceptance, using pci_tsm::evidence.slot as the selected
-	 *		  certificate slot; optional for legacy backends
+	 *		  certificate slot; required for link backends
 	 * @disconnect: teardown the secure link
 	 *
 	 * Context: All callbacks run under pci_tsm_rwsem held for write to sync
diff --git a/samples/devsec/link_tsm.c b/samples/devsec/link_tsm.c
index 61cf6c16e1b5..a6644b276a88 100644
--- a/samples/devsec/link_tsm.c
+++ b/samples/devsec/link_tsm.c
@@ -132,6 +132,10 @@ static void devsec_link_tsm_pci_remove(struct pci_tsm *tsm)
 static DECLARE_BITMAP(devsec_stream_ids, NR_TSM_STREAMS);
 static struct pci_ide *devsec_streams[NR_TSM_STREAMS];
 
+static bool fail_stream_connect;
+module_param(fail_stream_connect, bool, 0600);
+MODULE_PARM_DESC(fail_stream_connect, "Fail stream setup after identity collection");
+
 static unsigned long *alloc_devsec_stream_id(unsigned long *stream_id)
 {
 	unsigned long id;
@@ -146,7 +150,7 @@ static unsigned long *alloc_devsec_stream_id(unsigned long *stream_id)
 DEFINE_FREE(free_devsec_stream, unsigned long *, if (_T) clear_bit(*_T, devsec_stream_ids))
 
 /*
- * Reference consumer for a TSM driver "connect" operation callback. The
+ * Reference consumer for a TSM driver "evidence_accept" operation callback. The
  * low-level TSM driver understands details about the platform the PCI
  * core does not, like number of available streams that can be
  * established per host bridge. The expected flow is:
@@ -168,11 +172,20 @@ DEFINE_FREE(free_devsec_stream, unsigned long *, if (_T) clear_bit(*_T, devsec_s
  * or use model sequencing.
  */
 static int devsec_link_tsm_connect(struct pci_dev *pdev)
+{
+	/* probe() populated the synthetic identity; allocate streams on accept. */
+	return 0;
+}
+
+static int devsec_link_tsm_evidence_accept(struct pci_dev *pdev)
 {
 	struct pci_dev *rp = pcie_find_root_port(pdev);
 	unsigned long __stream_id;
 	int rc;
 
+	if (fail_stream_connect)
+		return -EIO;
+
 	dev_dbg(pci_tsm_host(pdev), "%s\n", pci_name(pdev));
 
 	unsigned long *stream_id __free(free_devsec_stream) =
@@ -235,6 +248,7 @@ static struct pci_tsm_ops devsec_link_pci_ops = {
 	.probe = devsec_link_tsm_pci_probe,
 	.remove = devsec_link_tsm_pci_remove,
 	.connect = devsec_link_tsm_connect,
+	.evidence_accept = devsec_link_tsm_evidence_accept,
 	.disconnect = devsec_link_tsm_disconnect,
 };
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* [RFC PATCH v1 11/11] virt: coco: arm-cca-guest: Refresh selected evidence against trusted digests
  2026-10-28 15:26 [RFC PATCH v1 00/11] PCI/TSM: Add CTLV attestation Aneesh Kumar K.V (Arm)
                   ` (9 preceding siblings ...)
  2026-10-28 15:26 ` [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance Aneesh Kumar K.V (Arm)
@ 2026-10-28 15:26 ` Aneesh Kumar K.V (Arm)
  10 siblings, 0 replies; 17+ messages in thread
From: Aneesh Kumar K.V (Arm) @ 2026-10-28 15:26 UTC (permalink / raw)
  To: linux-coco
  Cc: Aneesh Kumar K.V (Arm), Jiri Pirko, Kevin Tian,
	Alexey Kardashevskiy, Xu Yilun, Catalin Marinas, Suzuki K Poulose,
	Steven Price, Kiryl Shutsemau, Rick Edgecombe, Dave Hansen,
	Jonathan Cameron, Jason Gunthorpe, Bjorn Helgaas, Ashish Kalra,
	Tom Lendacky, John Allen, Will Deacon, Mark Rutland, patches

Refresh either measurements with a 32-byte challenge or the interface
report without one. Reject other objects and unsupported flags before
issuing native requests.

Verify refreshed bytes against the trusted digest, hash algorithm,
certificate slot and lock sequence. Check that the other cached object's
sequence is unchanged, then replace only the verified selected cache
entry. Keep the previous cache intact on failure and document refresh
constraints and generation invalidation.

Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Jonathan Cameron <jic23@kernel.org>
Cc: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 drivers/virt/coco/arm-cca-guest/main.c | 134 +++++++++++++++----------
 1 file changed, 83 insertions(+), 51 deletions(-)

diff --git a/drivers/virt/coco/arm-cca-guest/main.c b/drivers/virt/coco/arm-cca-guest/main.c
index f50f3e660bbc..d9de5d6c0e37 100644
--- a/drivers/virt/coco/arm-cca-guest/main.c
+++ b/drivers/virt/coco/arm-cca-guest/main.c
@@ -174,86 +174,94 @@ static void unregister_cca_tsm_report(void *data)
 
 #ifdef CONFIG_PCI_TSM
 
-static int __maybe_unused
-cca_update_dev_measurements(struct pci_dev *pdev, const u8 *nonce)
+static int cca_refresh_dev_evidence(struct pci_dev *pdev,
+		enum pci_tsm_evidence_type type, const u8 *nonce)
 {
-	int ret;
-	void *measurements;
-	size_t measurements_size;
-	int vdev_id = rsi_vdev_id(pdev);
-	struct pci_tsm_evidence *evidence;
-	struct rsi_vdevice_info *dev_info;
-	struct pci_tsm_evidence_object *obj;
 	struct cca_guest_dsc *dsc = to_cca_guest_dsc(pdev);
+	struct pci_tsm_evidence *evidence = &dsc->pci.base_tsm.evidence;
+	struct pci_tsm_evidence_object *obj = &evidence->obj[type];
+	struct rsi_vdevice_info *dev_info;
+	bool report = type == PCI_TSM_EVIDENCE_TYPE_REPORT;
+	int vdev_id = rsi_vdev_id(pdev);
+	void *data;
+	u8 *digest;
+	u8 expected_hash_algo;
+	size_t size;
+	int object_type;
+	int ret;
 
-	/* Regenerate the measurement from the device */
-	ret = rhi_update_vdev_measurements_cache(pdev, nonce);
-	if (ret) {
-		pci_err(pdev, "failed to update device measurements from device (%d)\n", ret);
-		return ret;
+	if (report) {
+		object_type = RHI_DA_OBJECT_INTERFACE_REPORT;
+		ret = rhi_update_vdev_interface_report_cache(pdev);
+	} else {
+		object_type = RHI_DA_OBJECT_MEASUREMENT;
+		ret = rhi_update_vdev_measurements_cache(pdev, nonce);
 	}
-
-	ret = rhi_read_cached_object(vdev_id, RHI_DA_OBJECT_MEASUREMENT,
-				     &measurements, &measurements_size);
-	if (ret) {
-		pci_err(pdev, "failed to get device measurements from the host (%d)\n", ret);
+	if (ret)
 		return ret;
-	}
 
+	ret = rhi_read_cached_object(vdev_id, object_type, &data, &size);
+	if (ret)
+		return ret;
 	dev_info = kmalloc(sizeof(*dev_info), GFP_KERNEL);
 	if (!dev_info) {
 		ret = -ENOMEM;
-		goto free_measurements;
+		goto free_data;
 	}
-
 	if (rsi_vdev_get_info(vdev_id, virt_to_phys(dev_info))) {
-		pci_err(pdev, "failed to get device digests (%d)\n", ret);
 		ret = -EIO;
 		goto free_dev_info;
 	}
 
-	/* Make sure no unexpected lock/unlock operation happened from guest */
-	if (dsc->dev_info.lock_seq != dev_info->lock_seq) {
-		pci_err(pdev, "Unexpected lock/unlock operation from host (%d)\n", ret);
+	if (evidence->digest_algo == HASH_ALGO_SHA256)
+		expected_hash_algo = RSI_HASH_SHA_256;
+	else
+		expected_hash_algo = RSI_HASH_SHA_512;
+
+	/* Keep every cached object bound to the same locked device. */
+	if (dev_info->lock_seq != dsc->dev_info.lock_seq || dev_info->id_index ||
+	    dev_info->hash_algo != expected_hash_algo) {
 		ret = -EIO;
 		goto free_dev_info;
 	}
 
-	/*
-	 * Verify that the digests of the provided reports match with the
-	 * digests from RMM
-	 */
-	ret = cca_verify_digest(dev_info->hash_algo, measurements,
-				measurements_size, dev_info->meas_digest);
-	if (ret) {
-		pci_err(pdev, "RMM provided digest mismatch (%d)\n", ret);
-		goto free_dev_info;
+	if (report) {
+		if (dev_info->meas_seq != dsc->dev_info.meas_seq) {
+			ret = -EIO;
+			goto free_dev_info;
+		}
+		digest = dev_info->report_digest;
+	} else {
+		if (dev_info->report_seq != dsc->dev_info.report_seq) {
+			ret = -EIO;
+			goto free_dev_info;
+		}
+		digest = dev_info->meas_digest;
 	}
 
-	/* fill evidence details */
-	evidence = &dsc->pci.base_tsm.evidence;
+	ret = cca_verify_digest(dev_info->hash_algo, data, size, digest);
+	if (ret)
+		goto free_dev_info;
 
-	/* Now update the evidence under lock. */
+	/* Publish only after verification; failures preserve the old cache. */
 	down_write(&evidence->lock);
-	evidence->generation = dev_info->meas_seq;
-
-	obj = &evidence->obj[PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS];
-	if (obj->data)
-		kvfree(obj->data);
-	obj->data = measurements;
-	obj->len = measurements_size;
-
-	dsc->dev_info.meas_seq = dev_info->meas_seq;
-	memcpy(dsc->dev_info.meas_digest, dev_info->meas_digest, SHA512_DIGEST_SIZE);
+	kvfree(obj->data);
+	obj->data = data;
+	obj->len = size;
+	memcpy(obj->digest, digest, SHA512_DIGEST_SIZE);
+	if (report)
+		dsc->dev_info.report_seq = dev_info->report_seq;
+	else
+		dsc->dev_info.meas_seq = dev_info->meas_seq;
+	evidence->generation++;
 	up_write(&evidence->lock);
-
 	kfree(dev_info);
 	return 0;
 
 free_dev_info:
 	kfree(dev_info);
-free_measurements:
-	kvfree(measurements);
+free_data:
+	kvfree(data);
 	return ret;
 }
 
@@ -506,6 +514,29 @@ static int cca_tsm_accept(struct pci_dev *pdev)
 	return __cca_tsm_accept(pdev, dsc->dev_info.lock_seq);
 }
 
+static int cca_tsm_refresh_evidence(struct pci_tsm *tsm,
+		enum pci_tsm_evidence_type type, unsigned long flags,
+		void *nonce, size_t nonce_len)
+{
+	if (flags)
+		return -EOPNOTSUPP;
+
+	switch (type) {
+	case PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS:
+		if (nonce_len != 32)
+			return -EINVAL;
+		break;
+	case PCI_TSM_EVIDENCE_TYPE_REPORT:
+		if (nonce_len)
+			return -EINVAL;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return cca_refresh_dev_evidence(tsm->pdev, type, nonce);
+}
+
 static struct pci_tsm_ops cca_devsec_pci_ops = {
 	.owner = THIS_MODULE,
 	.evidence_types = BIT(PCI_TSM_EVIDENCE_TYPE_CERT0) |
@@ -515,6 +546,7 @@ static struct pci_tsm_ops cca_devsec_pci_ops = {
 	.lock = cca_tsm_lock,
 	.unlock = cca_tsm_unlock,
 	.accept	 = cca_tsm_accept,
+	.refresh_evidence = cca_tsm_refresh_evidence,
 };
 
 static void cca_devsec_tsm_remove(void *tsm_dev)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 17+ messages in thread

* Re: [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts
  2026-10-28 15:26 ` [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts Aneesh Kumar K.V (Arm)
@ 2026-10-28 22:56   ` Dave Jiang
  2026-10-29  5:30     ` Aneesh Kumar K.V
  0 siblings, 1 reply; 17+ messages in thread
From: Dave Jiang @ 2026-10-28 22:56 UTC (permalink / raw)
  To: Aneesh Kumar K.V (Arm), linux-coco
  Cc: Jiri Pirko, Kevin Tian, Alexey Kardashevskiy, Xu Yilun,
	Catalin Marinas, Suzuki K Poulose, Steven Price, Kiryl Shutsemau,
	Rick Edgecombe, Dave Hansen, Jonathan Cameron, Jason Gunthorpe,
	Bjorn Helgaas, Ashish Kalra, Tom Lendacky, John Allen,
	Will Deacon, Mark Rutland, patches

On 10/28/26 8:26 AM, Aneesh Kumar K.V (Arm) wrote:

< -- snip -->

> 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/
[2] https://lore.kernel.org/all/20260924041032.1096569-1-zhenzhong.duan@intel.com/
[3] https://lore.kernel.org/all/20260916115159.1938195-16-aik@amd.com/
[4] https://lore.kernel.org/all/20260705220819.2472765-1-djbw@kernel.org/

DJ


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts
  2026-10-28 22:56   ` Dave Jiang
@ 2026-10-29  5:30     ` Aneesh Kumar K.V
  2026-10-29 12:41       ` Jason Gunthorpe
  0 siblings, 1 reply; 17+ messages in thread
From: Aneesh Kumar K.V @ 2026-10-29  5:30 UTC (permalink / raw)
  To: Dave Jiang, linux-coco, Leon Romanovsky
  Cc: Jiri Pirko, Kevin Tian, Alexey Kardashevskiy, Xu Yilun,
	Catalin Marinas, Suzuki K Poulose, Steven Price, Kiryl Shutsemau,
	Rick Edgecombe, Dave Hansen, Jonathan Cameron, Jason Gunthorpe,
	Bjorn Helgaas, Ashish Kalra, Tom Lendacky, John Allen,
	Will Deacon, Mark Rutland, patches

Dave Jiang <dave.jiang@intel.com> writes:

> On 10/28/26 8:26 AM, Aneesh Kumar K.V (Arm) wrote:
>

 [ ... 232 lines skipped ... ] 

>> +	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?
>

Yes. The generic layer treats an evidence refresh failure as fatal. The
backend can decide how to present that failure.

>
> 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?
>

For the CCA backend, evidence is replaced only after successful
validation. If validation fails, the cached evidence remains unchanged.
However, we could instead discard the existing evidence cache when a
refresh starts.

>
> 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/
> [2] https://lore.kernel.org/all/20260924041032.1096569-1-zhenzhong.duan@intel.com/
> [3] https://lore.kernel.org/all/20260916115159.1938195-16-aik@amd.com/
> [4] https://lore.kernel.org/all/20260705220819.2472765-1-djbw@kernel.org/
>

The include/linux/attestation.h do cover

enum attestation_device_trust_state {
	ATTESTATION_DEVICE_TRUST_UNSET =
		CTLV_ATTESTATION_DEVICE_TRUST_STATE_UNSET,
	ATTESTATION_DEVICE_TRUST_NONE =
		CTLV_ATTESTATION_DEVICE_TRUST_STATE_NONE,
	ATTESTATION_DEVICE_TRUST_ADVERSARY =
		CTLV_ATTESTATION_DEVICE_TRUST_STATE_ADVERSARY,
	ATTESTATION_DEVICE_TRUST_AUTO =
		CTLV_ATTESTATION_DEVICE_TRUST_STATE_AUTO,
	ATTESTATION_DEVICE_TRUST_TCB = CTLV_ATTESTATION_DEVICE_TRUST_STATE_TCB,
};

I am still unclear how this will interact with generic device trust. We
can only determine that after reviewing the device trust series.

Leon,
are there any plans to post it?

For the current PCI/TSM CTLV implementation, however, it makes sense to
treat evidence as local to the TSM and keep it in pci_tsm_evidence.

-aneesh

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts
  2026-10-29  5:30     ` Aneesh Kumar K.V
@ 2026-10-29 12:41       ` Jason Gunthorpe
  0 siblings, 0 replies; 17+ messages in thread
From: Jason Gunthorpe @ 2026-10-29 12:41 UTC (permalink / raw)
  To: Aneesh Kumar K.V
  Cc: Dave Jiang, linux-coco, Leon Romanovsky, Jiri Pirko, Kevin Tian,
	Alexey Kardashevskiy, Xu Yilun, Catalin Marinas, Suzuki K Poulose,
	Steven Price, Kiryl Shutsemau, Rick Edgecombe, Dave Hansen,
	Jonathan Cameron, Bjorn Helgaas, Ashish Kalra, Tom Lendacky,
	John Allen, Will Deacon, Mark Rutland, patches

On Thu, Oct 29, 2026 at 11:00:03AM +0530, Aneesh Kumar K.V wrote:
> Dave Jiang <dave.jiang@intel.com> writes:
> 
> > On 10/28/26 8:26 AM, Aneesh Kumar K.V (Arm) wrote:
> >
> 
>  [ ... 232 lines skipped ... ] 
> 
> >> +	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?
> 
> Yes. The generic layer treats an evidence refresh failure as fatal. The
> backend can decide how to present that failure.

The locked generation scheme should fully protect against any desync
issues though? Do we need to care here?

I also continue to dislike the use of the word 'cached' here, why is
this like this? If someone wants evidence they should get the fresh
evidence with their nonce returned. I can understand hanging on to
certificates and other unsigned data but then what would even trigger
a refresh of that? Something you can't refresh isn't a cache, it is
just the immutable data.

Needs some more explanation I suppose, or maybe I haven't found the
explanation yet.

> > 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?
> 
> For the CCA backend, evidence is replaced only after successful
> validation. If validation fails, the cached evidence remains unchanged.
> However, we could instead discard the existing evidence cache when a
> refresh starts.

What validation?

> > 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/
> > [2] https://lore.kernel.org/all/20260924041032.1096569-1-zhenzhong.duan@intel.com/
> > [3] https://lore.kernel.org/all/20260916115159.1938195-16-aik@amd.com/
> > [4] https://lore.kernel.org/all/20260705220819.2472765-1-djbw@kernel.org/
> >
> 
> The include/linux/attestation.h do cover
> 
> enum attestation_device_trust_state {
> 	ATTESTATION_DEVICE_TRUST_UNSET =
> 		CTLV_ATTESTATION_DEVICE_TRUST_STATE_UNSET,
> 	ATTESTATION_DEVICE_TRUST_NONE =
> 		CTLV_ATTESTATION_DEVICE_TRUST_STATE_NONE,
> 	ATTESTATION_DEVICE_TRUST_ADVERSARY =
> 		CTLV_ATTESTATION_DEVICE_TRUST_STATE_ADVERSARY,
> 	ATTESTATION_DEVICE_TRUST_AUTO =
> 		CTLV_ATTESTATION_DEVICE_TRUST_STATE_AUTO,
> 	ATTESTATION_DEVICE_TRUST_TCB = CTLV_ATTESTATION_DEVICE_TRUST_STATE_TCB,
> };
> 
> I am still unclear how this will interact with generic device trust. We
> can only determine that after reviewing the device trust series.

I don't know why attestation subsystem would have an opinion or
interfaces for device trust?

Jason

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance
  2026-10-28 15:26 ` [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance Aneesh Kumar K.V (Arm)
@ 2026-10-29 16:56   ` Dave Jiang
  2026-10-29 17:10     ` Jason Gunthorpe
  0 siblings, 1 reply; 17+ messages in thread
From: Dave Jiang @ 2026-10-29 16:56 UTC (permalink / raw)
  To: Aneesh Kumar K.V (Arm), linux-coco
  Cc: Jiri Pirko, Kevin Tian, Alexey Kardashevskiy, Xu Yilun,
	Catalin Marinas, Suzuki K Poulose, Steven Price, Kiryl Shutsemau,
	Rick Edgecombe, Dave Hansen, Jonathan Cameron, Jason Gunthorpe,
	Bjorn Helgaas, Ashish Kalra, Tom Lendacky, John Allen,
	Will Deacon, Mark Rutland, patches



On 10/28/26 8:26 AM, Aneesh Kumar K.V (Arm) wrote:
> Declare certificate slot zero as the CCA link backend evidence
> capability. Limit connect to PDEV creation and slot-zero identity
> collection. Copy the chain into the PCI evidence cache before publishing
> the CTLV device.
> 
> Complete public-key submission, secure-session setup and stream
> connection through evidence_accept after userspace verifies the chain.
> Keep the evidence snapshot separate from the RMM communication cache and
> release it on context removal.
> 
> With AMD converted in the previous patch, CCA is the last link backend
> that still establishes streams during connect. Now require
> evidence_accept for every link backend and initialize newly connected
> contexts without active streams. Reject link providers that omit the
> callback, and remove the legacy connect-time session and IDE exception
> from the API comments and documentation. Connect remains required at
> this point; a later patch allows shared-discovery backends to omit it.

TDX Connect is another one. The TDX host link [1] sets up the SPDM session and then the IDE streams in its connect (tdx_tsm_link_connect(),
patches 27 and 30).

It can't defer the session. The TDX Module runs the whole SPDM session setup inside TDH.SPDM.CONNECT, and the host relays its DOE messages
without interpreting them, so there is no defined point to stop after the certificates. The chains come back in the device info only once TDH.SPDM.CONNECT completes (TDX Connect ABI Reference Spec 2.1 v0.66, 4.2.20.1).

For TDX, the chain can be checked after connect instead. A connected link doesn't make the device trusted by a guest. The guest still verifies the device before it accepts it. Could evidence_accept stay optional?

[1] https://lore.kernel.org/all/20260327160132.2946114-1-yilun.xu@linux.intel.com/

DJ

> 
> Cc: Jiri Pirko <jiri@resnulli.us>
> Cc: Bjorn Helgaas <bhelgaas@google.com>
> Cc: Jonathan Cameron <jic23@kernel.org>
> Cc: Jason Gunthorpe <jgg@nvidia.com>
> Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
> ---
>  Documentation/ABI/testing/sysfs-bus-pci  |   2 +-
>  Documentation/driver-api/attestation.rst |  30 +++++
>  Documentation/driver-api/pci/tsm.rst     |  70 +++++++++--
>  drivers/pci/tsm/core.c                   |   5 +-
>  drivers/virt/coco/arm-cca-host/main.c    | 143 ++++++++++++++++-------
>  drivers/virt/coco/arm-cca-host/rmi-da.c  |   7 +-
>  drivers/virt/coco/arm-cca-host/rmi-da.h  |   2 +
>  include/linux/pci-tsm.h                  |   6 +-
>  samples/devsec/link_tsm.c                |  16 ++-
>  9 files changed, 222 insertions(+), 59 deletions(-)
> 
> diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
> index 56b1d92427ef..391807e609f9 100644
> --- a/Documentation/ABI/testing/sysfs-bus-pci
> +++ b/Documentation/ABI/testing/sysfs-bus-pci
> @@ -639,7 +639,7 @@ Description:
>  		device with that TSM and collect its supported identity evidence.
>  		Reads return the associated TSM name, or an empty string.
>  		Successful connect creates the CTLV attestation instance.
> -		Backends may defer secure-session and stream setup until
> +		Link backends defer secure-session and stream setup until
>  		userspace verifies the chain and issues CTLV evidence-accept.
>  
>  What:		/sys/bus/pci/devices/.../tsm/disconnect
> diff --git a/Documentation/driver-api/attestation.rst b/Documentation/driver-api/attestation.rst
> index d23b7c65d35b..2ab1d2bf031e 100644
> --- a/Documentation/driver-api/attestation.rst
> +++ b/Documentation/driver-api/attestation.rst
> @@ -130,6 +130,36 @@ The next kernel requires the same selector and restored lifetime, rebuilds
>  deduplication from committed IMA journal entries, and never imports the legacy
>  IMA measurement-list buffer for this mode.
>  
> +PCI host identity and acceptance
> +================================
> +
> +Host connections use ``tsm/connect`` and ``tsm/disconnect`` in the PCI device's
> +sysfs directory. Write the link TSM name, such as ``tsm0``, to either file.
> +Backends declare the evidence objects and certificate slots they expose;
> +DEVICE_GET advertises these capabilities and EVIDENCE_GET exports the cached
> +evidence. The common provider does not restrict host certificates to a fixed
> +slot.
> +
> +After verifying a chain, issue EVIDENCE_ACCEPT with the device and evidence
> +generations returned by EVIDENCE_GET. The ``evidence.accept-object`` selector
> +identifies the certificate slot and must belong to the retrieved snapshot.
> +The link backend's ``evidence_accept`` callback receives the selected slot in
> +``pci_tsm.evidence.slot`` and establishes the secure session and streams.
> +Every link backend must provide this callback; connect only prepares identity
> +evidence and must defer secure-session and IDE setup until acceptance.
> +Successful acceptance makes the connection available to IOMMUFD consumers.
> +Repeating acceptance does not allocate another stream, and selecting a
> +different slot after stream setup returns EBUSY.
> +
> +EVIDENCE_ACCEPT_CLEAR clears the recorded verifier verdict only. It leaves an
> +established stream active; accepting the evidence again reuses that stream.
> +Writing the TSM name to ``tsm/disconnect`` releases either an identity-only
> +connection or its stream and identity resources. It returns EBUSY while
> +IOMMUFD vdevices hold connection contexts.
> +
> +See Documentation/driver-api/pci/tsm.rst for backend-specific behavior and
> +``ctlvctl`` examples of evidence retrieval and acceptance.
> +
>  Simulation provider
>  ===================
>  
> diff --git a/Documentation/driver-api/pci/tsm.rst b/Documentation/driver-api/pci/tsm.rst
> index 28c9ec5dd4e6..03bee3098d19 100644
> --- a/Documentation/driver-api/pci/tsm.rst
> +++ b/Documentation/driver-api/pci/tsm.rst
> @@ -30,6 +30,11 @@ backend registration alone do not create instances.
>  Operation availability
>  ======================
>  
> +Every link backend must provide ``evidence_accept`` for secure-session and
> +IDE setup after userspace verification. The ``connect`` callback prepares
> +identity evidence without establishing the secure link. It is required for
> +link backends.
> +
>  CTLV provides DEVICE_GET, PCI STATE_GET, EVIDENCE_GET, EVIDENCE_ACCEPT,
>  EVIDENCE_ACCEPT_CLEAR and DEVSEC_STATE_GET on established contexts. Backends
>  declare the evidence objects and certificate slots they expose. The common
> @@ -70,13 +75,64 @@ DSM BDF. The TSM ID is the numeric suffix of /sys/class/tsm/tsmN. PCI
>  EVIDENCE_REFRESH requires ``pci.generation`` from STATE_GET; a mismatch returns
>  ESTALE before invoking the native backend.
>  
> -For a link backend that defers secure-link setup until evidence acceptance,
> -write its TSM name to the DSM's ``tsm/connect`` sysfs file to collect identity
> -evidence. DEVICE_GET advertises the backend's available objects and certificate
> -slots; EVIDENCE_GET retrieves the cached chain. After verifying it, issue
> -EVIDENCE_ACCEPT with the returned device and evidence generations to complete
> -session and IDE setup. Writing the same TSM name to ``tsm/disconnect`` releases
> -the context, including an identity context that has not been accepted.
> +The host writes a link TSM name to its DSM's ``tsm/connect`` sysfs file.
> +Connect prepares an identity context and caches the evidence objects
> +declared by the backend, without establishing a secure session or IDE
> +streams. Use DEVICE_GET to discover the available objects and certificate
> +slots, then use EVIDENCE_GET to retrieve an advertised ``cert-chain``
> +instance. Verify the chain under the relying party's policy before issuing
> +EVIDENCE_ACCEPT with the returned device and evidence generations.
> +Acceptance completes session and stream setup. The authenticated attribute
> +remains empty until setup succeeds.
> +
> +Use EVIDENCE_ACCEPT with ``evidence.accept-object`` selecting ``cert-chain``,
> +format ``spdm-cert-chain``, and an advertised certificate slot as
> +``evidence.blob-instance``. The selected object must have been retrieved in the
> +referenced evidence generation; otherwise acceptance returns ESTALE.
> +
> +For example, use ``ctlvctl`` to export an advertised certificate chain (slot 0
> +in this example)::
> +
> +  ctlvctl --spec-dir=./specs exec ctlv0 evidence-get --json '{
> +    "evidence.object": [{
> +      "evidence.blob-class": "cert-chain",
> +      "evidence.format": "spdm-cert-chain",
> +      "evidence.blob-instance": 0,
> +      "evidence.representation": "raw",
> +      "evidence.data": {
> +        "file": "./cert0.bin", "data-buf-size": 16777216
> +      }
> +    }]
> +  }'
> +
> +Verify ``cert0.bin`` under the relying party's policy before accepting it.
> +Use the ``device.generation`` and ``evidence.generation`` values returned by
> +EVIDENCE_GET. If they are 0 and 1 respectively::
> +
> +  ctlvctl --spec-dir=./specs exec ctlv0 evidence-accept --json '{
> +    "device.generation": 0,
> +    "evidence.generation": 1,
> +    "evidence.accept-object": {
> +      "evidence.blob-class": "cert-chain",
> +      "evidence.format": "spdm-cert-chain",
> +      "evidence.blob-instance": 0
> +    }
> +  }'
> +
> +The commands use ``ctlv0`` and ``./specs`` as examples; use the discovered CTLV
> +instance and the directory containing the matching specification files.
> +Generation pairs are scoped to an instance lifetime. Keep the same CTLV file
> +descriptor open from evidence collection through acceptance in a production
> +verifier, and discard its generation pair on close or ENODEV. The separate
> +``ctlvctl`` invocations illustrate the request format and assume the instance
> +remains unchanged between commands. ``ctlvctl`` exports evidence and submits
> +the verdict; certificate verification is the relying party's responsibility.
> +
> +Write the same TSM name to ``tsm/disconnect`` to release identity and stream
> +resources. Disconnect works before evidence acceptance too, and returns EBUSY
> +while an IOMMUFD vdevice holds a connection context. EVIDENCE_ACCEPT_CLEAR only
> +clears the verdict; disconnect owns stream teardown. VM assignment and native
> +TDISP transport remain in IOMMUFD and the platform TSM driver.
>  
>  A guest verifier follows this sequence:
>  
> diff --git a/drivers/pci/tsm/core.c b/drivers/pci/tsm/core.c
> index 170e043b1d3b..983463dd0811 100644
> --- a/drivers/pci/tsm/core.c
> +++ b/drivers/pci/tsm/core.c
> @@ -261,7 +261,7 @@ static int pci_tsm_connect(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
>  	if (rc)
>  		return rc;
>  
> -	tsm_pf0->stream_connected = !ops->evidence_accept;
> +	tsm_pf0->stream_connected = false;
>  	pdev->tsm = no_free_ptr(pci_tsm);
>  
>  	/*
> @@ -315,7 +315,8 @@ static bool pci_tsm_can_connect(struct tsm_dev *tsm_dev)
>  {
>  	const struct pci_tsm_ops *ops = tsm_dev ? tsm_dev->pci_ops : NULL;
>  
> -	return is_link_tsm(tsm_dev) && ops->remove && ops->connect && ops->disconnect;
> +	return is_link_tsm(tsm_dev) && ops->remove && ops->connect &&
> +		ops->evidence_accept && ops->disconnect;
>  }
>  
>  static ssize_t connect_store(struct device *dev, struct device_attribute *attr,
> diff --git a/drivers/virt/coco/arm-cca-host/main.c b/drivers/virt/coco/arm-cca-host/main.c
> index 0bad6001654b..0f12353322fa 100644
> --- a/drivers/virt/coco/arm-cca-host/main.c
> +++ b/drivers/virt/coco/arm-cca-host/main.c
> @@ -13,6 +13,8 @@
>  #include <linux/tsm.h>
>  #include <linux/cleanup.h>
>  #include <linux/pci-doe.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
>  
>  #include "rmi-da.h"
>  
> @@ -66,6 +68,7 @@ static void cca_tsm_pci_remove(struct pci_tsm *tsm)
>  	if (is_pci_tsm_pf0(pdev)) {
>  		struct cca_host_pf0_ep_dsc *pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
>  
> +		kvfree(tsm->evidence.obj[PCI_TSM_EVIDENCE_TYPE_CERT0].data);
>  		pci_tsm_pf0_destructor(&pf0_ep_dsc->pci);
>  		kfree(pf0_ep_dsc);
>  	} else {
> @@ -212,7 +215,6 @@ static int pci_dev_addr_range(struct pci_dev *pdev, struct rmi_addr_range *pdev_
>  static int cca_pdev_create_ncoh_stream(struct pci_dev *pdev, unsigned long stream_id)
>  {
>  	int ret;
> -	long stream_handle;
>  	struct cca_host_rp_dsc *rp_dsc;
>  	struct rmi_pdev_stream_params *params;
>  	struct pci_dev *rp = pcie_find_root_port(pdev);
> @@ -245,11 +247,9 @@ static int cca_pdev_create_ncoh_stream(struct pci_dev *pdev, unsigned long strea
>  	params->ide_sid = stream_id;
>  	params->num_addr_range = pci_dev_addr_range(pdev, params->addr_range);
>  
> -	ret = cca_pdev_stream_connect(pdev, rp, params, &stream_handle);
> -	if (ret)
> +	ret = cca_pdev_stream_connect(pdev, rp, params, &pf0_ep_dsc->stream_handle);
> +	if (ret && !pf0_ep_dsc->stream_created)
>  		cca_root_port_pdev_put(rp_dsc);
> -	else
> -		pf0_ep_dsc->stream_handle = stream_handle;
>  
>  	free_page((unsigned long)params);
>  	return ret;
> @@ -258,7 +258,6 @@ static int cca_pdev_create_ncoh_stream(struct pci_dev *pdev, unsigned long strea
>  static int cca_pdev_create_ncoh_sys_stream(struct pci_dev *pdev)
>  {
>  	int ret;
> -	long stream_handle;
>  	struct rmi_pdev_stream_params *params;
>  	struct cca_host_pf0_ep_dsc *pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
>  
> @@ -273,9 +272,7 @@ static int cca_pdev_create_ncoh_sys_stream(struct pci_dev *pdev)
>  	params->ide_sid = 0; /* ignored */
>  	params->num_addr_range = pci_dev_addr_range(pdev, params->addr_range);
>  
> -	ret = cca_pdev_stream_connect(pdev, NULL, params, &stream_handle);
> -	if (!ret)
> -		pf0_ep_dsc->stream_handle = stream_handle;
> +	ret = cca_pdev_stream_connect(pdev, NULL, params, &pf0_ep_dsc->stream_handle);
>  
>  	free_page((unsigned long)params);
>  	return ret;
> @@ -298,7 +295,65 @@ static inline bool cca_pdev_need_sel_ide_streams(struct pci_dev *pdev)
>  	return pci_pcie_type(pdev) == PCI_EXP_TYPE_ENDPOINT;
>  }
>  
> +static int cca_tsm_publish_certificate(struct pci_dev *pdev)
> +{
> +	struct cca_host_pf0_ep_dsc *pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
> +	struct pci_tsm_evidence *evidence = &pdev->tsm->evidence;
> +	struct pci_tsm_evidence_object *obj = &evidence->obj[PCI_TSM_EVIDENCE_TYPE_CERT0];
> +	struct cache_object *cache;
> +	void *data;
> +
> +	/* On-chip endpoints can connect without SPDM identity collection. */
> +	if (!pf0_ep_dsc->pci.doe_mb)
> +		return 0;
> +
> +	guard(mutex)(&pf0_ep_dsc->pdev.object_lock);
> +	cache = pf0_ep_dsc->cert_chain.cache;
> +	if (!cache || !cache->offset)
> +		return -ENODATA;
> +
> +	/* Later RMM communication may reallocate the original cache. */
> +	data = kvmemdup(cache->buf, cache->offset, GFP_KERNEL);
> +	if (!data)
> +		return -ENOMEM;
> +
> +	guard(rwsem_write)(&evidence->lock);
> +	obj->data = data;
> +	obj->len = cache->offset;
> +	return 0;
> +}
> +
>  static int cca_tsm_connect(struct pci_dev *pdev)
> +{
> +	struct cca_host_pf0_ep_dsc *pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
> +	int ret;
> +
> +	ret = init_dev_communication_buffers(pdev, &pf0_ep_dsc->pdev.comm_data);
> +	if (ret)
> +		return ret;
> +
> +	ret = cca_pdev_create(pdev);
> +	if (ret)
> +		goto err_pdev_create;
> +
> +	ret = cca_pdev_collect_identity(pdev);
> +	if (ret)
> +		goto pdev_destroy;
> +
> +	ret = cca_tsm_publish_certificate(pdev);
> +	if (ret)
> +		goto pdev_destroy;
> +
> +	return 0;
> +
> +pdev_destroy:
> +	cca_pdev_stop_and_destroy(pdev);
> +err_pdev_create:
> +	free_dev_communication_buffers(&pf0_ep_dsc->pdev.comm_data);
> +	return ret;
> +}
> +
> +static int cca_tsm_evidence_accept(struct pci_dev *pdev)
>  {
>  	struct pci_dev *rp = pcie_find_root_port(pdev);
>  	struct cca_host_pf0_ep_dsc *pf0_ep_dsc;
> @@ -309,9 +364,18 @@ static int cca_tsm_connect(struct pci_dev *pdev)
>  	if (WARN_ON(!is_pci_tsm_pf0(pdev)))
>  		return -EIO;
>  
> +	/* Connect collected identity from slot zero for this PDEV. */
> +	if (pdev->tsm->evidence.slot != 0)
> +		return -EOPNOTSUPP;
> +
>  	pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
> -	if (cca_pdev_need_sel_ide_streams(pdev)) {
> +	if (cca_pdev_needs_key(pdev)) {
> +		ret = cca_pdev_set_public_key(pdev);
> +		if (ret)
> +			goto err_key;
> +	}
>  
> +	if (cca_pdev_need_sel_ide_streams(pdev)) {
>  		ide = pci_ide_stream_alloc(pdev);
>  		if (!ide) {
>  			ret = -ENOMEM;
> @@ -344,42 +408,26 @@ static int cca_tsm_connect(struct pci_dev *pdev)
>  			goto err_tsm;
>  	}
>  
> -	ret = init_dev_communication_buffers(pdev, &pf0_ep_dsc->pdev.comm_data);
> -	if (ret)
> -		goto err_comm_buff;
> -	ret = cca_pdev_create(pdev);
> -	if (ret)
> -		goto err_pdev_create;
> -
> -	ret = cca_pdev_collect_identity(pdev);
> -	if (ret)
> -		goto pdev_destroy;
> -
> -	if (cca_pdev_needs_key(pdev)) {
> -		ret = cca_pdev_set_public_key(pdev);
> -		if (ret)
> -			goto pdev_destroy;
> -	}
>  	/* Create IDE streams */
>  	ret = cca_pdev_create_streams(pdev, stream_id);
>  	if (ret)
> -		goto pdev_destroy;
> +		goto err_stream_connect;
>  	/*
>  	 * Once ide is setup, enable the stream at the endpoint
>  	 * Root port will be done by RMM
>  	 */
> -	if (cca_pdev_need_sel_ide_streams(pdev))
> -		pci_ide_stream_enable(pdev, ide);
> +	if (cca_pdev_need_sel_ide_streams(pdev)) {
> +		ret = pci_ide_stream_enable(pdev, ide);
> +		if (ret)
> +			goto err_stream_connect;
> +	}
>  
>  	return 0;
>  
> -pdev_destroy:
> -	cca_pdev_stop_and_destroy(pdev);
> -err_pdev_create:
> -	free_dev_communication_buffers(&pf0_ep_dsc->pdev.comm_data);
> -err_comm_buff:
> -	if (cca_pdev_need_sel_ide_streams(pdev))
> -		tsm_ide_stream_unregister(ide);
> +err_stream_connect:
> +	/* Retain partial RMM/IDE state for sysfs disconnect to release. */
> +	pdev->tsm->error = true;
> +	return ret;
>  err_tsm:
>  	if (cca_pdev_need_sel_ide_streams(pdev)) {
>  		pci_ide_stream_teardown(rp, ide);
> @@ -391,7 +439,9 @@ static int cca_tsm_connect(struct pci_dev *pdev)
>  		pci_ide_stream_free(ide);
>  	pf0_ep_dsc->sel_stream = NULL;
>  err_stream_alloc:
> -
> +	return ret;
> +err_key:
> +	pdev->tsm->error = true;
>  	return ret;
>  }
>  
> @@ -405,26 +455,33 @@ static void cca_tsm_disconnect(struct pci_dev *pdev)
>  	if (!pf0_ep_dsc)
>  		return;
>  
> -	if (cca_pdev_need_sel_ide_streams(pdev))
> -		ide = pf0_ep_dsc->sel_stream;
> +	ide = pf0_ep_dsc->sel_stream;
> +	if (pf0_ep_dsc->stream_created) {
> +		if (!cca_pdev_need_sel_ide_streams(pdev))
> +			rp = NULL;
>  
> -	cca_pdev_disconnect_stream(pdev, rp, pf0_ep_dsc->stream_handle);
> -	if (rp)
> -		cca_root_port_pdev_put(to_cca_rp_dsc(rp));
> +		cca_pdev_disconnect_stream(pdev, rp, pf0_ep_dsc->stream_handle);
> +		if (rp)
> +			cca_root_port_pdev_put(to_cca_rp_dsc(rp));
> +
> +		pf0_ep_dsc->stream_created = false;
> +	}
>  
>  	cca_pdev_stop_and_destroy(pdev);
>  	free_dev_communication_buffers(&pf0_ep_dsc->pdev.comm_data);
>  
> -	if (cca_pdev_need_sel_ide_streams(pdev)) {
> +	if (ide) {
>  		pci_ide_stream_release(ide);
>  		pf0_ep_dsc->sel_stream = NULL;
>  	}
>  }
>  
>  static struct pci_tsm_ops cca_link_pci_ops = {
> +	.evidence_types = BIT(PCI_TSM_EVIDENCE_TYPE_CERT0),
>  	.probe = cca_tsm_pci_probe,
>  	.remove = cca_tsm_pci_remove,
>  	.connect = cca_tsm_connect,
> +	.evidence_accept = cca_tsm_evidence_accept,
>  	.disconnect = cca_tsm_disconnect,
>  	.owner = THIS_MODULE,
>  	.viommu_get_ops = cca_viommu_get_ops,
> diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.c b/drivers/virt/coco/arm-cca-host/rmi-da.c
> index c4f505753bf9..d18812b91c31 100644
> --- a/drivers/virt/coco/arm-cca-host/rmi-da.c
> +++ b/drivers/virt/coco/arm-cca-host/rmi-da.c
> @@ -92,8 +92,9 @@ static int init_pdev_params(struct pci_dev *pdev, struct rmi_pdev_params *params
>  	params->pdev_id = rid;
>  	params->hb_base = cfg->res.start;
>  	params->routing_id = pci_ide_segment(pdev);
> -	/* slot number for certificate chain default to zero */
> -	params->id_index = 0;
> +	/* Root ports do not use an SPDM certificate chain. */
> +	if (params->flags & RMI_PDEV_FLAGS_SPDM)
> +		params->id_index = 0;
>  	params->hash_algo = RMI_HASH_SHA_256;
>  	/* no multi function device here. */
>  	params->rid_base = rid;
> @@ -921,6 +922,8 @@ int cca_pdev_stream_connect(struct pci_dev *pdev1, struct pci_dev *pdev2,
>  	if (rmi_pdev_stream_connect(stream_params_phys, stream_handle))
>  		return -EIO;
>  
> +	/* Keep the handle and peer alive even if communication fails below. */
> +	to_cca_pf0_ep_dsc(pdev1)->stream_created = true;
>  	return submit_stream_work(pdev1, pdev2, *stream_handle);
>  }
>  
> diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.h b/drivers/virt/coco/arm-cca-host/rmi-da.h
> index 00a68afc0cb8..2024ca6cb9f3 100644
> --- a/drivers/virt/coco/arm-cca-host/rmi-da.h
> +++ b/drivers/virt/coco/arm-cca-host/rmi-da.h
> @@ -87,6 +87,7 @@ struct cca_host_pdev_dsc {
>   * @pdev: pdev communication context
>   * @sel_stream: Selective IDE Stream descriptor
>   * @stream_handle: Stream handle returned by stream connect
> + * @stream_created: RMM allocated a stream, including incomplete setup
>   * @rmi_signature_algorithm: Signature algorithm used for public key
>   * @cert_chain: cetrificate chain
>   * @vca: SPDM's Version-Capabilities-Algorithms cache object
> @@ -96,6 +97,7 @@ struct cca_host_pf0_ep_dsc {
>  	struct cca_host_pdev_dsc pdev;
>  	struct pci_ide *sel_stream;
>  	unsigned long stream_handle;
> +	bool stream_created;
>  
>  	char *rmi_signature_algorithm;
>  	struct {
> diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
> index 8fd209a2c32b..a2b439edbd0c 100644
> --- a/include/linux/pci-tsm.h
> +++ b/include/linux/pci-tsm.h
> @@ -88,11 +88,11 @@ struct pci_tsm_ops {
>  	 * @probe: establish context with the TSM (allocate / wrap 'struct
>  	 *	   pci_tsm') for follow-on link operations
>  	 * @remove: destroy link operations context
> -	 * @connect: collect device identity; required for link backends; legacy
> -	 *	     backends may also establish IDE
> +	 * @connect: collect device identity; required for link backends;
> +	 *	     session and IDE setup must wait for @evidence_accept
>  	 * @evidence_accept: establish the secure session and IDE after evidence
>  	 *		  acceptance, using pci_tsm::evidence.slot as the selected
> -	 *		  certificate slot; optional for legacy backends
> +	 *		  certificate slot; required for link backends
>  	 * @disconnect: teardown the secure link
>  	 *
>  	 * Context: All callbacks run under pci_tsm_rwsem held for write to sync
> diff --git a/samples/devsec/link_tsm.c b/samples/devsec/link_tsm.c
> index 61cf6c16e1b5..a6644b276a88 100644
> --- a/samples/devsec/link_tsm.c
> +++ b/samples/devsec/link_tsm.c
> @@ -132,6 +132,10 @@ static void devsec_link_tsm_pci_remove(struct pci_tsm *tsm)
>  static DECLARE_BITMAP(devsec_stream_ids, NR_TSM_STREAMS);
>  static struct pci_ide *devsec_streams[NR_TSM_STREAMS];
>  
> +static bool fail_stream_connect;
> +module_param(fail_stream_connect, bool, 0600);
> +MODULE_PARM_DESC(fail_stream_connect, "Fail stream setup after identity collection");
> +
>  static unsigned long *alloc_devsec_stream_id(unsigned long *stream_id)
>  {
>  	unsigned long id;
> @@ -146,7 +150,7 @@ static unsigned long *alloc_devsec_stream_id(unsigned long *stream_id)
>  DEFINE_FREE(free_devsec_stream, unsigned long *, if (_T) clear_bit(*_T, devsec_stream_ids))
>  
>  /*
> - * Reference consumer for a TSM driver "connect" operation callback. The
> + * Reference consumer for a TSM driver "evidence_accept" operation callback. The
>   * low-level TSM driver understands details about the platform the PCI
>   * core does not, like number of available streams that can be
>   * established per host bridge. The expected flow is:
> @@ -168,11 +172,20 @@ DEFINE_FREE(free_devsec_stream, unsigned long *, if (_T) clear_bit(*_T, devsec_s
>   * or use model sequencing.
>   */
>  static int devsec_link_tsm_connect(struct pci_dev *pdev)
> +{
> +	/* probe() populated the synthetic identity; allocate streams on accept. */
> +	return 0;
> +}
> +
> +static int devsec_link_tsm_evidence_accept(struct pci_dev *pdev)
>  {
>  	struct pci_dev *rp = pcie_find_root_port(pdev);
>  	unsigned long __stream_id;
>  	int rc;
>  
> +	if (fail_stream_connect)
> +		return -EIO;
> +
>  	dev_dbg(pci_tsm_host(pdev), "%s\n", pci_name(pdev));
>  
>  	unsigned long *stream_id __free(free_devsec_stream) =
> @@ -235,6 +248,7 @@ static struct pci_tsm_ops devsec_link_pci_ops = {
>  	.probe = devsec_link_tsm_pci_probe,
>  	.remove = devsec_link_tsm_pci_remove,
>  	.connect = devsec_link_tsm_connect,
> +	.evidence_accept = devsec_link_tsm_evidence_accept,
>  	.disconnect = devsec_link_tsm_disconnect,
>  };
>  


^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance
  2026-10-29 16:56   ` Dave Jiang
@ 2026-10-29 17:10     ` Jason Gunthorpe
  0 siblings, 0 replies; 17+ messages in thread
From: Jason Gunthorpe @ 2026-10-29 17:10 UTC (permalink / raw)
  To: Dave Jiang
  Cc: Aneesh Kumar K.V (Arm), linux-coco, Jiri Pirko, Kevin Tian,
	Alexey Kardashevskiy, Xu Yilun, Catalin Marinas, Suzuki K Poulose,
	Steven Price, Kiryl Shutsemau, Rick Edgecombe, Dave Hansen,
	Jonathan Cameron, Bjorn Helgaas, Ashish Kalra, Tom Lendacky,
	John Allen, Will Deacon, Mark Rutland, patches

On Thu, Oct 29, 2026 at 09:56:26AM -0700, Dave Jiang wrote:
> 
> 
> On 10/28/26 8:26 AM, Aneesh Kumar K.V (Arm) wrote:
> > Declare certificate slot zero as the CCA link backend evidence
> > capability. Limit connect to PDEV creation and slot-zero identity
> > collection. Copy the chain into the PCI evidence cache before publishing
> > the CTLV device.
> > 
> > Complete public-key submission, secure-session setup and stream
> > connection through evidence_accept after userspace verifies the chain.
> > Keep the evidence snapshot separate from the RMM communication cache and
> > release it on context removal.
> > 
> > With AMD converted in the previous patch, CCA is the last link backend
> > that still establishes streams during connect. Now require
> > evidence_accept for every link backend and initialize newly connected
> > contexts without active streams. Reject link providers that omit the
> > callback, and remove the legacy connect-time session and IDE exception
> > from the API comments and documentation. Connect remains required at
> > this point; a later patch allows shared-discovery backends to omit it.
> 
> TDX Connect is another one. The TDX host link [1] sets up the SPDM
> session and then the IDE streams in its connect
> (tdx_tsm_link_connect(), patches 27 and 30).

Yeah, everyone tried to do it this way, but that is what is changing.

Do not call TDH.SPDM.CONNECT when processing the sysfs connect
operation. The sysfs operation only establishes what TSM driver is in
use.

You call TDH.SPDM.CONNECT triggered by attestation subsystem with the
slot id provided by userspace.

Don't get confused by the similar names :)

> It can't defer the session. The TDX Module runs the whole SPDM
> session setup inside TDH.SPDM.CONNECT, and the host relays its DOE
> messages without interpreting them, so there is no defined point to
> stop after the certificates. The chains come back in the device info
> only once TDH.SPDM.CONNECT completes (TDX Connect ABI Reference Spec
> 2.1 v0.66, 4.2.20.1).

Yeah, its fine. TDX will have to do what Aneesh proposed for ARM, and
use use the Rust SPDM Driver to operate DOE prior to calling
TDH.SPDM.CONNECT. This will allow discovering what is in the slots and
passing that information to userspace.

> For TDX, the chain can be checked after connect instead. A connected
> link doesn't make the device trusted by a guest. The guest still
> verifies the device before it accepts it. Could evidence_accept stay
> optional?

No, it is a key part of any crypto handshake, something in userspace
has to acknowledge the SPDM session is "Authenticated" before any use
of it progresses.

For these combined connect and IDE flows, "use" probably means
allowing a kernel driver to bind. Ideally once we connec the TSM there
will be no driver bound, and no driver can be bound until we accept
it.

This probably isn't *strictly* necessary since userspace can sequence
driver binding (assuming that doesn't get removed), more of a safety
sequencing.

For a guest it would mean you are allowed to go to RUN (or maybe it
implicitly goes to run?)

Jason

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2026-10-29 17:11 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-28 15:26 [RFC PATCH v1 00/11] PCI/TSM: Add CTLV attestation Aneesh Kumar K.V (Arm)
2026-10-28 15:26 ` [RFC PATCH v1 01/11] attestation: Restrict inherited operations per instance Aneesh Kumar K.V (Arm)
2026-10-28 15:26 ` [RFC PATCH v1 02/11] attestation: Filter evidence objects " Aneesh Kumar K.V (Arm)
2026-10-28 15:26 ` [RFC PATCH v1 03/11] attestation: Enter secure operation when evidence is accepted Aneesh Kumar K.V (Arm)
2026-10-28 15:26 ` [RFC PATCH v1 04/11] attestation: Bind object selection to accepted evidence snapshots Aneesh Kumar K.V (Arm)
2026-10-28 15:26 ` [RFC PATCH v1 05/11] attestation: Make the evidence-get challenge attribute optional Aneesh Kumar K.V (Arm)
2026-10-28 15:26 ` [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family Aneesh Kumar K.V (Arm)
2026-10-28 15:26 ` [RFC PATCH v1 07/11] attestation: Add attestation helpers for PCI/TSM integration Aneesh Kumar K.V (Arm)
2026-10-28 15:26 ` [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts Aneesh Kumar K.V (Arm)
2026-10-28 22:56   ` Dave Jiang
2026-10-29  5:30     ` Aneesh Kumar K.V
2026-10-29 12:41       ` Jason Gunthorpe
2026-10-28 15:26 ` [RFC PATCH v1 09/11] crypto/ccp: Defer SEV-TIO link setup until evidence acceptance Aneesh Kumar K.V (Arm)
2026-10-28 15:26 ` [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance Aneesh Kumar K.V (Arm)
2026-10-29 16:56   ` Dave Jiang
2026-10-29 17:10     ` Jason Gunthorpe
2026-10-28 15:26 ` [RFC PATCH v1 11/11] virt: coco: arm-cca-guest: Refresh selected evidence against trusted digests Aneesh Kumar K.V (Arm)

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.