mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support
@ 2026-11-13 16:08 Jiri Pirko
  2026-11-13 16:08 ` [PATCH RFC v2 1/9] attestation: Add the base family specification and generated files Jiri Pirko
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
  To: linux-coco
  Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
	lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
	James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
	alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
	elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
	peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
	nik.borisov, sergii.dmytruk

From: Jiri Pirko <jiri@nvidia.com>

Linux has several attestation interfaces, each tied to one technology.
Extending a measurement takes device-specific flows for TPM, CCA, TDX
and SEV, none of them discoverable, and requesting a signed quote takes
another set, SPDM included. A new root of trust has no logical home
short of squeezing it into tsm_report and tsm_mr: tsm_report covers
reports alone, the MRs went to tsm_mr, and SPDM fits neither.

This series adds a common attestation subsystem. Its goal is to support
platform, TEE and device roots of trust through one model, including
TPM2, TDX, SEV-SNP, Arm CCA, FIRME, Caliptra and PCI/TSM-backed
devices, so that a program extends a register or asks for evidence
without knowing the hardware behind it. This first series implements
the common subsystem and a TDX provider. The native TDX interfaces,
/dev/tdx_guest, tsm_report and tsm_mr, stay as they are.

RFC v1 [1] posted this and CTLV as one patchset. For convenience, v2
splits it in two, and this series applies on top of the CTLV series,
posted alongside as

  [PATCH RFC v2 00/15] ctlv: Add a schema-driven TLV uAPI for character devices

Each attestation device has a node, /dev/attestation/attestationN,
that speaks CTLV: a request is checked against the family's schema and
executed at once, rather than assembled through several files. The
base family, ctlv-attestation, has what every provider shares, so a
tool written against it works with any provider. TDX's family,
ctlv-attestation-tdx, inherits it and adds no ops. It fills the base
family's extension points: device-get's info with the TD's MRCONFIGID,
MROWNER and MROWNERCONFIG, and evidence-vendor-format with the two TDX
Quote formats.

The base family's ops, as ctlvgen --signatures prints them, with the
purpose of each:

  # What the device supports, fixed for its life, and its family's fields.
  device-get() ->
      { caps: { evidence?: { objects[16]: { id, class, formats[4]?,
                                            vendor-formats[4]?, max-length,
                                            challenge-lengths[8]?: { length?,
                                                                     min-length?,
                                                                     max-length? } } },
                mr?: { flags } }, info: device-info }

  # Evidence: each object named, written to a blob the caller supplies,
  # a signed one made by the call over the challenge.
  evidence-get(challenge?, objects[16]: { id, data: out blob }) ->
      { results[16]: { format?, vendor-format?, length }, mr-generation? }

  # Describe every MR, or the one an ID names.
  mr-get(id?) -> { items[]: { id, mr-desc } }

  # One MR's values, a digest per bank, and the MR generation they
  # reflect.
  mr-read(id) -> { mr-generation, mr-values[hash-algo]: mr-value }

  # Extend banks of one MR by the caller's digests.
  mr-extend(id, mr-values[hash-algo]: mr-value) -> { mr-generation }

An mr-desc is an MR's name, its banks, what it allows and its register
index, and an mr-value one bank's hash algorithm and digest. The
userspace ABI gives each op's request, reply, errors and access.

On TDX, evidence-get returns the Quote over a 64-byte challenge,
labelled a legacy or a DICE one, and, where ACPI describes it, the
CCEL event log. The MRs are MRTD, which cannot be extended, and RTMR0
to RTMR3, all SHA-384. With ctlvctl from the CTLV series and
CTLV_SPEC_DIR pointing at Documentation/ctlv/specs, the simulator's
output, rewritten for a TD, its lengths left out:

  # The device and its family chain.
  $ ctlvctl list
  attestation/attestation0        ctlv/ctlv-attestation/ctlv-attestation-tdx

  # What it supports.
  $ ctlvctl exec attestation/attestation0 device-get
  # device-get, 1 fragment, generation 0
    device-get caps
      device-get caps.evidence
        device-get caps.evidence.objects
          device-get caps.evidence.objects.id 0
          device-get caps.evidence.objects.class signed
          device-get caps.evidence.objects.vendor-formats quote
          device-get caps.evidence.objects.vendor-formats quote-dice
          device-get caps.evidence.objects.max-length 131048
          device-get caps.evidence.objects.challenge-lengths
            device-get caps.evidence.objects.challenge-lengths.length 64
        device-get caps.evidence.objects
          device-get caps.evidence.objects.id 1
          device-get caps.evidence.objects.class event-log
          device-get caps.evidence.objects.formats ccel
          device-get caps.evidence.objects.max-length ...
      device-get caps.mr
        device-get caps.mr.flags extend
    device-get info
      device-info.mrconfigid hex:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
      device-info.mrowner hex:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
      device-info.mrownerconfig hex:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

  # A Quote over a 64-byte nonce, written to quote.bin.
  $ nonce=$(openssl rand -hex 64)
  $ ctlvctl exec attestation/attestation0 evidence-get --json \
          '{"challenge":"hex:'"$nonce"'",
            "objects":[{"id":0,"data":{"file":"quote.bin"}}]}'
  # evidence-get, 1 fragment, generation 0
    mr-generation 0
    evidence-get results
      evidence-get results.vendor-format quote
      evidence-get results.length ...

  # Every MR.
  $ ctlvctl exec attestation/attestation0 mr-get
  # mr-get, 1 fragment, generation 0
    mr-get items
      mr-get items.id 0
      mr-desc.name mrtd
      mr-desc.supported-hash-algorithms HASH_ALGO_SHA384
      mr-desc.caps 0
    mr-get items
      mr-get items.id 1
      mr-desc.name rtmr0
      mr-desc.supported-hash-algorithms HASH_ALGO_SHA384
      mr-desc.register-index 0
      mr-desc.caps extend
    ...

  # The value of RTMR0, MR 1.
  $ ctlvctl exec attestation/attestation0 mr-read id=1
  # mr-read, 1 fragment, generation 0
    mr-generation 0
    mr-values
      mr-value.hash-algo HASH_ALGO_SHA384
      mr-value.data hex:b57a300eac0c9b8593bafbadee812f8915456913c28010dab20d700446169bdd56e1af87ce94d66c8fd2d456b3e42826

  # Extend RTMR3, MR 4, by a file's SHA-384 digest.
  $ digest=$(sha384sum app.img | cut -d' ' -f1)
  $ ctlvctl exec attestation/attestation0 mr-extend --json \
          '{"id":4,"mr-values":[{"hash-algo":"HASH_ALGO_SHA384",
                                 "data":"hex:'"$digest"'"}]}'
  # mr-extend, 1 fragment, generation 0
    mr-generation 1

This series is the first part. The branch [2] has the whole work,
building on it: CTLV's events, the MR journal with IMA as its first
user, memfd and dma-buf evidence blobs, an mr-extend that hashes the
caller's data, evidence acceptance and device security, a simulation
provider, KUnit tests and selftests.

The series adds 3480 lines:

  attestation core, C               1340
  generated files                    770
  TDX provider, C                    470
  docs                               462
  family specifications              206
  ABI snapshots                      170
  registry, build                     62

Changes since RFC v1, each patch listing its own:

- Each evidence object has an ID, a class, signed or event-log, and
  the formats it may have: from evidence-format for a layout a
  standard defines, from evidence-vendor-format, whose values the
  device's family gives, for a vendor's. A signed object lists the
  challenge lengths it takes.
- evidence-get names objects by ID, may omit the challenge, and takes
  output blobs of any length, failing with the length one needs.
- TDX labels each Quote, returns the CCEL log, and leaves the
  TDREPORT to /dev/tdx_guest.
- The MR journal and its notifications are left out, until IMA uses
  the subsystem.
- mr-extend may extend some of an MR's banks, and every MR can be
  read.
- mr-get, a query, describes every MR or the one its optional id
  names, and mr-dump is gone.
- Attestation has its own class and /dev/attestation/ nodes, and the
  TDX provider is part of tdx-guest instead of a module of its own.
- The specs are written as op trees and records, with a comment on
  every item, and a userspace ABI document describes the family.

The patches:

  1      the base family's specification and generated files
  2      the userspace ABI
  3-5    the provider and instance core, the MRs, and the CTLV ops
  6      how to write a provider
  7      tdx-guest's native attestation service, for the provider
  8-9    the TDX family's specification and implementation

Note to reviewers: the uAPI, in 1 and 2, and the provider API, in 3
and 6, matter most at this stage.

[1] https://lore.kernel.org/all/20261015145053.3664509-1-jiri@resnulli.us/
[2] https://github.com/jpirko/linux_mlxsw/commits/wip_attestation_all_2

Jiri Pirko (9):
  attestation: Add the base family specification and generated files
  Documentation: attestation: Add userspace ABI
  attestation: Prepare provider and instance core
  attestation: Prepare measurement registers
  attestation: Add CTLV operation handling
  Documentation: attestation: Describe how to write a provider
  tdx-guest: Expose the native attestation service API
  attestation: Add the TDX family specification and generated files
  attestation: Add the TDX family implementation

 .../ctlv/abi/ctlv-attestation-tdx.yaml        |  21 +
 Documentation/ctlv/abi/ctlv-attestation.yaml  | 147 +++++
 Documentation/ctlv/abi/families.registry.yaml |   2 +
 Documentation/ctlv/families.yaml              |   2 +
 Documentation/ctlv/generated-files.yaml       |  14 +
 .../ctlv/specs/ctlv-attestation-tdx.yaml      |  25 +
 .../ctlv/specs/ctlv-attestation.yaml          | 181 +++++++
 Documentation/driver-api/attestation.rst      |  99 ++++
 Documentation/driver-api/index.rst            |   1 +
 .../userspace-api/attestation-tdx.rst         |  59 +++
 Documentation/userspace-api/attestation.rst   | 294 ++++++++++
 Documentation/userspace-api/index.rst         |   1 +
 Documentation/virt/coco/tdx-guest.rst         |   8 +
 MAINTAINERS                                   |  13 +
 drivers/Kconfig                               |   2 +
 drivers/Makefile                              |   1 +
 drivers/attestation/Kconfig                   |  16 +
 drivers/attestation/Makefile                  |   5 +
 drivers/attestation/core/core.c               | 500 ++++++++++++++++++
 drivers/attestation/core/ctlv.c               | 499 +++++++++++++++++
 drivers/attestation/core/internal.h           |  34 ++
 drivers/attestation/core/mr.c                 |  64 +++
 .../generated/ctlv_attestation_gen.c          | 179 +++++++
 .../generated/ctlv_attestation_gen.h          | 204 +++++++
 drivers/virt/coco/tdx-guest/Kconfig           |   4 +-
 drivers/virt/coco/tdx-guest/Makefile          |   6 +
 drivers/virt/coco/tdx-guest/attestation.c     | 313 +++++++++++
 .../coco/tdx-guest/ctlv_attestation_tdx_gen.c |  65 +++
 .../coco/tdx-guest/ctlv_attestation_tdx_gen.h |  44 ++
 drivers/virt/coco/tdx-guest/internal.h        |  48 ++
 .../coco/tdx-guest/{tdx-guest.c => main.c}    | 137 ++++-
 include/linux/attestation.h                   | 243 +++++++++
 include/uapi/ctlv/ctlv_attestation_gen.h      | 234 ++++++++
 include/uapi/ctlv/ctlv_attestation_tdx_gen.h  |  42 ++
 include/uapi/ctlv/ctlv_families_gen.h         |   2 +
 35 files changed, 3480 insertions(+), 29 deletions(-)
 create mode 100644 Documentation/ctlv/abi/ctlv-attestation-tdx.yaml
 create mode 100644 Documentation/ctlv/abi/ctlv-attestation.yaml
 create mode 100644 Documentation/ctlv/specs/ctlv-attestation-tdx.yaml
 create mode 100644 Documentation/ctlv/specs/ctlv-attestation.yaml
 create mode 100644 Documentation/driver-api/attestation.rst
 create mode 100644 Documentation/userspace-api/attestation-tdx.rst
 create mode 100644 Documentation/userspace-api/attestation.rst
 create mode 100644 drivers/attestation/Kconfig
 create mode 100644 drivers/attestation/Makefile
 create mode 100644 drivers/attestation/core/core.c
 create mode 100644 drivers/attestation/core/ctlv.c
 create mode 100644 drivers/attestation/core/internal.h
 create mode 100644 drivers/attestation/core/mr.c
 create mode 100644 drivers/attestation/generated/ctlv_attestation_gen.c
 create mode 100644 drivers/attestation/generated/ctlv_attestation_gen.h
 create mode 100644 drivers/virt/coco/tdx-guest/attestation.c
 create mode 100644 drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.c
 create mode 100644 drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.h
 create mode 100644 drivers/virt/coco/tdx-guest/internal.h
 rename drivers/virt/coco/tdx-guest/{tdx-guest.c => main.c} (79%)
 create mode 100644 include/linux/attestation.h
 create mode 100644 include/uapi/ctlv/ctlv_attestation_gen.h
 create mode 100644 include/uapi/ctlv/ctlv_attestation_tdx_gen.h

-- 
2.56.0


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

* [PATCH RFC v2 1/9] attestation: Add the base family specification and generated files
  2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
  2026-11-13 16:08 ` [PATCH RFC v2 2/9] Documentation: attestation: Add userspace ABI Jiri Pirko
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
  To: linux-coco
  Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
	lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
	James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
	alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
	elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
	peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
	nik.borisov, sergii.dmytruk

From: Jiri Pirko <jiri@nvidia.com>

Define the base attestation family. Reserve its family ID and add the
ABI snapshot, registry entry and generated kernel and UAPI files.

The ABI is intentionally specification-first. The generated descriptor
is linked after the provider and operation handlers arrive.

Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- dropped the MR log ops and the sets, groups and attrs only they use
- dropped the family's priv lifecycle hooks
- made the hash algorithm attrs an enum of <linux/hash_info.h>
- replaced the blob class and representation with an evidence class,
  signed or event-log
- dropped max-total-output-length
- renamed stubs to extension points
- commented the values, operations and fields
- wrote the spec as op trees and records
- reported each signed object's challenge lengths, in bytes, as a list
  of up to 8 lengths and ranges
- let evidence-get omit the challenge
- took output blobs of any length
- merged mr-dump into mr-get, a query whose id is an optional filter
- listed an MR's banks in the provider's order, each once
- let mr-extend extend some of an MR's banks
- dropped the MR read caps, as every MR can be read
- numbered every enum member and flag bit
- moved the generated UAPI headers to include/uapi/ctlv/
- documented every item of the spec
- put each field's doc above its generated helpers
- said that every evidence-get produces new evidence
- replaced the format strings with the evidence-format enum and the
  evidence-vendor-format enum extension point
- named an evidence-get object by its ID
- said message instead of wire
- bounded an MR's banks by hash-algo, not a number
---
 Documentation/ctlv/abi/ctlv-attestation.yaml  | 147 +++++++++++
 Documentation/ctlv/abi/families.registry.yaml |   1 +
 Documentation/ctlv/families.yaml              |   1 +
 Documentation/ctlv/generated-files.yaml       |   7 +
 .../ctlv/specs/ctlv-attestation.yaml          | 181 ++++++++++++++
 MAINTAINERS                                   |   9 +
 .../generated/ctlv_attestation_gen.c          | 179 ++++++++++++++
 .../generated/ctlv_attestation_gen.h          | 204 +++++++++++++++
 include/uapi/ctlv/ctlv_attestation_gen.h      | 234 ++++++++++++++++++
 include/uapi/ctlv/ctlv_families_gen.h         |   1 +
 10 files changed, 964 insertions(+)
 create mode 100644 Documentation/ctlv/abi/ctlv-attestation.yaml
 create mode 100644 Documentation/ctlv/specs/ctlv-attestation.yaml
 create mode 100644 drivers/attestation/generated/ctlv_attestation_gen.c
 create mode 100644 drivers/attestation/generated/ctlv_attestation_gen.h
 create mode 100644 include/uapi/ctlv/ctlv_attestation_gen.h

diff --git a/Documentation/ctlv/abi/ctlv-attestation.yaml b/Documentation/ctlv/abi/ctlv-attestation.yaml
new file mode 100644
index 000000000000..16b7be1fbc70
--- /dev/null
+++ b/Documentation/ctlv/abi/ctlv-attestation.yaml
@@ -0,0 +1,147 @@
+# 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 family, from
+# Documentation/ctlv/specs/ctlv-attestation.yaml, written by
+# "make -C tools/ctlv regen".
+#
+# Updating the file is how a UAPI change gets reviewed; an update that
+# removes or renumbers anything, narrows a bound, or makes a member required
+# is an ABI break, whatever the file says afterwards.
+family: {name: ctlv-attestation, id: 1, inherits: ctlv}
+enums:
+  evidence-class: {signed: 1, event-log: 2}
+  evidence-format: {ccel: 1}
+  hash-algo: {$header: linux/hash_info.h, $enum: hash_algo,
+              $end: HASH_ALGO__LAST}
+flags:
+  mr-device-caps: {extend: 0}
+  mr-caps: {extend: 0}
+records:
+  mr-desc:
+    id: 0x00010000
+    fields:
+      name: {number: 1, type: string, min-length: 1, max-length: 128}
+      supported-hash-algorithms: {number: 2, enum: hash-algo,
+                                  max-occurrences: hash-algo}
+      caps: {number: 3, flags: mr-caps}
+      register-index: {number: 4, type: uint, max: u32-max, optional: true}
+  mr-value:
+    id: 0x00020000
+    fields:
+      hash-algo: {number: 1, enum: hash-algo}
+      data: {number: 2, type: binary, min-length: 16, max-length: 64}
+  mr-generation:
+    id: 0x00030000
+    fields:
+      mr-generation: {number: 1, type: uint, max: u32-max}
+  mr-values:
+    id: 0x00040000
+    fields:
+      mr-values: {number: 1, record: mr-value, max-occurrences: hash-algo}
+  device-get request:
+    id: 0x80010000
+  device-get reply:
+    id: 0xc0010000
+    fields:
+      caps: {number: 1, record: device-get caps}
+      info: {number: 2, extension: device-info}
+  device-get caps:
+    id: 0xc0011000
+    fields:
+      evidence: {number: 1, record: device-get caps.evidence, optional: true}
+      mr: {number: 2, record: device-get caps.mr, optional: true}
+  device-get caps.evidence:
+    id: 0xc0011100
+    fields:
+      objects: {number: 1, record: device-get caps.evidence.objects,
+                max-occurrences: 16}
+  device-get caps.evidence.objects:
+    id: 0xc0011110
+    fields:
+      id: {number: 1, type: uint, max: 15}
+      class: {number: 2, enum: evidence-class}
+      formats: {number: 3, enum: evidence-format, max-occurrences: 4,
+                optional: true}
+      vendor-formats: {number: 4, enum: evidence-vendor-format,
+                       max-occurrences: 4, optional: true}
+      max-length: {number: 5, type: uint}
+      challenge-lengths:
+        {number: 6, record: device-get caps.evidence.objects.challenge-lengths,
+         max-occurrences: 8, optional: true}
+  device-get caps.evidence.objects.challenge-lengths:
+    id: 0xc0011111
+    fields:
+      length: {number: 1, type: uint, min: 1, max: 4K, unit: bytes,
+               optional: true}
+      min-length: {number: 2, type: uint, min: 1, max: 4K, unit: bytes,
+                   optional: true}
+      max-length: {number: 3, type: uint, min: 1, max: 4K, unit: bytes,
+                   optional: true}
+  device-get caps.mr:
+    id: 0xc0011200
+    fields:
+      flags: {number: 1, flags: mr-device-caps}
+  evidence-get request:
+    id: 0x80020000
+    fields:
+      challenge: {number: 1, type: binary, min-length: 1, max-length: 4K,
+                  optional: true}
+      objects: {number: 2, record: evidence-get objects, max-occurrences: 16}
+  evidence-get objects:
+    id: 0x80021000
+    fields:
+      id: {number: 1, type: uint, max: 15}
+      data: {number: 2, blob: output, backings: [va], max-length: u32-max}
+  evidence-get reply:
+    id: 0xc0020000
+    fields:
+      results: {number: 1, record: evidence-get results, max-occurrences: 16}
+    members: ['record mr-generation?']
+  evidence-get results:
+    id: 0xc0021000
+    fields:
+      format: {number: 1, enum: evidence-format, optional: true}
+      vendor-format: {number: 2, enum: evidence-vendor-format, optional: true}
+      length: {number: 3, type: uint}
+  mr-get request:
+    id: 0x80030000
+    fields:
+      id: {number: 1, type: uint, max: u32-max, optional: true}
+  mr-get reply:
+    id: 0xc0030000
+    fields:
+      items: {number: 1, record: mr-get items, max-occurrences: unlimited}
+  mr-get items:
+    id: 0xc0031000
+    fields:
+      id: {number: 1, type: uint, max: u32-max}
+    members: [$include mr-desc]
+  mr-read request:
+    id: 0x80040000
+    fields:
+      id: {number: 1, type: uint, max: u32-max}
+  mr-read reply:
+    id: 0xc0040000
+    members: [record mr-generation, record mr-values]
+  mr-extend request:
+    id: 0x80050000
+    fields:
+      id: {number: 1, type: uint, max: u32-max}
+    members: [record mr-values]
+  mr-extend reply:
+    id: 0xc0050000
+    members: [record mr-generation]
+extensions: {device-info: 1}
+enum-extensions: {evidence-vendor-format: 1}
+ops:
+  device-get: {id: 1, type: action, fd-access: read,
+               request: device-get request, reply: device-get reply}
+  evidence-get: {id: 2, type: action, availability: dynamic, optional: true,
+                 request: evidence-get request, reply: evidence-get reply}
+  mr-get: {id: 3, type: query, fd-access: read, optional: true,
+           request: mr-get request, reply: mr-get reply}
+  mr-read: {id: 4, type: query, fd-access: read, optional: true,
+            request: mr-read request, reply: mr-read reply}
+  mr-extend: {id: 5, type: action, optional: true, request: mr-extend request,
+              reply: mr-extend reply}
diff --git a/Documentation/ctlv/abi/families.registry.yaml b/Documentation/ctlv/abi/families.registry.yaml
index 5845e7457771..7a5c6af29565 100644
--- a/Documentation/ctlv/abi/families.registry.yaml
+++ b/Documentation/ctlv/abi/families.registry.yaml
@@ -5,3 +5,4 @@
 # Written by "make -C tools/ctlv regen".
 families:
   - ctlv
+  - ctlv-attestation
diff --git a/Documentation/ctlv/families.yaml b/Documentation/ctlv/families.yaml
index 4d219d7af74a..dbb0243b873d 100644
--- a/Documentation/ctlv/families.yaml
+++ b/Documentation/ctlv/families.yaml
@@ -15,3 +15,4 @@
 
 families:
   - ctlv
+  - ctlv-attestation
diff --git a/Documentation/ctlv/generated-files.yaml b/Documentation/ctlv/generated-files.yaml
index d4766b176ceb..00a56634805d 100644
--- a/Documentation/ctlv/generated-files.yaml
+++ b/Documentation/ctlv/generated-files.yaml
@@ -16,3 +16,10 @@ families:
       kernel-header: include/linux/ctlv_gen.h
       kernel-source: lib/ctlv/ctlv_gen.c
       user-source: tools/ctlv/libctlv/ctlv_user_gen.c
+
+  - name: ctlv-attestation
+    source: Documentation/ctlv/specs/ctlv-attestation.yaml
+    outputs:
+      uapi-header: include/uapi/ctlv/ctlv_attestation_gen.h
+      kernel-header: drivers/attestation/generated/ctlv_attestation_gen.h
+      kernel-source: drivers/attestation/generated/ctlv_attestation_gen.c
diff --git a/Documentation/ctlv/specs/ctlv-attestation.yaml b/Documentation/ctlv/specs/ctlv-attestation.yaml
new file mode 100644
index 000000000000..78b2dad6318a
--- /dev/null
+++ b/Documentation/ctlv/specs/ctlv-attestation.yaml
@@ -0,0 +1,181 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+%YAML 1.2
+---
+# Attestation devices: evidence and measurement registers (MRs). The device's
+# family inherits this one, fills device-info and gives evidence-vendor-format
+# the formats its vendor defines.
+family: ctlv-attestation
+inherits: ctlv
+
+enums:
+  # What an evidence object is.
+  evidence-class:
+    # Made by evidence-get over its challenge, and signed by the root of
+    # trust.
+    - signed
+    # The platform's log of what firmware and the boot chain measured into
+    # the MRs. Later extends, through mr-extend or otherwise, are not in it.
+    - event-log
+  # A layout of an evidence object that a standard defines.
+  evidence-format:
+    # The log area of an ACPI CCEL table, a TCG crypto-agile event log.
+    - ccel
+  # A layout of an evidence object that one vendor defines, given by the
+  # device's family.
+  evidence-vendor-format: extension
+  # The kernel's hash algorithms.
+  hash-algo:
+    $header: linux/hash_info.h
+    $enum: hash_algo
+    $end: HASH_ALGO__LAST
+
+flags:
+  # What a device offers for its MRs.
+  mr-device-caps:
+    # mr-extend is provided.
+    - extend
+  # What one MR allows.
+  mr-caps:
+    # The MR may be extended.
+    - extend
+
+records:
+  # One MR, as mr-get describes it.
+  mr-desc:
+    # The platform's name for the MR, such as rtmr0.
+    name: string(1..128)
+    # Its banks, each once, in the provider's order.
+    supported-hash-algorithms[hash-algo]: hash-algo
+    # What it allows.
+    caps: mr-caps
+    # The hardware's number for the MR, where it has one: 0 for rtmr0.
+    register-index?: uint(..u32-max)
+
+  # One bank of an MR.
+  mr-value:
+    # The bank's algorithm.
+    hash-algo: hash-algo
+    # The digest, as long as hash-algo's.
+    data: binary(16..64)
+
+  # The device's MR generation, which each extend bumps.
+  mr-generation: uint(..u32-max)
+
+  # Values for banks of the MR, each bank once.
+  mr-values[hash-algo]: record mr-value
+
+ops:
+  # What the device can do, and its family's fields.
+  device-get:
+    type: action
+    fd-access: read
+    reply:
+      # What the device supports, fixed for its life.
+      caps:
+        # Present when the device produces evidence.
+        evidence?:
+          # One per object evidence-get can produce.
+          objects[16]:
+            # The device's handle for the object in requests.
+            id: uint(..15)
+            # What the object is.
+            class: evidence-class
+            # The layouts a standard defines that the object may have. At
+            # least one of formats and vendor-formats is present.
+            formats[4]?: evidence-format
+            # The layouts its vendor defines that the object may have.
+            vendor-formats[4]?: evidence-vendor-format
+            # The longest the object gets; a blob this long always holds it.
+            max-length: uint
+            # A signed object's challenge lengths: each entry is one length,
+            # or a range from min-length to max-length.
+            challenge-lengths[8]?:
+              # A single length.
+              length?: uint(1..4K, bytes)
+              # The shortest of a range.
+              min-length?: uint(1..4K, bytes)
+              # The longest of a range.
+              max-length?: uint(1..4K, bytes)
+        # Present when the device has MRs.
+        mr?:
+          # The MR operations the device provides.
+          flags: mr-device-caps
+      # The device's family's fields.
+      info: extension device-info
+
+  # Evidence objects, written into the caller's blobs. A signed object is made
+  # by the call, over the challenge, and never reused; the others are read as
+  # they stand at the call.
+  evidence-get:
+    type: action
+    fd-access: read-write
+    availability: dynamic
+    optional: true
+    request:
+      # Bound into each signed object named: pass a verifier's nonce, or a hash
+      # binding one to your context, never data a peer chose. Present exactly
+      # when a signed object is named, and of a length each of them takes.
+      challenge?: binary(1..4K)
+      # The objects to produce, each named once.
+      objects[16]:
+        # The object, by the ID device-get lists.
+        id: uint(..15)
+        # Where the object is written. One too short for it fails the op with
+        # EMSGSIZE, and error-info's required-length says how long it has to
+        # be.
+        data: blob(out, va)
+    reply:
+      # One per object, in request order.
+      results[16]:
+        # The layout it came in, where a standard defines it. Exactly one of
+        # format and vendor-format is present.
+        format?: evidence-format
+        # The layout it came in, where its vendor defines it.
+        vendor-format?: evidence-vendor-format
+        # The bytes written to its blob.
+        length: uint
+      # Present when the evidence depends on the MRs.
+      mr-generation?: record
+
+  # The MRs' IDs and descriptions, which do not change; mr-read returns
+  # values, which change with every extension and are read from the hardware.
+  mr-get:
+    type: query
+    fd-access: read
+    optional: true
+    suggested-reply-length: 4K
+    request:
+      # Only this MR; every MR without it.
+      id?: uint(..u32-max)
+    reply:
+      # One per MR, ordered by ID.
+      items[]:
+        # The device's handle for the MR in requests.
+        id: uint(..u32-max)
+        $include: mr-desc
+
+  # One MR's values, and the MR generation they belong to.
+  mr-read:
+    type: query
+    fd-access: read
+    optional: true
+    suggested-reply-length: 4K
+    request:
+      # The MR to read, by the ID mr-get lists.
+      id: uint(..u32-max)
+    reply:
+      mr-generation: record
+      mr-values: record
+
+  # Extends banks of the MR by digests, as TPM2_PCR_Extend.
+  mr-extend:
+    type: action
+    fd-access: read-write
+    optional: true
+    request:
+      # The MR to extend, by the ID mr-get lists.
+      id: uint(..u32-max)
+      # For the banks to extend, each once, in any order.
+      mr-values: record
+    reply:
+      mr-generation: record
diff --git a/MAINTAINERS b/MAINTAINERS
index b16bedbbe0b6..fa450924b921 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4433,6 +4433,15 @@ F:	rust/kernel/sync/atomic.rs
 F:	rust/kernel/sync/atomic/
 F:	rust/kernel/sync/refcount.rs
 
+ATTESTATION SUBSYSTEM
+M:	Jiri Pirko <jiri@nvidia.com>
+L:	linux-security-module@vger.kernel.org
+S:	Supported
+F:	Documentation/ctlv/abi/ctlv-attestation*
+F:	Documentation/ctlv/specs/ctlv-attestation*
+F:	drivers/attestation/
+F:	include/uapi/ctlv/ctlv_attestation*
+
 ATTO EXPRESSSAS SAS/SATA RAID SCSI DRIVER
 M:	Bradley Grove <linuxdrivers@attotech.com>
 L:	linux-scsi@vger.kernel.org
diff --git a/drivers/attestation/generated/ctlv_attestation_gen.c b/drivers/attestation/generated/ctlv_attestation_gen.c
new file mode 100644
index 000000000000..e1c759d6fd8a
--- /dev/null
+++ b/drivers/attestation/generated/ctlv_attestation_gen.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/*
+ * Generated from Documentation/ctlv/specs/ctlv-attestation.yaml by
+ * tools/ctlv/ctlv-regen.sh. Do not edit.
+ */
+/* CTLV-GEN kernel source */
+
+#define _CTLV_GENERATED_METADATA_ONLY
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/build_bug.h>
+#include <linux/ctlv_family.h>
+#include <linux/export.h>
+#include <linux/limits.h>
+#include <linux/sizes.h>
+
+#include <uapi/ctlv/ctlv_gen.h>
+#include <uapi/ctlv/ctlv_attestation_gen.h>
+
+#include "ctlv_attestation_gen.h"
+
+static const struct ctlv_node_schema device_get_request_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(0, 0, 0),
+};
+
+static const struct ctlv_node_schema device_get_reply_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(1, 2, 2),
+	CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_DEVICE_GET_CAPS, 1, 3, 2, 0),
+	CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_DEVICE_GET_INFO, 1, 0, 0, 0),
+	CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_DEVICE_GET_CAPS_EVIDENCE, 1, 5, 1, 1),
+	CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_DEVICE_GET_CAPS_MR, 1, 6, 1, 1),
+	CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_DEVICE_GET_EVIDENCE_OBJECTS, 16, 7, 6, 3),
+	CTLV_NODE_SCHEMA_FLAGS(CTLV_ATTESTATION_T_DEVICE_GET_MR_FLAGS, 1,
+			       CTLV_ATTESTATION_MR_DEVICE_CAPS_MASK),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_ID, 1, 0, 15),
+	CTLV_NODE_SCHEMA_ENUM(CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_CLASS, 1, 2),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_MAX_LENGTH, 1, 0, U64_MAX),
+	CTLV_NODE_SCHEMA_ENUM(CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_FORMATS, 4, 1),
+	CTLV_NODE_SCHEMA_ENUM_EXT(CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_VENDOR_FORMATS, 4,
+				  CTLV_FAMILY_ATTESTATION,
+				  CTLV_ATTESTATION_ENUM_EXT_EVIDENCE_VENDOR_FORMAT),
+	CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_CHALLENGE_LENGTHS, 8, 13, 3,
+				0),
+	CTLV_NODE_SCHEMA_UINT_UNIT(CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_LENGTH, 1, 1,
+				   4096, CTLV_UNIT_BYTES, 0),
+	CTLV_NODE_SCHEMA_UINT_UNIT(CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MIN_LENGTH, 1, 1,
+				   4096, CTLV_UNIT_BYTES, 0),
+	CTLV_NODE_SCHEMA_UINT_UNIT(CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MAX_LENGTH, 1, 1,
+				   4096, CTLV_UNIT_BYTES, 0),
+};
+
+/* Extension points selected by this tree. */
+static const struct ctlv_ext_schema device_get_reply_ext_schemas[] = {
+	CTLV_EXT_SCHEMA(CTLV_FAMILY_ATTESTATION, CTLV_ATTESTATION_EXT_DEVICE_INFO, 2),
+};
+
+static const struct ctlv_node_schema evidence_get_request_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(1, 2, 1),
+	CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS, 16, 3, 2, 2),
+	CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_T_EVIDENCE_GET_CHALLENGE, 1, 1, SZ_4K),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS_ID, 1, 0, 15),
+	CTLV_NODE_SCHEMA_BLOB(CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS_DATA, 1, U32_MAX, 1,
+			      BIT(CTLV_BLOB_TYPE_VA),
+			      CTLV_BLOB_DIRECTION_OUTPUT),
+};
+
+static const struct ctlv_node_schema evidence_get_reply_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(1, 2, 1),
+	CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS, 16, 3, 3, 1),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_GENERATION, 1, 0, U32_MAX),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_LENGTH, 1, 0, U64_MAX),
+	CTLV_NODE_SCHEMA_ENUM(CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_FORMAT, 1, 1),
+	CTLV_NODE_SCHEMA_ENUM_EXT(CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_VENDOR_FORMAT, 1,
+				  CTLV_FAMILY_ATTESTATION,
+				  CTLV_ATTESTATION_ENUM_EXT_EVIDENCE_VENDOR_FORMAT),
+};
+
+static const struct ctlv_node_schema mr_get_request_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(1, 1, 0),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_GET_ID, 1, 0, U32_MAX),
+};
+
+static const struct ctlv_node_schema mr_get_reply_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(1, 1, 1),
+	CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_MR_GET_ITEMS, CTLV_NODE_SCHEMA_UNLIMITED, 2, 5,
+				4),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_GET_ITEMS_ID, 1, 0, U32_MAX),
+	CTLV_NODE_SCHEMA_STRING(CTLV_ATTESTATION_T_MR_DESC_NAME, 1, 1, 128),
+	CTLV_NODE_SCHEMA_ENUM_RANGE(CTLV_ATTESTATION_T_MR_DESC_SUPPORTED_HASH_ALGORITHMS, 23,
+				    HASH_ALGO_MD4, HASH_ALGO__LAST - 1),
+	CTLV_NODE_SCHEMA_FLAGS(CTLV_ATTESTATION_T_MR_DESC_CAPS, 1, CTLV_ATTESTATION_MR_CAPS_MASK),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_DESC_REGISTER_INDEX, 1, 0, U32_MAX),
+};
+
+static const struct ctlv_node_schema mr_read_request_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(1, 1, 1),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_READ_ID, 1, 0, U32_MAX),
+};
+
+static const struct ctlv_node_schema mr_read_reply_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(1, 2, 2),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_GENERATION, 1, 0, U32_MAX),
+	CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_MR_VALUES, 23, 3, 2, 2),
+	CTLV_NODE_SCHEMA_ENUM_RANGE(CTLV_ATTESTATION_T_MR_VALUE_HASH_ALGO, 1, HASH_ALGO_MD4,
+				    HASH_ALGO__LAST - 1),
+	CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_T_MR_VALUE_DATA, 1, 16, 64),
+};
+
+static const struct ctlv_node_schema mr_extend_request_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(1, 2, 2),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_EXTEND_ID, 1, 0, U32_MAX),
+	CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_MR_VALUES, 23, 3, 2, 2),
+	CTLV_NODE_SCHEMA_ENUM_RANGE(CTLV_ATTESTATION_T_MR_VALUE_HASH_ALGO, 1, HASH_ALGO_MD4,
+				    HASH_ALGO__LAST - 1),
+	CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_T_MR_VALUE_DATA, 1, 16, 64),
+};
+
+static const struct ctlv_node_schema mr_extend_reply_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(1, 1, 1),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_GENERATION, 1, 0, U32_MAX),
+};
+
+/* 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, 15848,
+			      CTLV_SCHEMA_TREE(device_get_request),
+			      CTLV_SCHEMA_TREE_EXTS(device_get_reply)),
+	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_EVIDENCE_GET, CTLV_OP_FD_ACCESS_READ_WRITE,
+			      CTLV_OP_AVAILABILITY_DYNAMIC, 6176, 1448,
+			      CTLV_SCHEMA_TREE(evidence_get_request),
+			      CTLV_SCHEMA_TREE(evidence_get_reply), .optional = true),
+	CTLV_OP_SCHEMA_QUERY(CTLV_ATTESTATION_OP_MR_GET, CTLV_OP_FD_ACCESS_READ,
+			     CTLV_OP_AVAILABILITY_FIXED, 40, 184, 4096,
+			     CTLV_SCHEMA_TREE(mr_get_request), CTLV_SCHEMA_TREE(mr_get_reply),
+			     .optional = true),
+	CTLV_OP_SCHEMA_QUERY(CTLV_ATTESTATION_OP_MR_READ, CTLV_OP_FD_ACCESS_READ,
+			     CTLV_OP_AVAILABILITY_FIXED, 40, 112, 4096,
+			     CTLV_SCHEMA_TREE(mr_read_request), CTLV_SCHEMA_TREE(mr_read_reply),
+			     .optional = true),
+	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_MR_EXTEND, CTLV_OP_FD_ACCESS_READ_WRITE,
+			      CTLV_OP_AVAILABILITY_FIXED, 2800, 40,
+			      CTLV_SCHEMA_TREE(mr_extend_request),
+			      CTLV_SCHEMA_TREE(mr_extend_reply), .optional = true),
+};
+
+/* The family ID is its position in the central registry. */
+const struct ctlv_family_schema ctlv_attestation_schema = {
+	.name = "ctlv-attestation",
+	.family_id = 1,
+	.ops = ctlv_attestation_op_schemas,
+	.ops_count = ARRAY_SIZE(ctlv_attestation_op_schemas),
+};
+
+/* Maximum uevent cost for this family chain. */
+static_assert(107 <= CTLV_UEVENT_MAX_LEN);
+static_assert(4 <= CTLV_UEVENT_MAX_VARS);
+
+/* Guards for generated maxima. */
+static_assert(15848 <= CTLV_MAX_INLINE_MESSAGE_LEN);
+static_assert(4 <= CTLV_MAX_DECLARED_RECORD_DEPTH);
+
+/* Local action and query bindings in declaration order. */
+static const struct ctlv_op ctlv_attestation_ops[] = {
+	CTLV_OP_ACTION(ctlv_attestation_device_get),
+	CTLV_OP_ACTION(ctlv_attestation_evidence_get),
+	CTLV_OP_QUERY(ctlv_attestation_mr_get),
+	CTLV_OP_QUERY(ctlv_attestation_mr_read),
+	CTLV_OP_ACTION(ctlv_attestation_mr_extend),
+};
+
+/* Family descriptor used by ctlv_init(). */
+const struct ctlv_family ctlv_attestation_family = {
+	.schema = &ctlv_attestation_schema,
+	.parent = &ctlv_family,
+	.ops = ctlv_attestation_ops,
+	.ops_count = ARRAY_SIZE(ctlv_attestation_ops),
+};
+EXPORT_SYMBOL_NS_GPL(ctlv_attestation_family, "CTLV");
diff --git a/drivers/attestation/generated/ctlv_attestation_gen.h b/drivers/attestation/generated/ctlv_attestation_gen.h
new file mode 100644
index 000000000000..5ff54385b42e
--- /dev/null
+++ b/drivers/attestation/generated/ctlv_attestation_gen.h
@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * Generated from Documentation/ctlv/specs/ctlv-attestation.yaml by
+ * tools/ctlv/ctlv-regen.sh. Do not edit.
+ */
+/* CTLV-GEN kernel header */
+
+/* Kernel metadata and family API of the ctlv-attestation CTLV family. */
+
+#ifndef _LINUX_CTLV_ATTESTATION_GEN_H
+#define _LINUX_CTLV_ATTESTATION_GEN_H
+
+#include <linux/ctlv_family.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+
+#include <uapi/ctlv/ctlv_attestation_gen.h>
+
+/* Extension points, as a descendant's fill names one. */
+#define CTLV_ATTESTATION_EXT_DEVICE_INFO 1
+
+/* Enum extension points, numbered apart from the others. */
+#define CTLV_ATTESTATION_ENUM_EXT_EVIDENCE_VENDOR_FORMAT 1
+
+/* Ops this family declares, in declaration order. */
+extern const struct ctlv_op_schema ctlv_attestation_op_schemas[];
+
+extern const struct ctlv_family_schema ctlv_attestation_schema;
+
+/* Implemented by this family, one per action and query it declares. */
+int ctlv_attestation_device_get_action(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+				       const struct ctlv_cursor *request,
+				       struct ctlv_cursor *reply);
+int ctlv_attestation_evidence_get_action(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+					 const struct ctlv_cursor *request,
+					 struct ctlv_cursor *reply);
+int ctlv_attestation_mr_get_query(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+				  const struct ctlv_cursor *request, struct ctlv_cursor *reply);
+int ctlv_attestation_mr_read_query(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+				   const struct ctlv_cursor *request, struct ctlv_cursor *reply);
+int ctlv_attestation_mr_extend_action(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+				      const struct ctlv_cursor *request, struct ctlv_cursor *reply);
+
+extern const struct ctlv_family ctlv_attestation_family;
+
+#ifndef _CTLV_GENERATED_METADATA_ONLY
+
+/* Optional publication and dynamic availability helpers. */
+CTLV_OP_PROVIDE(ctlv_attestation_evidence_get, CTLV_ATTESTATION_OP_EVIDENCE_GET)
+CTLV_OP_ENABLE(ctlv_attestation_evidence_get, CTLV_ATTESTATION_OP_EVIDENCE_GET)
+CTLV_OP_DISABLE(ctlv_attestation_evidence_get, CTLV_ATTESTATION_OP_EVIDENCE_GET)
+CTLV_OP_PROVIDE(ctlv_attestation_mr_get, CTLV_ATTESTATION_OP_MR_GET)
+CTLV_OP_PROVIDE(ctlv_attestation_mr_read, CTLV_ATTESTATION_OP_MR_READ)
+CTLV_OP_PROVIDE(ctlv_attestation_mr_extend, CTLV_ATTESTATION_OP_MR_EXTEND)
+
+/* Reading a validated message. A blob has a resolver instead of a getter. */
+/* The objects to produce, each named once. */
+CTLV_IN_REF(ctlv_attestation_evidence_get_objects, CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS,
+	    CTLV_SCHEMA_RECORD)
+#define ctlv_attestation_evidence_get_objects_for_each(_cursor, _record) \
+	CTLV_IN_FOR_EACH(ctlv_attestation_evidence_get_objects, _cursor, _record)
+/* The object, by the ID device-get lists. */
+CTLV_IN_REF(ctlv_attestation_evidence_get_objects_id, CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS_ID,
+	    CTLV_SCHEMA_UINT)
+CTLV_IN_HAS(ctlv_attestation_evidence_get_objects_id)
+CTLV_IN_U64(ctlv_attestation_evidence_get_objects_id)
+/*
+ * Where the object is written. One too short for it fails the op with EMSGSIZE, and error-info's
+ * required-length says how long it has to be.
+ */
+CTLV_IN_REF(ctlv_attestation_evidence_get_objects_data,
+	    CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS_DATA, CTLV_SCHEMA_BLOB)
+CTLV_IN_HAS(ctlv_attestation_evidence_get_objects_data)
+CTLV_IN_BLOB(ctlv_attestation_evidence_get_objects_data)
+/*
+ * Bound into each signed object named: pass a verifier's nonce, or a hash binding one to your
+ * context, never data a peer chose. Present exactly when a signed object is named, and of a length
+ * each of them takes.
+ */
+CTLV_IN_REF(ctlv_attestation_evidence_get_challenge, CTLV_ATTESTATION_T_EVIDENCE_GET_CHALLENGE,
+	    CTLV_SCHEMA_BINARY)
+CTLV_IN_HAS(ctlv_attestation_evidence_get_challenge)
+CTLV_IN_BINARY(ctlv_attestation_evidence_get_challenge)
+/* Only this MR; every MR without it. */
+CTLV_IN_REF(ctlv_attestation_mr_get_id, CTLV_ATTESTATION_T_MR_GET_ID, CTLV_SCHEMA_UINT)
+CTLV_IN_HAS(ctlv_attestation_mr_get_id)
+CTLV_IN_U64(ctlv_attestation_mr_get_id)
+/* The MR to read, by the ID mr-get lists. */
+CTLV_IN_REF(ctlv_attestation_mr_read_id, CTLV_ATTESTATION_T_MR_READ_ID, CTLV_SCHEMA_UINT)
+CTLV_IN_HAS(ctlv_attestation_mr_read_id)
+CTLV_IN_U64(ctlv_attestation_mr_read_id)
+/* The MR to extend, by the ID mr-get lists. */
+CTLV_IN_REF(ctlv_attestation_mr_extend_id, CTLV_ATTESTATION_T_MR_EXTEND_ID, CTLV_SCHEMA_UINT)
+CTLV_IN_HAS(ctlv_attestation_mr_extend_id)
+CTLV_IN_U64(ctlv_attestation_mr_extend_id)
+/* Values for banks of the MR, each bank once. */
+CTLV_IN_REF(ctlv_attestation_mr_values, CTLV_ATTESTATION_T_MR_VALUES, CTLV_SCHEMA_RECORD)
+#define ctlv_attestation_mr_values_for_each(_cursor, _record) \
+	CTLV_IN_FOR_EACH(ctlv_attestation_mr_values, _cursor, _record)
+/* The bank's algorithm. */
+CTLV_IN_REF(ctlv_attestation_mr_value_hash_algo, CTLV_ATTESTATION_T_MR_VALUE_HASH_ALGO,
+	    CTLV_SCHEMA_ENUM)
+CTLV_IN_HAS(ctlv_attestation_mr_value_hash_algo)
+CTLV_IN_U64(ctlv_attestation_mr_value_hash_algo)
+/* The digest, as long as hash-algo's. */
+CTLV_IN_REF(ctlv_attestation_mr_value_data, CTLV_ATTESTATION_T_MR_VALUE_DATA, CTLV_SCHEMA_BINARY)
+CTLV_IN_HAS(ctlv_attestation_mr_value_data)
+CTLV_IN_BINARY(ctlv_attestation_mr_value_data)
+
+/* Writing a reply. An append is atomic; a record ends with ctlv_record_end(). */
+/* What the device supports, fixed for its life. */
+CTLV_RECORD_START(ctlv_attestation_device_get_caps, CTLV_ATTESTATION_T_DEVICE_GET_CAPS,
+		  CTLV_SCHEMA_RECORD)
+/* Present when the device produces evidence. */
+CTLV_RECORD_START(ctlv_attestation_device_get_caps_evidence,
+		  CTLV_ATTESTATION_T_DEVICE_GET_CAPS_EVIDENCE, CTLV_SCHEMA_RECORD)
+/* One per object evidence-get can produce. */
+CTLV_RECORD_START(ctlv_attestation_device_get_evidence_objects,
+		  CTLV_ATTESTATION_T_DEVICE_GET_EVIDENCE_OBJECTS, CTLV_SCHEMA_RECORD)
+/* The device's handle for the object in requests. */
+CTLV_PUT_U64(ctlv_attestation_device_get_objects_id, CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_ID,
+	     CTLV_SCHEMA_UINT)
+/* What the object is. */
+CTLV_PUT_U64(ctlv_attestation_device_get_objects_class, CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_CLASS,
+	     CTLV_SCHEMA_ENUM)
+/* The longest the object gets; a blob this long always holds it. */
+CTLV_PUT_U64(ctlv_attestation_device_get_objects_max_length,
+	     CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_MAX_LENGTH, CTLV_SCHEMA_UINT)
+/*
+ * The layouts a standard defines that the object may have. At least one of formats and
+ * vendor-formats is present.
+ */
+CTLV_PUT_U64(ctlv_attestation_device_get_objects_formats,
+	     CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_FORMATS, CTLV_SCHEMA_ENUM)
+/* The layouts its vendor defines that the object may have. */
+CTLV_PUT_U64(ctlv_attestation_device_get_objects_vendor_formats,
+	     CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_VENDOR_FORMATS, CTLV_SCHEMA_ENUM)
+/*
+ * A signed object's challenge lengths: each entry is one length, or a range from min-length to
+ * max-length.
+ */
+CTLV_RECORD_START(ctlv_attestation_device_get_objects_challenge_lengths,
+		  CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_CHALLENGE_LENGTHS, CTLV_SCHEMA_RECORD)
+/* A single length. */
+CTLV_PUT_U64(ctlv_attestation_device_get_challenge_lengths_length,
+	     CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_LENGTH, CTLV_SCHEMA_UINT)
+/* The shortest of a range. */
+CTLV_PUT_U64(ctlv_attestation_device_get_challenge_lengths_min_length,
+	     CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MIN_LENGTH, CTLV_SCHEMA_UINT)
+/* The longest of a range. */
+CTLV_PUT_U64(ctlv_attestation_device_get_challenge_lengths_max_length,
+	     CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MAX_LENGTH, CTLV_SCHEMA_UINT)
+/* Present when the device has MRs. */
+CTLV_RECORD_START(ctlv_attestation_device_get_caps_mr, CTLV_ATTESTATION_T_DEVICE_GET_CAPS_MR,
+		  CTLV_SCHEMA_RECORD)
+/* The MR operations the device provides. */
+CTLV_PUT_U64(ctlv_attestation_device_get_mr_flags, CTLV_ATTESTATION_T_DEVICE_GET_MR_FLAGS,
+	     CTLV_SCHEMA_FLAGS)
+/* The device's family's fields. */
+CTLV_RECORD_START(ctlv_attestation_device_get_info, CTLV_ATTESTATION_T_DEVICE_GET_INFO,
+		  CTLV_SCHEMA_RECORD)
+/* One per object, in request order. */
+CTLV_RECORD_START(ctlv_attestation_evidence_get_results, CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS,
+		  CTLV_SCHEMA_RECORD)
+/* The bytes written to its blob. */
+CTLV_PUT_U64(ctlv_attestation_evidence_get_results_length,
+	     CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_LENGTH, CTLV_SCHEMA_UINT)
+/*
+ * The layout it came in, where a standard defines it. Exactly one of format and vendor-format is
+ * present.
+ */
+CTLV_PUT_U64(ctlv_attestation_evidence_get_results_format,
+	     CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_FORMAT, CTLV_SCHEMA_ENUM)
+/* The layout it came in, where its vendor defines it. */
+CTLV_PUT_U64(ctlv_attestation_evidence_get_results_vendor_format,
+	     CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_VENDOR_FORMAT, CTLV_SCHEMA_ENUM)
+/* The device's MR generation, which each extend bumps. */
+CTLV_PUT_U64(ctlv_attestation_mr_generation, CTLV_ATTESTATION_T_MR_GENERATION, CTLV_SCHEMA_UINT)
+/* One per MR, ordered by ID. */
+CTLV_RECORD_START(ctlv_attestation_mr_get_items, CTLV_ATTESTATION_T_MR_GET_ITEMS,
+		  CTLV_SCHEMA_RECORD)
+/* The device's handle for the MR in requests. */
+CTLV_PUT_U64(ctlv_attestation_mr_get_items_id, CTLV_ATTESTATION_T_MR_GET_ITEMS_ID, CTLV_SCHEMA_UINT)
+/* The platform's name for the MR, such as rtmr0. */
+CTLV_PUT_STRING(ctlv_attestation_mr_desc_name, CTLV_ATTESTATION_T_MR_DESC_NAME, CTLV_SCHEMA_STRING)
+/* Its banks, each once, in the provider's order. */
+CTLV_PUT_U64(ctlv_attestation_mr_desc_supported_hash_algorithms,
+	     CTLV_ATTESTATION_T_MR_DESC_SUPPORTED_HASH_ALGORITHMS, CTLV_SCHEMA_ENUM)
+/* What it allows. */
+CTLV_PUT_U64(ctlv_attestation_mr_desc_caps, CTLV_ATTESTATION_T_MR_DESC_CAPS, CTLV_SCHEMA_FLAGS)
+/* The hardware's number for the MR, where it has one: 0 for rtmr0. */
+CTLV_PUT_U64(ctlv_attestation_mr_desc_register_index, CTLV_ATTESTATION_T_MR_DESC_REGISTER_INDEX,
+	     CTLV_SCHEMA_UINT)
+/* Values for banks of the MR, each bank once. */
+CTLV_RECORD_START(ctlv_attestation_mr_values, CTLV_ATTESTATION_T_MR_VALUES, CTLV_SCHEMA_RECORD)
+/* The bank's algorithm. */
+CTLV_PUT_U64(ctlv_attestation_mr_value_hash_algo, CTLV_ATTESTATION_T_MR_VALUE_HASH_ALGO,
+	     CTLV_SCHEMA_ENUM)
+/* The digest, as long as hash-algo's. */
+CTLV_PUT_BINARY(ctlv_attestation_mr_value_data, CTLV_ATTESTATION_T_MR_VALUE_DATA,
+		CTLV_SCHEMA_BINARY)
+#endif /* _CTLV_GENERATED_METADATA_ONLY */
+
+#endif /* _LINUX_CTLV_ATTESTATION_GEN_H */
diff --git a/include/uapi/ctlv/ctlv_attestation_gen.h b/include/uapi/ctlv/ctlv_attestation_gen.h
new file mode 100644
index 000000000000..f4a1cfe02bb2
--- /dev/null
+++ b/include/uapi/ctlv/ctlv_attestation_gen.h
@@ -0,0 +1,234 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * Generated from Documentation/ctlv/specs/ctlv-attestation.yaml by
+ * tools/ctlv/ctlv-regen.sh. Do not edit.
+ */
+/* CTLV-GEN uapi header */
+
+/*
+ * Constants of the ctlv-attestation CTLV family. Types and op IDs are complete; the message format
+ * carrying them is in <ctlv/ctlv.h>.
+ *
+ * The inherited ctlv header is included here, so one include covers the whole effective schema.
+ */
+
+#ifndef _UAPI_CTLV_CTLV_ATTESTATION_GEN_H
+#define _UAPI_CTLV_CTLV_ATTESTATION_GEN_H
+
+#include <ctlv/ctlv.h>
+#include <ctlv/ctlv_families_gen.h>
+#include <ctlv/ctlv_gen.h>
+#include <linux/const.h>
+#include <linux/hash_info.h>
+
+/* enum evidence-class */
+enum _ctlv_attestation_evidence_class {
+	/* Made by evidence-get over its challenge, and signed by the root of trust. */
+	CTLV_ATTESTATION_EVIDENCE_CLASS_SIGNED = 1,
+	/*
+	 * The platform's log of what firmware and the boot chain measured into the MRs. Later
+	 * extends, through mr-extend or otherwise, are not in it.
+	 */
+	CTLV_ATTESTATION_EVIDENCE_CLASS_EVENT_LOG = 2,
+};
+
+/* enum evidence-format */
+enum _ctlv_attestation_evidence_format {
+	/* The log area of an ACPI CCEL table, a TCG crypto-agile event log. */
+	CTLV_ATTESTATION_EVIDENCE_FORMAT_CCEL = 1,
+};
+
+/* flags mr-device-caps */
+enum _ctlv_attestation_mr_device_caps_bit {
+	CTLV_ATTESTATION_MR_DEVICE_CAPS_EXTEND_BIT = 0,
+};
+
+/* mr-extend is provided. */
+#define CTLV_ATTESTATION_MR_DEVICE_CAPS_EXTEND _BITULL(CTLV_ATTESTATION_MR_DEVICE_CAPS_EXTEND_BIT)
+#define CTLV_ATTESTATION_MR_DEVICE_CAPS_MASK CTLV_ATTESTATION_MR_DEVICE_CAPS_EXTEND
+
+/* flags mr-caps */
+enum _ctlv_attestation_mr_caps_bit {
+	CTLV_ATTESTATION_MR_CAPS_EXTEND_BIT = 0,
+};
+
+/* The MR may be extended. */
+#define CTLV_ATTESTATION_MR_CAPS_EXTEND _BITULL(CTLV_ATTESTATION_MR_CAPS_EXTEND_BIT)
+#define CTLV_ATTESTATION_MR_CAPS_MASK CTLV_ATTESTATION_MR_CAPS_EXTEND
+
+/* mr-desc */
+/* The platform's name for the MR, such as rtmr0. */
+#define CTLV_ATTESTATION_T_MR_DESC_NAME \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00010000, 1)
+/* Its banks, each once, in the provider's order. */
+#define CTLV_ATTESTATION_T_MR_DESC_SUPPORTED_HASH_ALGORITHMS \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00010000, 2)
+/* What it allows. */
+#define CTLV_ATTESTATION_T_MR_DESC_CAPS \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00010000, 3)
+/* The hardware's number for the MR, where it has one: 0 for rtmr0. */
+#define CTLV_ATTESTATION_T_MR_DESC_REGISTER_INDEX \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00010000, 4)
+
+/* mr-value */
+/* The bank's algorithm. */
+#define CTLV_ATTESTATION_T_MR_VALUE_HASH_ALGO \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00020000, 1)
+/* The digest, as long as hash-algo's. */
+#define CTLV_ATTESTATION_T_MR_VALUE_DATA \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00020000, 2)
+
+/* mr-generation */
+/* The device's MR generation, which each extend bumps. */
+#define CTLV_ATTESTATION_T_MR_GENERATION \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00030000, 1)
+
+/* mr-values */
+/* Values for banks of the MR, each bank once. */
+#define CTLV_ATTESTATION_T_MR_VALUES \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00040000, 1)
+
+/* device-get reply */
+/* What the device supports, fixed for its life. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_CAPS \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0010000, 1)
+/* The device's family's fields. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_INFO \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0010000, 2)
+
+/* device-get caps */
+/* Present when the device produces evidence. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_CAPS_EVIDENCE \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011000, 1)
+/* Present when the device has MRs. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_CAPS_MR \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011000, 2)
+
+/* device-get caps.evidence */
+/* One per object evidence-get can produce. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_EVIDENCE_OBJECTS \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011100, 1)
+
+/* device-get caps.evidence.objects */
+/* The device's handle for the object in requests. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_ID \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011110, 1)
+/* What the object is. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_CLASS \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011110, 2)
+/*
+ * The layouts a standard defines that the object may have. At least one of formats and
+ * vendor-formats is present.
+ */
+#define CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_FORMATS \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011110, 3)
+/* The layouts its vendor defines that the object may have. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_VENDOR_FORMATS \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011110, 4)
+/* The longest the object gets; a blob this long always holds it. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_MAX_LENGTH \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011110, 5)
+/*
+ * A signed object's challenge lengths: each entry is one length, or a range from min-length to
+ * max-length.
+ */
+#define CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_CHALLENGE_LENGTHS \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011110, 6)
+
+/* device-get caps.evidence.objects.challenge-lengths */
+/* A single length. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_LENGTH \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011111, 1)
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_LENGTH_UNIT CTLV_UNIT_BYTES
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_LENGTH_SCALE 0
+/* The shortest of a range. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MIN_LENGTH \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011111, 2)
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MIN_LENGTH_UNIT CTLV_UNIT_BYTES
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MIN_LENGTH_SCALE 0
+/* The longest of a range. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MAX_LENGTH \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011111, 3)
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MAX_LENGTH_UNIT CTLV_UNIT_BYTES
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MAX_LENGTH_SCALE 0
+
+/* device-get caps.mr */
+/* The MR operations the device provides. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_MR_FLAGS \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011200, 1)
+
+/* evidence-get request */
+/*
+ * Bound into each signed object named: pass a verifier's nonce, or a hash binding one to your
+ * context, never data a peer chose. Present exactly when a signed object is named, and of a length
+ * each of them takes.
+ */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_CHALLENGE \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x80020000, 1)
+/* The objects to produce, each named once. */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x80020000, 2)
+
+/* evidence-get objects */
+/* The object, by the ID device-get lists. */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS_ID \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x80021000, 1)
+/*
+ * Where the object is written. One too short for it fails the op with EMSGSIZE, and error-info's
+ * required-length says how long it has to be.
+ */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS_DATA \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x80021000, 2)
+
+/* evidence-get reply */
+/* One per object, in request order. */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0020000, 1)
+
+/* evidence-get results */
+/*
+ * The layout it came in, where a standard defines it. Exactly one of format and vendor-format is
+ * present.
+ */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_FORMAT \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0021000, 1)
+/* The layout it came in, where its vendor defines it. */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_VENDOR_FORMAT \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0021000, 2)
+/* The bytes written to its blob. */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_LENGTH \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0021000, 3)
+
+/* mr-get request */
+/* Only this MR; every MR without it. */
+#define CTLV_ATTESTATION_T_MR_GET_ID \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x80030000, 1)
+
+/* mr-get reply */
+/* One per MR, ordered by ID. */
+#define CTLV_ATTESTATION_T_MR_GET_ITEMS \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0030000, 1)
+
+/* mr-get items */
+/* The device's handle for the MR in requests. */
+#define CTLV_ATTESTATION_T_MR_GET_ITEMS_ID \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0031000, 1)
+
+/* mr-read request */
+/* The MR to read, by the ID mr-get lists. */
+#define CTLV_ATTESTATION_T_MR_READ_ID \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x80040000, 1)
+
+/* mr-extend request */
+/* The MR to extend, by the ID mr-get lists. */
+#define CTLV_ATTESTATION_T_MR_EXTEND_ID \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x80050000, 1)
+
+/* ops */
+#define CTLV_ATTESTATION_OP_DEVICE_GET CTLV_OP(CTLV_FAMILY_ATTESTATION, 1)
+#define CTLV_ATTESTATION_OP_EVIDENCE_GET CTLV_OP(CTLV_FAMILY_ATTESTATION, 2)
+#define CTLV_ATTESTATION_OP_MR_GET CTLV_OP(CTLV_FAMILY_ATTESTATION, 3)
+#define CTLV_ATTESTATION_OP_MR_READ CTLV_OP(CTLV_FAMILY_ATTESTATION, 4)
+#define CTLV_ATTESTATION_OP_MR_EXTEND CTLV_OP(CTLV_FAMILY_ATTESTATION, 5)
+
+#endif /* _UAPI_CTLV_CTLV_ATTESTATION_GEN_H */
diff --git a/include/uapi/ctlv/ctlv_families_gen.h b/include/uapi/ctlv/ctlv_families_gen.h
index 83f1719c89aa..83f6a257ddf2 100644
--- a/include/uapi/ctlv/ctlv_families_gen.h
+++ b/include/uapi/ctlv/ctlv_families_gen.h
@@ -12,6 +12,7 @@
 
 enum _ctlv_family {
 	CTLV_FAMILY_ROOT = 0,
+	CTLV_FAMILY_ATTESTATION = 1,
 };
 
 #endif /* _UAPI_CTLV_CTLV_FAMILIES_GEN_H */
-- 
2.56.0


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

* [PATCH RFC v2 2/9] Documentation: attestation: Add userspace ABI
  2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
  2026-11-13 16:08 ` [PATCH RFC v2 1/9] attestation: Add the base family specification and generated files Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
  2026-11-13 16:08 ` [PATCH RFC v2 3/9] attestation: Prepare provider and instance core Jiri Pirko
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
  To: linux-coco
  Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
	lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
	James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
	alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
	elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
	peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
	nik.borisov, sergii.dmytruk

From: Jiri Pirko <jiri@nvidia.com>

Describe the attestation family for userspace: its operations and their
errors, the evidence objects and the measurement registers.

Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
 Documentation/userspace-api/attestation.rst | 283 ++++++++++++++++++++
 Documentation/userspace-api/index.rst       |   1 +
 MAINTAINERS                                 |   1 +
 3 files changed, 285 insertions(+)
 create mode 100644 Documentation/userspace-api/attestation.rst

diff --git a/Documentation/userspace-api/attestation.rst b/Documentation/userspace-api/attestation.rst
new file mode 100644
index 000000000000..3c610acc3382
--- /dev/null
+++ b/Documentation/userspace-api/attestation.rst
@@ -0,0 +1,283 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=========================
+Attestation userspace ABI
+=========================
+
+A confidential guest proves what it runs to a remote verifier with evidence: a
+statement of its measurements, signed by its root of trust and carrying a
+challenge the verifier chose, and the logs behind those measurements. An
+attestation provider gives its device a node, ``/dev/attestation/attestationN``.
+Through it a program asks for evidence, and reads and extends the measurement
+registers (MRs) the evidence reports.
+
+The node speaks CTLV, described in :doc:`ctlv`, as a descendant of the
+``ctlv-attestation`` family, one family per provider. Every operation below
+belongs to ``ctlv-attestation``, so a tool written against it works with any
+provider. The specification is
+``Documentation/ctlv/specs/ctlv-attestation.yaml`` and the constants are in
+``include/uapi/ctlv/ctlv_attestation_gen.h``.
+
+======================  ======  ==========  ================================
+Operation               Type    Access      Purpose
+======================  ======  ==========  ================================
+device-get              action  read        what the device supports
+evidence-get            action  read-write  evidence objects
+mr-get                  query   read        describe the MRs
+mr-read                 query   read        one MR's values
+mr-extend               action  read-write  extend one MR
+======================  ======  ==========  ================================
+
+``device-get`` is always provided. Every other operation is optional, and a
+device provides those its provider implements, which ``op-get`` lists. The
+errors below are the family's own. Those of the transport, such as ``EACCES``
+for a descriptor without the access an operation needs, are in :doc:`ctlv`,
+and once a device is gone or has stopped, every operation fails with
+``ENODEV``. A provider's own failure returns its errno, such as ``EIO``.
+
+Generations
+===========
+
+A counter tells a program whether what it holds is still current. It is 32
+bits wide. An operation that would advance it past its maximum fails with
+``EOVERFLOW`` and stops the device.
+
+``mr-generation``
+    The state of the MRs. It advances with every extension, and a reply that
+    depends on MR state carries the generation it reflects.
+
+Evidence
+========
+
+``device-get``
+--------------
+
+Describes the device: what it supports and the provider's own information. It
+takes no request. The reply carries:
+
+``caps``
+    A record for each part the device has:
+
+    ``caps.evidence``
+        With ``evidence-get``: one of ``objects`` for each object the device
+        produces, up to 16, as `Objects`_ describes.
+
+    ``caps.mr``
+        With MRs: ``flags``, which has ``extend`` where ``mr-extend`` is
+        provided. ``mr-read`` is always provided with MRs.
+
+``info``
+    The device's family's fields, its ``device-info``.
+
+``evidence-get``
+----------------
+
+Writes each object the request names into a blob the caller supplies. A
+signed object is made by the call, over the challenge, and never reused. The
+others, such as an event log, are read as they stand at the call. One call so
+returns signed evidence with the log needed to check it. The request carries:
+
+``challenge``
+    The challenge, described below. Present exactly when a signed object is
+    named.
+
+``objects``
+    1 to 16 objects, each naming one of the ``caps.evidence.objects`` of
+    ``device-get`` by its ``id`` and giving ``data``, the output blob it is
+    written to, sized as described below.
+
+The reply carries:
+
+``results``
+    One per object, in request order, each giving the object's format in
+    exactly one of ``format`` and ``vendor-format``, and its ``length``, the
+    bytes written to its blob.
+
+``mr-generation``
+    Where an object binds MRs, the MR generation the evidence reflects.
+
+``EINVAL``
+    A signed object is named without a challenge, a challenge is given
+    without one, or its length is one a signed object named does not take.
+    Two objects are the same.
+
+``EMSGSIZE``
+    An object's blob is too short for it. The error information's
+    ``required-length`` says how long it has to be.
+
+``ENOENT``
+    An object's ``id`` is not one ``device-get`` lists.
+
+``EPROTO``
+    The root of trust returned an object in a format the device does not
+    list for it.
+
+Access is read-write. Availability is dynamic: a device may switch the
+operation off for a time, and meanwhile it fails with ``EOPNOTSUPP``.
+
+The challenge
+~~~~~~~~~~~~~
+
+A signed object binds the challenge. Each signed object lists its
+``challenge-lengths``, up to 8, each one ``length`` or a range from
+``min-length`` to ``max-length``, and the subsystem keeps them within 1 to 4K.
+The challenge has to be of a length every signed object named lists.
+
+Pass a nonce from the verifier, or a hash that binds such a nonce to the
+caller's own context, such as a key the evidence is to vouch for. Never pass
+data a peer chose. Any process that can run ``evidence-get`` can have the
+device sign what it passes, and one that passes a peer's bytes makes the
+device a signing oracle for that peer.
+
+Objects
+~~~~~~~
+
+Each of ``caps.evidence.objects`` describes one object:
+
+``id``
+    0 to 15, which a request names it by.
+
+``class``
+    What the object is, as ``evidence-class`` below says.
+
+``formats`` and ``vendor-formats``
+    The formats it may have.
+
+``max-length``
+    The longest it gets, however many objects a call asks for.
+
+``challenge-lengths``
+    For a signed object, the challenge lengths it takes.
+
+``evidence-class`` says what an object is:
+
+``signed``
+    Evidence the root of trust signs over the challenge. It carries its own
+    signature.
+
+``event-log``
+    The platform's log of what firmware and the boot chain measured into the
+    MRs, for a verifier to replay. Later extends, through ``mr-extend`` or
+    otherwise, are not in it.
+
+A blob of the object's ``max-length`` always holds it. A shorter one saves
+memory where objects run shorter than their bound, as certificate chains do:
+where the object does not fit, ``evidence-get`` fails with ``EMSGSIZE`` and
+returns no evidence, and ``required-length`` in the error information is the
+length to retry with. The retry collects the evidence anew, and may pass the
+same challenge.
+
+Two enums name the formats:
+
+``evidence-format``
+    ``ctlv-attestation``'s, for a layout a standard defines, such as the
+    CCEL, so every family that produces it uses the same value.
+
+``evidence-vendor-format``
+    For a layout one vendor defines, whose values the device's family gives
+    through a CTLV enum extension point.
+
+The ABI check of ``regen`` covers both enums. An object lists the formats it
+may have, from either enum, in ``formats`` and ``vendor-formats``, and the
+reply names the one written in exactly one of ``format`` and
+``vendor-format``. A new layout is a new value.
+
+Measurement registers
+=====================
+
+Extending an MR folds a digest into its value, so the value commits to every
+extension in order. An MR has a value for each hash algorithm it supports, a
+bank. An MR's ``id`` is the device's handle for it, and ``mr-desc`` describes
+it:
+
+``name``
+    The platform's name for the MR, such as ``rtmr0``.
+
+``supported-hash-algorithms``
+    One for each bank, in the provider's order.
+
+``register-index``
+    The hardware's number for the MR, where it has one.
+
+``caps``
+    ``extend`` where the MR may be extended. A provider still refuses an
+    extend the platform forbids at the time, as with ``EPERM`` for a TPM PCR
+    the current locality may not extend.
+
+Every MR can be read, though a read can still fail, as when the caller lacks
+authorization or the platform's state forbids it.
+
+The name, banks, caps and register index describe an MR, and do not change.
+The values change with every extension, so the two are read apart.
+``mr-get`` describes every MR, which is how a program learns the IDs, or the
+one an ID names. It is answered from what the device keeps, while
+``mr-read``, which returns one MR's values, asks the hardware. ``mr-read`` and
+``mr-extend`` take only the ID.
+
+A hash algorithm is a constant of ``enum hash_algo`` in
+``include/uapi/linux/hash_info.h``, such as ``HASH_ALGO_SHA384``.
+
+Nothing reserves an MR, and the kernel does not arbitrate between its
+writers. Any program with a read-write descriptor can extend it, and so can
+the register's own interface, as for a TPM's PCRs. A program that replays an
+MR from its own records has to make sure, through the system's configuration,
+that it is the only writer; ``mr-generation`` counts only the extensions made
+through the device.
+
+``mr-get``
+----------
+
+Describes every MR, in fragments ordered by ID, or only the MR whose ``id``
+the request carries. Each is one of ``items``, with its ``id`` and its
+``mr-desc``, as `Measurement registers`_ lists it. The query's generation is
+the MR generation, so a continuation fails with ``ESTALE`` once any MR has
+changed.
+
+``ENOENT``
+    No MR has that ID.
+
+Access is read.
+
+``mr-read``
+-----------
+
+Returns the current values of the MR whose ``id`` the request carries. The
+reply carries:
+
+``mr-generation``
+    The MR generation of the values.
+
+``mr-values``
+    One for each bank, in ``mr-get``'s order, with its ``hash-algo`` and
+    ``data``.
+
+``ENOENT``
+    No MR has that ID.
+
+Access is read.
+
+``mr-extend``
+-------------
+
+Extends the MR whose ``id`` the request carries. The request carries one of
+``mr-values`` for each bank of the MR to extend, in any order, each a
+``hash-algo`` and a ``data`` of exactly that algorithm's digest size. The
+banks left out keep their values. The device hashes nothing for the caller.
+
+The reply carries ``mr-generation``, the MR generation after the extension.
+
+``ENOENT``
+    No MR has that ID.
+
+``EOPNOTSUPP``
+    The MR cannot be extended, or the provider cannot extend only some of its
+    banks.
+
+``EPERM``
+    The platform forbids extending the MR at the time.
+
+``EINVAL``
+    The request carries no ``mr-values``, a value is for a bank the MR does not
+    have or repeats one, or a digest has the wrong length.
+
+Access is read-write.
diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst
index d1b53c4e16eb..61051cd28fd1 100644
--- a/Documentation/userspace-api/index.rst
+++ b/Documentation/userspace-api/index.rst
@@ -36,6 +36,7 @@ Security-related interfaces
    mfd_noexec
    spec_ctrl
    tee
+   attestation
    check_exec
 
 Devices and I/O
diff --git a/MAINTAINERS b/MAINTAINERS
index fa450924b921..0c1778e676ed 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4439,6 +4439,7 @@ L:	linux-security-module@vger.kernel.org
 S:	Supported
 F:	Documentation/ctlv/abi/ctlv-attestation*
 F:	Documentation/ctlv/specs/ctlv-attestation*
+F:	Documentation/userspace-api/attestation*.rst
 F:	drivers/attestation/
 F:	include/uapi/ctlv/ctlv_attestation*
 
-- 
2.56.0


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

* [PATCH RFC v2 3/9] attestation: Prepare provider and instance core
  2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
  2026-11-13 16:08 ` [PATCH RFC v2 1/9] attestation: Add the base family specification and generated files Jiri Pirko
  2026-11-13 16:08 ` [PATCH RFC v2 2/9] Documentation: attestation: Add userspace ABI Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
  2026-11-13 16:08 ` [PATCH RFC v2 4/9] attestation: Prepare measurement registers Jiri Pirko
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
  To: linux-coco
  Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
	lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
	James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
	alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
	elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
	peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
	nik.borisov, sergii.dmytruk

From: Jiri Pirko <jiri@nvidia.com>

Add provider validation, instance registration and lifecycle management
for the attestation family. Each instance is a device of the attestation
class, with a node under /dev/attestation/, and embeds its CTLV
instance and node.

The provider descriptor is one immutable evidence and MR contract; MR
state follows in the next patch. The core object can be compile-checked
here. CONFIG_ATTESTATION remains unwired until the CTLV handlers land.

Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- gave attestation its own device, class and /dev/attestation/ nodes,
  each a struct ctlv_device
- dropped the goto from attestation_registry_insert()
- noted that instance indexes are never reused
- selected CRYPTO_HASH_INFO
- selected CTLV instead of depending on it
- kept each guard() on one line
- dropped the journal from the Kconfig help
- dropped read(), poll() and the event teardown
- refused hash algorithms the hash-algo enum does not list
- gave each evidence object a class and the formats it may have
- dropped max_total_output_length
- took each signed object's challenge lengths as a list, ascending
  and disjoint
- left the object count and challenge length limits to CTLV
- added attestation_evidence_write(), for blobs of any length
- let a request omit the challenge
- separated the steps of the longer functions
- took an MR's algorithms in any order, each once
- let mr_extend get the digests of some of an MR's banks
- required mr_read of a provider with MRs, as every MR can be read
- said that an MR's extend cap is fixed
- took the UAPI headers from include/uapi/ctlv/
- named evidence objects by ID, and had the provider give each
  result's format
---
 MAINTAINERS                         |   1 +
 drivers/attestation/Kconfig         |  12 +
 drivers/attestation/Makefile        |   4 +
 drivers/attestation/core/core.c     | 429 ++++++++++++++++++++++++++++
 drivers/attestation/core/internal.h |  21 ++
 include/linux/attestation.h         | 240 ++++++++++++++++
 6 files changed, 707 insertions(+)
 create mode 100644 drivers/attestation/Kconfig
 create mode 100644 drivers/attestation/Makefile
 create mode 100644 drivers/attestation/core/core.c
 create mode 100644 drivers/attestation/core/internal.h
 create mode 100644 include/linux/attestation.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 0c1778e676ed..e57fc8bb9c1d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4441,6 +4441,7 @@ F:	Documentation/ctlv/abi/ctlv-attestation*
 F:	Documentation/ctlv/specs/ctlv-attestation*
 F:	Documentation/userspace-api/attestation*.rst
 F:	drivers/attestation/
+F:	include/linux/attestation*
 F:	include/uapi/ctlv/ctlv_attestation*
 
 ATTO EXPRESSSAS SAS/SATA RAID SCSI DRIVER
diff --git a/drivers/attestation/Kconfig b/drivers/attestation/Kconfig
new file mode 100644
index 000000000000..a6bfb17dd480
--- /dev/null
+++ b/drivers/attestation/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menuconfig ATTESTATION
+	tristate "Attestation"
+	select CRYPTO_HASH_INFO
+	select CTLV
+	help
+	  Provides a CTLV family for evidence and measurement registers.
+
+	  Providers register one instance for a physical or logical device and
+	  translate the common operations to their native service.
+
+	  If you are unsure, say N.
diff --git a/drivers/attestation/Makefile b/drivers/attestation/Makefile
new file mode 100644
index 000000000000..8a75558a7013
--- /dev/null
+++ b/drivers/attestation/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_ATTESTATION) += attestation.o
+
+attestation-y := core/core.o
diff --git a/drivers/attestation/core/core.c b/drivers/attestation/core/core.c
new file mode 100644
index 000000000000..587a0a9616b6
--- /dev/null
+++ b/drivers/attestation/core/core.c
@@ -0,0 +1,429 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <crypto/hash_info.h>
+#include <linux/bitmap.h>
+#include <linux/cleanup.h>
+#include <linux/ctlv.h>
+#include <linux/ctlv_device.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/overflow.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "internal.h"
+
+#define ATTESTATION_MAX_DEVICES 4096
+
+static DEFINE_XARRAY(attestation_instances);
+
+static DEFINE_MUTEX(attestation_registry_lock);
+/* Never reused, so a stale index cannot name a newer instance. */
+static unsigned long attestation_next_index;
+
+/* A reply carries only what hash-algo in ctlv-attestation.yaml lists. */
+static bool attestation_algorithm_valid(enum hash_algo algorithm)
+{
+	return algorithm >= 0 && algorithm <= HASH_ALGO_SHA3_512 && hash_digest_size[algorithm] &&
+	       hash_digest_size[algorithm] <= HASH_MAX_DIGESTSIZE;
+}
+
+static int attestation_mr_props_validate(const struct attestation_provider *provider,
+					 const struct attestation_mr_props *props)
+{
+	DECLARE_BITMAP(seen, HASH_ALGO__LAST) = {};
+	size_t index;
+
+	if (!props || !props->name || !*props->name || strlen(props->name) > 128 ||
+	    !props->algorithms || !props->algorithms_count || props->algorithms_count > 23)
+		return -EINVAL;
+	for (index = 0; index < props->algorithms_count; index++) {
+		enum hash_algo algorithm = props->algorithms[index];
+
+		if (!attestation_algorithm_valid(algorithm) ||
+		    __test_and_set_bit(algorithm, seen))
+			return -EINVAL;
+	}
+	if (props->extendable && !provider->mr_extend)
+		return -EINVAL;
+	return 0;
+}
+
+/* What device-get and evidence-get carry, as ctlv-attestation.yaml bounds it. */
+#define ATTESTATION_EVIDENCE_OBJECTS_MAX 16
+#define ATTESTATION_EVIDENCE_FORMATS_MAX 4
+#define ATTESTATION_CHALLENGE_LENGTHS_MAX 8
+#define ATTESTATION_CHALLENGE_MAX 4096
+/* Moves with each value appended to evidence-format. */
+#define ATTESTATION_EVIDENCE_FORMAT_LAST CTLV_ATTESTATION_EVIDENCE_FORMAT_CCEL
+
+static bool
+attestation_challenge_lengths_valid(const struct attestation_evidence_supported_object *object)
+{
+	size_t index;
+
+	if (object->evidence_class != ATTESTATION_EVIDENCE_SIGNED)
+		return !object->challenge_lengths_count;
+	if (!object->challenge_lengths || !object->challenge_lengths_count ||
+	    object->challenge_lengths_count > ATTESTATION_CHALLENGE_LENGTHS_MAX)
+		return false;
+	for (index = 0; index < object->challenge_lengths_count; index++) {
+		const struct attestation_evidence_challenge_length *length =
+			&object->challenge_lengths[index];
+
+		if (!length->min || length->min > length->max ||
+		    length->max > ATTESTATION_CHALLENGE_MAX ||
+		    (index && object->challenge_lengths[index - 1].max >= length->min))
+			return false;
+	}
+	return true;
+}
+
+static bool
+attestation_formats_valid(const struct attestation_evidence_supported_object *object,
+			  u32 vendor_max)
+{
+	size_t counts[2] = {}, index, prior;
+
+	if (!object->formats || !object->formats_count)
+		return false;
+	for (index = 0; index < object->formats_count; index++) {
+		const struct attestation_evidence_format *format = &object->formats[index];
+		u32 max = format->vendor ? vendor_max : ATTESTATION_EVIDENCE_FORMAT_LAST;
+
+		if (!format->value || format->value > max ||
+		    ++counts[format->vendor] > ATTESTATION_EVIDENCE_FORMATS_MAX)
+			return false;
+		for (prior = 0; prior < index; prior++)
+			if (object->formats[prior].vendor == format->vendor &&
+			    object->formats[prior].value == format->value)
+				return false;
+	}
+	return true;
+}
+
+static int attestation_evidence_caps_validate(const struct attestation_provider *provider,
+					      const struct ctlv_family *family)
+{
+	const struct attestation_evidence_caps *caps = provider->evidence_caps;
+	size_t index;
+	u32 vendor_max;
+
+	if (!caps)
+		return provider->evidence_get ? -EINVAL : 0;
+	if (!provider->evidence_get || !caps->objects || !caps->objects_count ||
+	    caps->objects_count > ATTESTATION_EVIDENCE_OBJECTS_MAX)
+		return -EINVAL;
+
+	vendor_max = ctlv_enum_ext_max(family, CTLV_FAMILY_ATTESTATION,
+				       CTLV_ATTESTATION_ENUM_EXT_EVIDENCE_VENDOR_FORMAT);
+	for (index = 0; index < caps->objects_count; index++) {
+		const struct attestation_evidence_supported_object *object = &caps->objects[index];
+
+		if (object->evidence_class < ATTESTATION_EVIDENCE_SIGNED ||
+		    object->evidence_class > ATTESTATION_EVIDENCE_EVENT_LOG ||
+		    !attestation_formats_valid(object, vendor_max) ||
+		    !attestation_challenge_lengths_valid(object) ||
+		    !object->max_length || object->max_length > UINT_MAX)
+			return -EINVAL;
+	}
+	return 0;
+}
+
+int attestation_provider_validate(const struct attestation_provider *provider,
+				  const struct ctlv_family *family)
+{
+	size_t index;
+	int ret;
+
+	if (!provider)
+		return -EINVAL;
+	ret = attestation_evidence_caps_validate(provider, family);
+	if (ret)
+		return ret;
+	if (!!provider->mrs != !!provider->mrs_count)
+		return -EINVAL;
+	if (provider->mrs_count && !provider->mr_read)
+		return -EINVAL;
+	if ((provider->mr_read || provider->mr_extend) && !provider->mrs_count)
+		return -EINVAL;
+	for (index = 0; index < provider->mrs_count; index++) {
+		ret = attestation_mr_props_validate(provider, &provider->mrs[index]);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static bool attestation_parent_exists(struct device *parent)
+{
+	struct attestation_dev *att;
+	unsigned long index;
+
+	xa_for_each(&attestation_instances, index, att)
+		if (att->node.dev.parent == parent)
+			return true;
+	return false;
+}
+
+static int attestation_registry_insert(struct attestation_dev *att)
+{
+	unsigned long index;
+	void *old;
+	int ret;
+
+	guard(mutex)(&attestation_registry_lock);
+	if (attestation_next_index > UINT_MAX)
+		return -ENOSPC;
+	index = attestation_next_index;
+	ret = xa_reserve(&attestation_instances, index, GFP_KERNEL);
+	if (ret)
+		return ret;
+
+	xa_lock(&attestation_instances);
+	if (attestation_parent_exists(att->node.dev.parent)) {
+		__xa_erase(&attestation_instances, index);
+		ret = -EEXIST;
+	} else {
+		old = __xa_store(&attestation_instances, index, att, GFP_NOWAIT);
+		ret = xa_err(old);
+	}
+	if (!ret) {
+		att->index = index;
+		attestation_next_index++;
+	}
+	xa_unlock(&attestation_instances);
+	return ret;
+}
+
+static void attestation_registry_remove(struct attestation_dev *att)
+{
+	xa_erase(&attestation_instances, att->index);
+}
+
+bool attestation_registered(struct attestation_dev *att)
+{
+	bool registered;
+
+	lockdep_assert_held(&att->lock);
+	xa_lock(&attestation_instances);
+	registered = xa_get_mark(&attestation_instances, att->index, ATTESTATION_REGISTERED);
+	xa_unlock(&attestation_instances);
+	return registered;
+}
+
+void attestation_stop(struct attestation_dev *att)
+{
+	lockdep_assert_held(&att->lock);
+	xa_lock(&attestation_instances);
+	__xa_clear_mark(&attestation_instances, att->index, ATTESTATION_REGISTERED);
+	xa_unlock(&attestation_instances);
+}
+
+/**
+ * attestation_evidence_write - Write an evidence object to its blob.
+ * @object: the requested object
+ * @data: the object's bytes
+ * @length: their length, at most the object's max_length
+ *
+ * A blob too short for the object fails the operation, and the caller is told
+ * how long it has to be.
+ *
+ * Return: 0, -EMSGSIZE where the blob is too short, or another negative error.
+ */
+int attestation_evidence_write(const struct attestation_evidence_object_request *object,
+			       const void *data, size_t length)
+{
+	if (length > object->supported_object->max_length)
+		return -E2BIG;
+	if (length > ctlv_blob_len(object->output))
+		return ctlv_blob_too_short(object->output, length);
+	return ctlv_blob_write(object->output, 0, data, length);
+}
+EXPORT_SYMBOL_NS_GPL(attestation_evidence_write, "ATTESTATION");
+
+void attestation_provide_ops(struct attestation_dev *att)
+{
+	const struct attestation_provider *provider = att->provider;
+	struct ctlv *ctlv = &att->ctlv;
+
+	if (provider->evidence_caps) {
+		ctlv_attestation_evidence_get_provide(ctlv);
+		ctlv_attestation_evidence_get_enable(ctlv);
+	}
+}
+
+/* Runs from the release, so it must not reach the provider's module. */
+static void attestation_state_free(struct attestation_dev *att)
+{
+	mutex_destroy(&att->lock);
+}
+
+static int attestation_op_pre(struct ctlv *ctlv, struct ctlv_op_ctx *ctx)
+{
+	struct attestation_dev *att = attestation_from_ctlv(ctlv);
+
+	mutex_lock(&att->lock);
+	if (!attestation_registered(att)) {
+		mutex_unlock(&att->lock);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static void attestation_op_post(struct ctlv *ctlv, struct ctlv_op_ctx *ctx)
+{
+	mutex_unlock(&attestation_from_ctlv(ctlv)->lock);
+}
+
+static const struct ctlv_hooks attestation_ctlv_hooks = {
+	.pre = attestation_op_pre,
+	.post = attestation_op_post,
+};
+
+/* Kernel callers find a device by its mark, and are drained once it is gone. */
+static void attestation_withdraw(struct attestation_dev *att)
+{
+	xa_lock(&attestation_instances);
+	__xa_clear_mark(&attestation_instances, att->index, ATTESTATION_REGISTERED);
+	xa_unlock(&attestation_instances);
+}
+
+static char *attestation_devnode(const struct device *dev, umode_t *mode)
+{
+	return kasprintf(GFP_KERNEL, "attestation/%s", dev_name(dev));
+}
+
+static void attestation_dev_release(struct device *dev)
+{
+	struct attestation_dev *att = container_of(dev, struct attestation_dev, node.dev);
+
+	attestation_state_free(att);
+	ctlv_fini(&att->ctlv);
+	ctlv_device_fini(&att->node);
+	kfree(att);
+}
+
+/*
+ * The core registers the class, so an open holds the core module and not the
+ * provider's, which may unload while readers remain.
+ */
+static struct ctlv_device_class attestation_class = {
+	.class = {
+		.name = "attestation",
+		.dev_release = attestation_dev_release,
+		.devnode = attestation_devnode,
+	},
+	.max_devices = ATTESTATION_MAX_DEVICES,
+};
+
+struct attestation_dev *_attestation_alloc(struct device *parent,
+					   const struct attestation_provider *provider,
+					   const struct ctlv_family *family, size_t size,
+					   size_t offset)
+{
+	struct attestation_dev *att;
+	int ret;
+
+	if (!parent || WARN_ON_ONCE(offset || size < sizeof(*att)) ||
+	    !ctlv_family_inherits(family, &ctlv_attestation_family))
+		return ERR_PTR(-EINVAL);
+	ret = attestation_provider_validate(provider, family);
+	if (ret)
+		return ERR_PTR(ret);
+
+	att = kzalloc(size, GFP_KERNEL);
+	if (!att)
+		return ERR_PTR(-ENOMEM);
+	att->provider = provider;
+	mutex_init(&att->lock);
+
+	/* From here on, the release frees whatever was set up. */
+	ret = ctlv_device_init(&att->node, &attestation_class, parent, &att->ctlv);
+	if (!ret)
+		ret = ctlv_init(&att->ctlv, &att->node.dev, family, &attestation_ctlv_hooks);
+	if (ret) {
+		put_device(&att->node.dev);
+		return ERR_PTR(ret);
+	}
+	attestation_provide_ops(att);
+	return att;
+}
+EXPORT_SYMBOL_NS_GPL(_attestation_alloc, "ATTESTATION");
+
+/**
+ * attestation_register - Make an allocated device reachable.
+ * @att: device from attestation_alloc()
+ *
+ * Return: 0, or a negative errno with @att still to be put.
+ */
+int attestation_register(struct attestation_dev *att)
+{
+	int ret;
+
+	ret = attestation_registry_insert(att);
+	if (ret)
+		return ret;
+	ctlv_publish(&att->ctlv);
+	xa_lock(&attestation_instances);
+	__xa_set_mark(&attestation_instances, att->index, ATTESTATION_REGISTERED);
+	xa_unlock(&attestation_instances);
+
+	ret = ctlv_device_add(&att->node);
+	if (ret)
+		goto err_withdraw;
+	return 0;
+
+err_withdraw:
+	attestation_withdraw(att);
+	ctlv_unpublish(&att->ctlv);
+	attestation_registry_remove(att);
+	return ret;
+}
+EXPORT_SYMBOL_NS_GPL(attestation_register, "ATTESTATION");
+
+/**
+ * attestation_unregister - Cut a registered device off from every caller.
+ * @att: registered device
+ *
+ * No provider callback runs once this returns, though open files may still
+ * hold @att.
+ */
+void attestation_unregister(struct attestation_dev *att)
+{
+	/* No new opens, and no node to open. */
+	ctlv_device_del(&att->node);
+	attestation_withdraw(att);
+	ctlv_unpublish(&att->ctlv);
+	attestation_registry_remove(att);
+}
+EXPORT_SYMBOL_NS_GPL(attestation_unregister, "ATTESTATION");
+
+/**
+ * attestation_put - Drop the reference attestation_alloc() returned.
+ * @att: device
+ */
+void attestation_put(struct attestation_dev *att)
+{
+	put_device(&att->node.dev);
+}
+EXPORT_SYMBOL_NS_GPL(attestation_put, "ATTESTATION");
+
+static int __init attestation_module_init(void)
+{
+	return ctlv_device_class_register(&attestation_class);
+}
+
+static void __exit attestation_module_exit(void)
+{
+	WARN_ON_ONCE(!xa_empty(&attestation_instances));
+	xa_destroy(&attestation_instances);
+	ctlv_device_class_unregister(&attestation_class);
+}
+
+module_init(attestation_module_init);
+module_exit(attestation_module_exit);
+MODULE_DESCRIPTION("Attestation core");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("CTLV");
diff --git a/drivers/attestation/core/internal.h b/drivers/attestation/core/internal.h
new file mode 100644
index 000000000000..f42bc3cd9a35
--- /dev/null
+++ b/drivers/attestation/core/internal.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _ATTESTATION_INTERNAL_H
+#define _ATTESTATION_INTERNAL_H
+
+#include <linux/attestation.h>
+#include <linux/ctlv.h>
+#include <linux/mutex.h>
+#include <linux/xarray.h>
+
+#include "../generated/ctlv_attestation_gen.h"
+
+#define ATTESTATION_REGISTERED XA_MARK_1
+
+bool attestation_registered(struct attestation_dev *att);
+void attestation_stop(struct attestation_dev *att);
+
+int attestation_provider_validate(const struct attestation_provider *provider,
+				  const struct ctlv_family *family);
+void attestation_provide_ops(struct attestation_dev *att);
+
+#endif /* _ATTESTATION_INTERNAL_H */
diff --git a/include/linux/attestation.h b/include/linux/attestation.h
new file mode 100644
index 000000000000..29bb776bfb41
--- /dev/null
+++ b/include/linux/attestation.h
@@ -0,0 +1,240 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _LINUX_ATTESTATION_H
+#define _LINUX_ATTESTATION_H
+
+#include <crypto/hash.h>
+#include <linux/build_bug.h>
+#include <linux/ctlv.h>
+#include <linux/ctlv_device.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+#include <uapi/ctlv/ctlv_attestation_gen.h>
+#include <uapi/linux/hash_info.h>
+
+struct attestation_dev;
+struct ctlv_blob;
+struct ctlv_family;
+
+enum attestation_evidence_class {
+	ATTESTATION_EVIDENCE_SIGNED = CTLV_ATTESTATION_EVIDENCE_CLASS_SIGNED,
+	ATTESTATION_EVIDENCE_EVENT_LOG = CTLV_ATTESTATION_EVIDENCE_CLASS_EVENT_LOG,
+};
+
+/**
+ * struct attestation_digest - One MR digest.
+ * @algorithm: hash algorithm
+ * @data: digest bytes
+ */
+struct attestation_digest {
+	enum hash_algo algorithm;
+	u8 data[HASH_MAX_DIGESTSIZE];
+};
+
+/**
+ * struct attestation_digest_set - Digests for an MR's banks, in the MR's order.
+ * @values_count: number of digests
+ * @values: digest array
+ */
+struct attestation_digest_set {
+	size_t values_count;
+	struct attestation_digest *values;
+};
+
+/**
+ * struct attestation_mr_props - Immutable MR properties.
+ * @id: provider-unique MR ID
+ * @name: MR name
+ * @algorithms: bank hash algorithms, each once, in the order replies list them
+ * @algorithms_count: number of algorithms
+ * @has_register_index: whether @register_index is valid
+ * @register_index: native register index
+ * @extendable: MR may be extended. Fixed: the provider instead refuses an
+ *	extend the platform forbids at the time, e.g. -EPERM for a TPM locality.
+ */
+struct attestation_mr_props {
+	unsigned int id;
+	const char *name;
+	const enum hash_algo *algorithms;
+	size_t algorithms_count;
+	bool has_register_index;
+	unsigned int register_index;
+	bool extendable;
+};
+
+/**
+ * struct attestation_evidence_format - The layout of an evidence object.
+ * @vendor: @value is an evidence-vendor-format value the device's family
+ *	gives, not an evidence-format one
+ * @value: the value, such as CTLV_ATTESTATION_EVIDENCE_FORMAT_CCEL
+ */
+struct attestation_evidence_format {
+	bool vendor;
+	u32 value;
+};
+
+/**
+ * struct attestation_evidence_challenge_length - Challenge lengths taken.
+ * @min: shortest challenge bytes, at least 1
+ * @max: longest challenge bytes, @min for one exact length, at most 4096
+ */
+struct attestation_evidence_challenge_length {
+	size_t min;
+	size_t max;
+};
+
+/**
+ * struct attestation_evidence_supported_object - One evidence object.
+ * @evidence_class: what the object is
+ * @formats: the layouts it may have, at most four of each kind
+ * @formats_count: number of formats
+ * @challenge_lengths: a signed object's challenge lengths, ascending and
+ *	disjoint; none for any other object
+ * @challenge_lengths_count: number of challenge lengths, at most 8
+ * @max_length: longest the object gets, in bytes
+ * @binds_mrs: the object covers MR values, as they are or by a digest, so
+ *	the reply carries mr-generation
+ *
+ * The object's index in the provider's objects is its ID.
+ */
+struct attestation_evidence_supported_object {
+	enum attestation_evidence_class evidence_class;
+	const struct attestation_evidence_format *formats;
+	size_t formats_count;
+	const struct attestation_evidence_challenge_length *challenge_lengths;
+	size_t challenge_lengths_count;
+	size_t max_length;
+	bool binds_mrs;
+};
+
+/**
+ * struct attestation_evidence_caps - Immutable evidence capabilities.
+ * @objects: supported evidence objects, at most 16
+ * @objects_count: number of objects
+ */
+struct attestation_evidence_caps {
+	const struct attestation_evidence_supported_object *objects;
+	size_t objects_count;
+};
+
+/**
+ * struct attestation_evidence_object_request - One resolved output object.
+ * @supported_object: the object requested
+ * @output: resolved output blob
+ */
+struct attestation_evidence_object_request {
+	const struct attestation_evidence_supported_object *supported_object;
+	struct ctlv_blob *output;
+};
+
+/**
+ * struct attestation_evidence_request - Validated evidence request.
+ * @challenge: challenge bytes, of a length every signed object requested
+ *	takes, or NULL where the request names no signed object
+ * @challenge_length: challenge length, 0 without a challenge
+ * @objects: requested output objects
+ * @objects_count: number of objects
+ */
+struct attestation_evidence_request {
+	const u8 *challenge;
+	size_t challenge_length;
+	const struct attestation_evidence_object_request *objects;
+	size_t objects_count;
+};
+
+/**
+ * struct attestation_evidence_object_result - What a provider made of one object.
+ * @format: the layout it came in, one of the object's formats
+ * @length: bytes written
+ */
+struct attestation_evidence_object_result {
+	struct attestation_evidence_format format;
+	size_t length;
+};
+
+/**
+ * struct attestation_evidence_result - Provider evidence result.
+ * @objects: one for every requested object, in request order
+ */
+struct attestation_evidence_result {
+	struct attestation_evidence_object_result *objects;
+};
+
+/**
+ * struct attestation_provider - Immutable provider operations and properties.
+ * @evidence_get: produce requested evidence, each object written with
+ *	attestation_evidence_write() and its result filled in
+ * @mr_read: read one MR; every MR can be read, so required with @mrs
+ * @mr_extend: extend the banks of one MR it gets digests for, leaving the
+ *	others, or fail with -EOPNOTSUPP before changing any if it cannot
+ * @evidence_caps: evidence capabilities, or NULL
+ * @mrs: fixed MR properties
+ * @mrs_count: number of fixed MRs
+ */
+struct attestation_provider {
+	int (*evidence_get)(struct attestation_dev *att,
+			    const struct attestation_evidence_request *request,
+			    struct attestation_evidence_result *result);
+	int (*mr_read)(struct attestation_dev *att, unsigned int mr_id,
+		       struct attestation_digest_set *values);
+	int (*mr_extend)(struct attestation_dev *att, unsigned int mr_id,
+			 const struct attestation_digest_set *values);
+	const struct attestation_evidence_caps *evidence_caps;
+	const struct attestation_mr_props *mrs;
+	size_t mrs_count;
+};
+
+/**
+ * struct attestation_dev - One attestation device.
+ * @node: the device's node, whose release frees the driver's struct embedding
+ *	this
+ *
+ * A driver embeds this first in its own struct, allocated by
+ * attestation_alloc(), and reaches that struct with container_of().
+ */
+struct attestation_dev {
+	struct ctlv_device node;
+	/* private: */
+	struct ctlv ctlv;
+	const struct attestation_provider *provider;
+	unsigned int index;
+
+	/* Mutable instance state. */
+	struct mutex lock;
+
+};
+
+/* What a CTLV callback of a family inheriting ctlv-attestation was handed. */
+static inline struct attestation_dev *attestation_from_ctlv(struct ctlv *ctlv)
+{
+	return container_of(ctlv, struct attestation_dev, ctlv);
+}
+
+struct attestation_dev *_attestation_alloc(struct device *parent,
+					   const struct attestation_provider *provider,
+					   const struct ctlv_family *family, size_t size,
+					   size_t offset);
+
+/**
+ * attestation_alloc - Allocate a driver struct embedding an attestation device.
+ * @parent: the device attested
+ * @provider: provider operations and properties, which must outlive the device
+ * @family: the driver's CTLV family, which inherits ctlv-attestation
+ * @type: the driver's struct
+ * @member: its struct attestation_dev, the first member
+ *
+ * Return: the driver's struct, released by attestation_put(), or an error
+ * pointer.
+ */
+#define attestation_alloc(parent, provider, family, type, member)		\
+	((type *)_attestation_alloc(parent, provider, family, sizeof(type),	\
+				    offsetof(type, member)))
+
+int attestation_register(struct attestation_dev *att);
+void attestation_unregister(struct attestation_dev *att);
+void attestation_put(struct attestation_dev *att);
+
+int attestation_evidence_write(const struct attestation_evidence_object_request *object,
+			       const void *data, size_t length);
+#endif /* _LINUX_ATTESTATION_H */
-- 
2.56.0


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

* [PATCH RFC v2 4/9] attestation: Prepare measurement registers
  2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
                   ` (2 preceding siblings ...)
  2026-11-13 16:08 ` [PATCH RFC v2 3/9] attestation: Prepare provider and instance core Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
  2026-11-13 16:08 ` [PATCH RFC v2 5/9] attestation: Add CTLV operation handling Jiri Pirko
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
  To: linux-coco
  Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
	lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
	James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
	alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
	elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
	peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
	nik.borisov, sergii.dmytruk

From: Jiri Pirko <jiri@nvidia.com>

Add the fixed MRs from the provider descriptor, their lookup and MR
extension, which advances an instance-wide MR generation.

The MR objects can be compile-checked here. CONFIG_ATTESTATION remains
unwired until the CTLV operation handlers complete the module.

Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- kept the MR state in struct attestation_dev, which providers embed
- dropped the MR journal and its notifications
- counted MR changes in a plain MR generation
- retitled from "Prepare measurement register journal"
- used the helpers generated for the rewritten spec
- let an extend carry values for some of an MR's banks
- always provided mr-read with MRs
---
 drivers/attestation/Makefile        |  2 +-
 drivers/attestation/core/core.c     | 71 +++++++++++++++++++++++++++++
 drivers/attestation/core/internal.h | 13 ++++++
 drivers/attestation/core/mr.c       | 64 ++++++++++++++++++++++++++
 include/linux/attestation.h         |  5 +-
 5 files changed, 153 insertions(+), 2 deletions(-)
 create mode 100644 drivers/attestation/core/mr.c

diff --git a/drivers/attestation/Makefile b/drivers/attestation/Makefile
index 8a75558a7013..28036586f5d8 100644
--- a/drivers/attestation/Makefile
+++ b/drivers/attestation/Makefile
@@ -1,4 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_ATTESTATION) += attestation.o
 
-attestation-y := core/core.o
+attestation-y := core/core.o core/mr.o
diff --git a/drivers/attestation/core/core.c b/drivers/attestation/core/core.c
index 587a0a9616b6..24abe0923306 100644
--- a/drivers/attestation/core/core.c
+++ b/drivers/attestation/core/core.c
@@ -213,6 +213,12 @@ bool attestation_registered(struct attestation_dev *att)
 	return registered;
 }
 
+void attestation_mr_changed(struct attestation_dev *att)
+{
+	lockdep_assert_held(&att->lock);
+	att->mr_generation++;
+}
+
 void attestation_stop(struct attestation_dev *att)
 {
 	lockdep_assert_held(&att->lock);
@@ -221,6 +227,38 @@ void attestation_stop(struct attestation_dev *att)
 	xa_unlock(&attestation_instances);
 }
 
+static struct attestation_mr *attestation_mr_alloc(struct attestation_dev *att,
+						   const struct attestation_mr_props *props)
+{
+	struct attestation_mr *mr;
+	int ret;
+
+	ret = attestation_mr_props_validate(att->provider, props);
+	if (ret)
+		return ERR_PTR(ret);
+	if (xa_load(&att->mrs, props->id))
+		return ERR_PTR(-EEXIST);
+	mr = kzalloc_obj(*mr, GFP_KERNEL);
+	if (!mr)
+		return ERR_PTR(-ENOMEM);
+	mr->props = props;
+	ret = xa_insert(&att->mrs, props->id, mr, GFP_KERNEL);
+	if (ret) {
+		kfree(mr);
+		return ERR_PTR(ret);
+	}
+	return mr;
+}
+
+struct attestation_mr *attestation_mr_lookup_live(struct attestation_dev *att, unsigned int mr_id)
+{
+	struct attestation_mr *mr;
+
+	lockdep_assert_held(&att->lock);
+	mr = xa_load(&att->mrs, mr_id);
+	return mr ? mr : ERR_PTR(-ENOENT);
+}
+
 /**
  * attestation_evidence_write - Write an evidence object to its blob.
  * @object: the requested object
@@ -252,11 +290,41 @@ void attestation_provide_ops(struct attestation_dev *att)
 		ctlv_attestation_evidence_get_provide(ctlv);
 		ctlv_attestation_evidence_get_enable(ctlv);
 	}
+
+	if (provider->mrs_count) {
+		ctlv_attestation_mr_get_provide(ctlv);
+		ctlv_attestation_mr_read_provide(ctlv);
+		if (provider->mr_extend)
+			ctlv_attestation_mr_extend_provide(ctlv);
+	}
+}
+
+static int attestation_mrs_create(struct attestation_dev *att)
+{
+	const struct attestation_provider *provider = att->provider;
+	size_t index;
+
+	for (index = 0; index < provider->mrs_count; index++) {
+		struct attestation_mr *mr;
+
+		mr = attestation_mr_alloc(att, &provider->mrs[index]);
+		if (IS_ERR(mr))
+			return PTR_ERR(mr);
+	}
+	return 0;
 }
 
 /* Runs from the release, so it must not reach the provider's module. */
 static void attestation_state_free(struct attestation_dev *att)
 {
+	struct attestation_mr *mr;
+	unsigned long index;
+
+	xa_for_each(&att->mrs, index, mr) {
+		xa_erase(&att->mrs, index);
+		kfree(mr);
+	}
+	xa_destroy(&att->mrs);
 	mutex_destroy(&att->lock);
 }
 
@@ -338,11 +406,14 @@ struct attestation_dev *_attestation_alloc(struct device *parent,
 		return ERR_PTR(-ENOMEM);
 	att->provider = provider;
 	mutex_init(&att->lock);
+	xa_init(&att->mrs);
 
 	/* From here on, the release frees whatever was set up. */
 	ret = ctlv_device_init(&att->node, &attestation_class, parent, &att->ctlv);
 	if (!ret)
 		ret = ctlv_init(&att->ctlv, &att->node.dev, family, &attestation_ctlv_hooks);
+	if (!ret)
+		ret = attestation_mrs_create(att);
 	if (ret) {
 		put_device(&att->node.dev);
 		return ERR_PTR(ret);
diff --git a/drivers/attestation/core/internal.h b/drivers/attestation/core/internal.h
index f42bc3cd9a35..19c2ade5e6db 100644
--- a/drivers/attestation/core/internal.h
+++ b/drivers/attestation/core/internal.h
@@ -11,11 +11,24 @@
 
 #define ATTESTATION_REGISTERED XA_MARK_1
 
+struct attestation_mr {
+	const struct attestation_mr_props *props;
+};
+
 bool attestation_registered(struct attestation_dev *att);
 void attestation_stop(struct attestation_dev *att);
+void attestation_mr_changed(struct attestation_dev *att);
 
 int attestation_provider_validate(const struct attestation_provider *provider,
 				  const struct ctlv_family *family);
 void attestation_provide_ops(struct attestation_dev *att);
 
+struct attestation_mr *attestation_mr_lookup_live(struct attestation_dev *att, unsigned int mr_id);
+int attestation_mr_values_validate(const struct attestation_mr_props *props,
+				   const struct attestation_digest_set *values, bool all_banks);
+int attestation_mr_extend_locked(struct attestation_dev *att, struct attestation_mr *mr,
+				 const struct attestation_digest_set *values);
+
+int attestation_mr_props_encode(struct ctlv_cursor *reply, const struct attestation_mr *mr);
+
 #endif /* _ATTESTATION_INTERNAL_H */
diff --git a/drivers/attestation/core/mr.c b/drivers/attestation/core/mr.c
new file mode 100644
index 000000000000..44a8c06df0c6
--- /dev/null
+++ b/drivers/attestation/core/mr.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/limits.h>
+
+#include "internal.h"
+
+/* @values follow the banks' order, so each bank comes once at most. */
+int attestation_mr_values_validate(const struct attestation_mr_props *props,
+				   const struct attestation_digest_set *values, bool all_banks)
+{
+	size_t bank = 0, index;
+
+	if (!values || !values->values || !values->values_count ||
+	    (all_banks && values->values_count != props->algorithms_count))
+		return -EINVAL;
+	for (index = 0; index < values->values_count; index++, bank++) {
+		while (bank < props->algorithms_count &&
+		       props->algorithms[bank] != values->values[index].algorithm)
+			bank++;
+		if (bank == props->algorithms_count)
+			return -EINVAL;
+	}
+	return 0;
+}
+
+int attestation_mr_extend_locked(struct attestation_dev *att, struct attestation_mr *mr,
+				 const struct attestation_digest_set *values)
+{
+	int ret;
+
+	lockdep_assert_held(&att->lock);
+	if (att->mr_generation == UINT_MAX) {
+		dev_warn(att->node.dev.parent,
+			 "attestation instance %u stopped: mr generation exhausted\n",
+			 att->index);
+		attestation_stop(att);
+		return -EOVERFLOW;
+	}
+	ret = att->provider->mr_extend(att, mr->props->id, values);
+	if (ret)
+		return ret;
+	attestation_mr_changed(att);
+	return 0;
+}
+
+int attestation_mr_props_encode(struct ctlv_cursor *reply, const struct attestation_mr *mr)
+{
+	u64 caps = 0;
+	size_t index;
+	bool ok = true;
+
+	ok &= ctlv_attestation_mr_get_items_id_put(reply, mr->props->id);
+	ok &= ctlv_attestation_mr_desc_name_put(reply, mr->props->name);
+	for (index = 0; index < mr->props->algorithms_count; index++)
+		ok &= ctlv_attestation_mr_desc_supported_hash_algorithms_put(
+			reply, mr->props->algorithms[index]);
+	if (mr->props->has_register_index)
+		ok &= ctlv_attestation_mr_desc_register_index_put(reply, mr->props->register_index);
+	if (mr->props->extendable)
+		caps |= CTLV_ATTESTATION_MR_CAPS_EXTEND;
+	ok &= ctlv_attestation_mr_desc_caps_put(reply, caps);
+	return ok ? 0 : -EMSGSIZE;
+}
diff --git a/include/linux/attestation.h b/include/linux/attestation.h
index 29bb776bfb41..7745938092c7 100644
--- a/include/linux/attestation.h
+++ b/include/linux/attestation.h
@@ -9,6 +9,7 @@
 #include <linux/device.h>
 #include <linux/mutex.h>
 #include <linux/types.h>
+#include <linux/xarray.h>
 
 #include <uapi/ctlv/ctlv_attestation_gen.h>
 #include <uapi/linux/hash_info.h>
@@ -200,9 +201,11 @@ struct attestation_dev {
 	const struct attestation_provider *provider;
 	unsigned int index;
 
-	/* Mutable instance state. */
+	/* Mutable instance and MR state. */
 	struct mutex lock;
+	unsigned int mr_generation;
 
+	struct xarray mrs;
 };
 
 /* What a CTLV callback of a family inheriting ctlv-attestation was handed. */
-- 
2.56.0


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

* [PATCH RFC v2 5/9] attestation: Add CTLV operation handling
  2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
                   ` (3 preceding siblings ...)
  2026-11-13 16:08 ` [PATCH RFC v2 4/9] attestation: Prepare measurement registers Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
  2026-11-13 16:08 ` [PATCH RFC v2 6/9] Documentation: attestation: Describe how to write a provider Jiri Pirko
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
  To: linux-coco
  Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
	lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
	James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
	alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
	elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
	peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
	nik.borisov, sergii.dmytruk

From: Jiri Pirko <jiri@nvidia.com>

Translate CTLV evidence and MR operations to the provider core and wire
the attestation family into the kernel build.

Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- handed the op handlers the CTLV instance
- put resolved blobs on evidence decode errors
- continued an MR dump past an MR that does not fit
- dropped the MR log query
- dropped the total output length check and its E2BIG
- reported each evidence object's class and formats
- reported each signed object's challenge lengths as a list, and took
  a challenge of a length every signed object named lists
- left the MR ID and hash algorithm bounds to CTLV
- used the helpers generated for the rewritten spec
- let evidence-get omit the challenge
- took output blobs of any length
- separated the steps of the longer functions
- let mr-extend extend some of an MR's banks, in any order
- replied to mr-read in the provider's bank order
- dropped the MR read caps, as every MR can be read
- said record instead of nest, the root being one
- took evidence-get's objects by ID, and replied with each one's
  format and length
---
 drivers/Kconfig                 |   2 +
 drivers/Makefile                |   1 +
 drivers/attestation/Makefile    |   3 +-
 drivers/attestation/core/ctlv.c | 499 ++++++++++++++++++++++++++++++++
 4 files changed, 504 insertions(+), 1 deletion(-)
 create mode 100644 drivers/attestation/core/ctlv.c

diff --git a/drivers/Kconfig b/drivers/Kconfig
index f2bed2ddeb66..5458886b0110 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -25,6 +25,8 @@ source "drivers/cache/Kconfig"
 
 source "drivers/connector/Kconfig"
 
+source "drivers/attestation/Kconfig"
+
 source "drivers/firmware/Kconfig"
 
 source "drivers/fwctl/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 0841ea851847..597db8c49da9 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -135,6 +135,7 @@ obj-$(CONFIG_MEMSTICK)		+= memstick/
 obj-$(CONFIG_INFINIBAND)	+= infiniband/
 obj-y				+= firmware/
 obj-$(CONFIG_FWCTL)		+= fwctl/
+obj-$(CONFIG_ATTESTATION)	+= attestation/
 obj-$(CONFIG_CRYPTO)		+= crypto/
 obj-$(CONFIG_SUPERH)		+= sh/
 obj-y				+= clocksource/
diff --git a/drivers/attestation/Makefile b/drivers/attestation/Makefile
index 28036586f5d8..e4bff7633d18 100644
--- a/drivers/attestation/Makefile
+++ b/drivers/attestation/Makefile
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_ATTESTATION) += attestation.o
 
-attestation-y := core/core.o core/mr.o
+attestation-y := core/core.o core/ctlv.o core/mr.o \
+		 generated/ctlv_attestation_gen.o
diff --git a/drivers/attestation/core/ctlv.c b/drivers/attestation/core/ctlv.c
new file mode 100644
index 000000000000..5299649a6066
--- /dev/null
+++ b/drivers/attestation/core/ctlv.c
@@ -0,0 +1,499 @@
+// 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>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "internal.h"
+
+static bool attestation_challenge_length_put(struct ctlv_cursor *object, size_t min, size_t max)
+{
+	struct ctlv_cursor entry;
+	bool ok;
+
+	if (!ctlv_attestation_device_get_objects_challenge_lengths_start(object, &entry))
+		return false;
+	if (min == max) {
+		ok = ctlv_attestation_device_get_challenge_lengths_length_put(&entry, min);
+	} else {
+		ok = ctlv_attestation_device_get_challenge_lengths_min_length_put(&entry, min);
+		ok &= ctlv_attestation_device_get_challenge_lengths_max_length_put(&entry, max);
+	}
+	ctlv_record_end(&entry);
+	return ok;
+}
+
+static bool attestation_evidence_object_put(struct ctlv_cursor *object, size_t id,
+					    const struct attestation_evidence_supported_object *cap)
+{
+	size_t index;
+	bool ok;
+
+	ok = ctlv_attestation_device_get_objects_id_put(object, id);
+	ok &= ctlv_attestation_device_get_objects_class_put(object, cap->evidence_class);
+	for (index = 0; index < cap->formats_count; index++) {
+		u32 value = cap->formats[index].value;
+
+		if (cap->formats[index].vendor)
+			ok &= ctlv_attestation_device_get_objects_vendor_formats_put(object, value);
+		else
+			ok &= ctlv_attestation_device_get_objects_formats_put(object, value);
+	}
+	ok &= ctlv_attestation_device_get_objects_max_length_put(object, cap->max_length);
+	for (index = 0; index < cap->challenge_lengths_count; index++)
+		ok &= attestation_challenge_length_put(object, cap->challenge_lengths[index].min,
+						       cap->challenge_lengths[index].max);
+	return ok;
+}
+
+static int attestation_device_caps_put(struct attestation_dev *att, struct ctlv_cursor *reply)
+{
+	const struct attestation_provider *provider = att->provider;
+	struct ctlv_cursor caps_record, group, object;
+	bool ok = true;
+	size_t index;
+	u64 flags;
+
+	if (!ctlv_attestation_device_get_caps_start(reply, &caps_record))
+		return -EMSGSIZE;
+
+	if (provider->evidence_caps) {
+		const struct attestation_evidence_caps *caps = provider->evidence_caps;
+
+		if (!ctlv_attestation_device_get_caps_evidence_start(&caps_record, &group))
+			return -EMSGSIZE;
+
+		for (index = 0; index < caps->objects_count; index++) {
+			if (!ctlv_attestation_device_get_evidence_objects_start(&group, &object)) {
+				ok = false;
+				break;
+			}
+			ok &= attestation_evidence_object_put(&object, index,
+							      &caps->objects[index]);
+			ctlv_record_end(&object);
+		}
+		ctlv_record_end(&group);
+	}
+
+	if (provider->mrs_count) {
+		if (!ctlv_attestation_device_get_caps_mr_start(&caps_record, &group))
+			return -EMSGSIZE;
+
+		flags = 0;
+		if (provider->mr_extend)
+			flags |= CTLV_ATTESTATION_MR_DEVICE_CAPS_EXTEND;
+		ok &= ctlv_attestation_device_get_mr_flags_put(&group, flags);
+		ctlv_record_end(&group);
+	}
+	ctlv_record_end(&caps_record);
+	return ok ? 0 : -EMSGSIZE;
+}
+
+int ctlv_attestation_device_get_action(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+				       const struct ctlv_cursor *request, struct ctlv_cursor *reply)
+{
+	struct attestation_dev *att = attestation_from_ctlv(ctlv);
+	struct ctlv_cursor info;
+	int ret;
+
+	ret = attestation_device_caps_put(att, reply);
+	if (ret)
+		return ret;
+	if (!ctlv_attestation_device_get_info_start(reply, &info))
+		return -EMSGSIZE;
+	ret = ctlv_ext_fill(ctx, request, &info);
+	ctlv_record_end(&info);
+	return ret;
+}
+
+static void attestation_evidence_request_free(struct attestation_evidence_object_request *objects,
+					      size_t count)
+{
+	while (count) {
+		count--;
+		if (objects[count].output)
+			ctlv_blob_put(objects[count].output);
+	}
+	kfree(objects);
+}
+
+static int
+attestation_evidence_decode_objects(struct attestation_dev *att, struct ctlv_op_ctx *ctx,
+				    const struct ctlv_cursor *request,
+				    struct attestation_evidence_request *decoded,
+				    struct attestation_evidence_object_request **objects_out)
+{
+	const struct attestation_evidence_caps *caps = att->provider->evidence_caps;
+	struct attestation_evidence_object_request *objects;
+	struct ctlv_cursor object;
+	size_t count = 0, index = 0, previous;
+	int ret;
+
+	ctlv_attestation_evidence_get_objects_for_each(&object, request) count++;
+	objects = kcalloc(count, sizeof(*objects), GFP_KERNEL);
+	if (!objects)
+		return -ENOMEM;
+
+	ctlv_attestation_evidence_get_objects_for_each(&object, request)
+	{
+		const struct attestation_evidence_supported_object *supported;
+		u64 id;
+
+		id = ctlv_attestation_evidence_get_objects_id_get(&object);
+		if (id >= caps->objects_count) {
+			ret = -ENOENT;
+			goto err_free;
+		}
+		supported = &caps->objects[id];
+		for (previous = 0; previous < index; previous++)
+			if (objects[previous].supported_object == supported) {
+				ret = -EINVAL;
+				goto err_free;
+			}
+
+		objects[index].supported_object = supported;
+		objects[index].output =
+			ctlv_attestation_evidence_get_objects_data_resolve(ctx, &object);
+		if (IS_ERR(objects[index].output)) {
+			ret = PTR_ERR(objects[index].output);
+			objects[index].output = NULL;
+			goto err_free;
+		}
+		index++;
+	}
+
+	decoded->objects = objects;
+	decoded->objects_count = count;
+	*objects_out = objects;
+	return 0;
+
+err_free:
+	attestation_evidence_request_free(objects, count);
+	return ret;
+}
+
+static bool
+attestation_challenge_length_supported(const struct attestation_evidence_supported_object *object,
+				       size_t length)
+{
+	size_t index;
+
+	for (index = 0; index < object->challenge_lengths_count; index++) {
+		if (length >= object->challenge_lengths[index].min &&
+		    length <= object->challenge_lengths[index].max)
+			return true;
+	}
+	return false;
+}
+
+/* A challenge goes with signed objects only, and has to suit each. */
+static int attestation_evidence_challenge_check(const struct attestation_evidence_request *decoded)
+{
+	bool signed_named = false;
+	size_t index;
+
+	for (index = 0; index < decoded->objects_count; index++) {
+		const struct attestation_evidence_supported_object *object =
+			decoded->objects[index].supported_object;
+
+		if (object->evidence_class != ATTESTATION_EVIDENCE_SIGNED)
+			continue;
+		signed_named = true;
+		if (!decoded->challenge ||
+		    !attestation_challenge_length_supported(object, decoded->challenge_length))
+			return -EINVAL;
+	}
+	return !signed_named && decoded->challenge ? -EINVAL : 0;
+}
+
+static int attestation_evidence_request_decode(struct attestation_dev *att, struct ctlv_op_ctx *ctx,
+					       const struct ctlv_cursor *request,
+					       struct attestation_evidence_request *decoded,
+					       struct attestation_evidence_object_request **objects)
+{
+	u32 challenge_length;
+	int ret;
+
+	if (ctlv_attestation_evidence_get_challenge_present(request)) {
+		decoded->challenge =
+			ctlv_attestation_evidence_get_challenge_get(request, &challenge_length);
+		decoded->challenge_length = challenge_length;
+	}
+
+	ret = attestation_evidence_decode_objects(att, ctx, request, decoded, objects);
+	if (ret)
+		return ret;
+	return attestation_evidence_challenge_check(decoded);
+}
+
+static bool attestation_format_in(const struct attestation_evidence_supported_object *object,
+				  const struct attestation_evidence_format *format)
+{
+	size_t index;
+
+	for (index = 0; index < object->formats_count; index++)
+		if (object->formats[index].vendor == format->vendor &&
+		    object->formats[index].value == format->value)
+			return true;
+	return false;
+}
+
+static bool
+attestation_evidence_result_put(struct ctlv_cursor *reply,
+				const struct attestation_evidence_supported_object *object,
+				const struct attestation_evidence_object_result *produced)
+{
+	u32 value = produced->format.value;
+	struct ctlv_cursor entry;
+	bool ok;
+
+	if (!ctlv_attestation_evidence_get_results_start(reply, &entry))
+		return false;
+	if (produced->format.vendor)
+		ok = ctlv_attestation_evidence_get_results_vendor_format_put(&entry, value);
+	else
+		ok = ctlv_attestation_evidence_get_results_format_put(&entry, value);
+	ok &= ctlv_attestation_evidence_get_results_length_put(&entry, produced->length);
+	ctlv_record_end(&entry);
+	return ok;
+}
+
+int ctlv_attestation_evidence_get_action(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+					 const struct ctlv_cursor *request,
+					 struct ctlv_cursor *reply)
+{
+	struct attestation_evidence_object_request *objects = NULL;
+	struct attestation_evidence_request decoded = {};
+	struct attestation_evidence_result result = {};
+	struct attestation_dev *att = attestation_from_ctlv(ctlv);
+	bool mr_dependent = false;
+	size_t index;
+	int put_ret;
+	int ret;
+
+	ret = attestation_evidence_request_decode(att, ctx, request, &decoded, &objects);
+	if (ret)
+		goto out;
+
+	result.objects = kcalloc(decoded.objects_count, sizeof(*result.objects), GFP_KERNEL);
+	if (!result.objects) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	for (index = 0; index < decoded.objects_count; index++)
+		mr_dependent |= decoded.objects[index].supported_object->binds_mrs;
+	ret = att->provider->evidence_get(att, &decoded, &result);
+	if (ret)
+		goto out;
+
+	for (index = 0; index < decoded.objects_count; index++) {
+		const struct attestation_evidence_object_result *produced = &result.objects[index];
+
+		if (produced->length > decoded.objects[index].supported_object->max_length ||
+		    produced->length > ctlv_blob_len(decoded.objects[index].output)) {
+			ret = -E2BIG;
+			goto out;
+		}
+		if (!attestation_format_in(decoded.objects[index].supported_object,
+					   &produced->format)) {
+			ret = -EPROTO;
+			goto out;
+		}
+	}
+
+	for (index = 0; index < decoded.objects_count; index++) {
+		put_ret = ctlv_blob_put(decoded.objects[index].output);
+		objects[index].output = NULL;
+		if (!ret)
+			ret = put_ret;
+	}
+	if (ret)
+		goto out;
+	if (mr_dependent && !ctlv_attestation_mr_generation_put(reply, att->mr_generation)) {
+		ret = -EMSGSIZE;
+		goto out;
+	}
+	for (index = 0; index < decoded.objects_count; index++)
+		if (!attestation_evidence_result_put(reply, decoded.objects[index].supported_object,
+						     &result.objects[index])) {
+			ret = -EMSGSIZE;
+			goto out;
+		}
+out:
+	kfree(result.objects);
+	attestation_evidence_request_free(objects, decoded.objects_count);
+	return ret;
+}
+
+int ctlv_attestation_mr_get_query(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+				  const struct ctlv_cursor *request, struct ctlv_cursor *reply)
+{
+	struct attestation_dev *att = attestation_from_ctlv(ctlv);
+	struct attestation_mr *mr;
+	struct ctlv_cursor inner;
+	unsigned long id, first = 0, last = UINT_MAX;
+	u64 cursor = ctlv_op_query_cursor(ctx);
+	unsigned int generation = att->mr_generation;
+	bool progress = false;
+	int ret;
+
+	if (cursor && ctlv_op_query_generation(ctx) != generation)
+		return -ESTALE;
+	if (ctlv_attestation_mr_get_id_present(request)) {
+		first = ctlv_attestation_mr_get_id_get(request);
+		mr = attestation_mr_lookup_live(att, first);
+		if (IS_ERR(mr))
+			return PTR_ERR(mr);
+		last = first;
+	} else if (cursor) {
+		first = cursor - 1;
+	}
+
+	xa_for_each_range(&att->mrs, id, mr, first, last) {
+		if (!ctlv_attestation_mr_get_items_start(reply, &inner))
+			break;
+		ret = attestation_mr_props_encode(&inner, mr);
+		if (ret == -EMSGSIZE) {
+			ctlv_record_cancel(&inner);
+			break;
+		}
+		ctlv_record_end(&inner);
+		if (ret)
+			return ret;
+		progress = true;
+	}
+
+	if (!mr)
+		return ctlv_op_query_done(ctx, generation);
+	if (!progress)
+		return -EMSGSIZE;
+	return ctlv_op_query_more(ctx, generation, id + 1);
+}
+
+static int attestation_mr_values_put(struct ctlv_cursor *reply,
+				     const struct attestation_digest_set *values)
+{
+	struct ctlv_cursor inner;
+	size_t index;
+	bool ok;
+
+	for (index = 0; index < values->values_count; index++) {
+		const struct attestation_digest *value = &values->values[index];
+		enum hash_algo algorithm = value->algorithm;
+		size_t length = hash_digest_size[algorithm];
+
+		if (!ctlv_attestation_mr_values_start(reply, &inner))
+			return -EMSGSIZE;
+		ok = ctlv_attestation_mr_value_hash_algo_put(&inner, algorithm);
+		ok &= ctlv_attestation_mr_value_data_put(&inner, value->data, length);
+		ctlv_record_end(&inner);
+		if (!ok)
+			return -EMSGSIZE;
+	}
+	return 0;
+}
+
+int ctlv_attestation_mr_read_query(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+				   const struct ctlv_cursor *request, struct ctlv_cursor *reply)
+{
+	struct attestation_digest_set values = {};
+	struct attestation_dev *att = attestation_from_ctlv(ctlv);
+	struct attestation_mr *mr;
+	u64 id;
+	int ret;
+
+	if (ctlv_op_query_cursor(ctx) &&
+	    ctlv_op_query_generation(ctx) != att->mr_generation)
+		return -ESTALE;
+	id = ctlv_attestation_mr_read_id_get(request);
+	mr = attestation_mr_lookup_live(att, id);
+	if (IS_ERR(mr))
+		return PTR_ERR(mr);
+
+	values.values_count = mr->props->algorithms_count;
+	values.values = kcalloc(values.values_count, sizeof(*values.values), GFP_KERNEL);
+	if (!values.values)
+		return -ENOMEM;
+	for (id = 0; id < values.values_count; id++)
+		values.values[id].algorithm = mr->props->algorithms[id];
+	ret = att->provider->mr_read(att, mr->props->id, &values);
+	if (!ret)
+		ret = attestation_mr_values_validate(mr->props, &values, true);
+	if (!ret && !ctlv_attestation_mr_generation_put(reply, att->mr_generation))
+		ret = -EMSGSIZE;
+	if (!ret)
+		ret = attestation_mr_values_put(reply, &values);
+	kfree(values.values);
+	if (ret)
+		return ret;
+	return ctlv_op_query_done(ctx, att->mr_generation);
+}
+
+static int attestation_mr_extend_decode(const struct ctlv_cursor *request,
+					const struct attestation_mr *mr,
+					struct attestation_digest_set *values)
+{
+	size_t banks = mr->props->algorithms_count, bank;
+	DECLARE_BITMAP(seen, HASH_ALGO__LAST) = {};
+	struct ctlv_cursor item;
+
+	values->values = kcalloc(banks, sizeof(*values->values), GFP_KERNEL);
+	if (!values->values)
+		return -ENOMEM;
+	ctlv_attestation_mr_values_for_each(&item, request)
+	{
+		u64 algorithm = ctlv_attestation_mr_value_hash_algo_get(&item);
+		struct attestation_digest *value;
+		const void *data;
+		u32 length;
+
+		for (bank = 0; bank < banks; bank++)
+			if (mr->props->algorithms[bank] == algorithm)
+				break;
+		data = ctlv_attestation_mr_value_data_get(&item, &length);
+		if (bank == banks || __test_and_set_bit(bank, seen) ||
+		    length != hash_digest_size[algorithm])
+			return -EINVAL;
+		value = &values->values[bank];
+		value->algorithm = algorithm;
+		memcpy(value->data, data, length);
+	}
+	/* Close up the banks left out. */
+	for_each_set_bit(bank, seen, banks)
+		values->values[values->values_count++] = values->values[bank];
+	return 0;
+}
+
+int ctlv_attestation_mr_extend_action(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+				      const struct ctlv_cursor *request, struct ctlv_cursor *reply)
+{
+	struct attestation_digest_set values = {};
+	struct attestation_dev *att = attestation_from_ctlv(ctlv);
+	struct attestation_mr *mr;
+	u64 id;
+	int ret;
+
+	id = ctlv_attestation_mr_extend_id_get(request);
+	mr = attestation_mr_lookup_live(att, id);
+	if (IS_ERR(mr))
+		return PTR_ERR(mr);
+
+	if (!mr->props->extendable)
+		return -EOPNOTSUPP;
+	ret = attestation_mr_extend_decode(request, mr, &values);
+	if (ret)
+		goto out_values;
+	ret = attestation_mr_values_validate(mr->props, &values, false);
+	if (ret)
+		goto out_values;
+	ret = attestation_mr_extend_locked(att, mr, &values);
+	if (!ret && !ctlv_attestation_mr_generation_put(reply, att->mr_generation))
+		ret = -EMSGSIZE;
+out_values:
+	kfree(values.values);
+	return ret;
+}
-- 
2.56.0


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

* [PATCH RFC v2 6/9] Documentation: attestation: Describe how to write a provider
  2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
                   ` (4 preceding siblings ...)
  2026-11-13 16:08 ` [PATCH RFC v2 5/9] attestation: Add CTLV operation handling Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
  2026-11-13 16:08 ` [PATCH RFC v2 7/9] tdx-guest: Expose the native attestation service API Jiri Pirko
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
  To: linux-coco
  Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
	lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
	James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
	alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
	elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
	peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
	nik.borisov, sergii.dmytruk

From: Jiri Pirko <jiri@nvidia.com>

Describe how a provider registers with the attestation core, how other
writers share its MRs and how it is torn down, with the kernel-doc of
the provider API.

Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- split out of the provider and instance core patch
- dropped the journal from the overview
- said how other writers extend the same MRs
- added the kernel-doc to the driver API page
---
 Documentation/driver-api/attestation.rst    | 99 +++++++++++++++++++++
 Documentation/driver-api/index.rst          |  1 +
 Documentation/userspace-api/attestation.rst |  3 +-
 MAINTAINERS                                 |  1 +
 4 files changed, 103 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/driver-api/attestation.rst

diff --git a/Documentation/driver-api/attestation.rst b/Documentation/driver-api/attestation.rst
new file mode 100644
index 000000000000..4137a856aea9
--- /dev/null
+++ b/Documentation/driver-api/attestation.rst
@@ -0,0 +1,99 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+===========
+Attestation
+===========
+
+The attestation subsystem gives each provider device a node of the
+``attestation`` class, ``/dev/attestation/attestationN``, which answers as a
+descendant of the ``ctlv-attestation`` family, one family per provider.  A
+program tells providers apart by the last entry of ``family-get`` on the
+open device, and generic tools use the ``ctlv-attestation`` operations on any
+of them.  The subsystem owns common evidence retrieval and
+measurement-register topology.  Providers retain ownership of native hardware
+access and immutable capability descriptions.
+
+Registering a provider
+======================
+
+A provider embeds ``struct attestation_dev`` first in its own structure and
+allocates both with ``attestation_alloc()``, passing its
+``struct attestation_provider`` and its family descriptor, which inherits
+``ctlv-attestation``:
+
+.. code-block:: c
+
+    struct foo {
+            struct attestation_dev att;
+            struct foo_hw *hw;
+    };
+
+    foo = attestation_alloc(parent, &foo_provider,
+                            &ctlv_attestation_foo_family, struct foo, att);
+    if (IS_ERR(foo))
+            return PTR_ERR(foo);
+    foo->hw = hw;
+    ret = attestation_register(&foo->att);
+    if (ret) {
+            attestation_put(&foo->att);
+            return ret;
+    }
+
+A provider callback gets the ``struct attestation_dev`` and reaches the
+provider's structure with ``container_of()``.  A CTLV callback of the
+provider's own family gets the CTLV instance, which
+``attestation_from_ctlv()`` turns into the device.  No provider callback runs
+before ``attestation_register()``, so the provider finishes its own setup in
+between.  The descriptor, evidence capabilities, fixed MR properties, strings
+and arrays remain immutable until the last ``attestation_put()``.
+
+The subsystem embeds the CTLV instance, and its hooks run every operation,
+those of the device's family included, under one instance mutex.  That
+serializes an operation and its native callback.
+
+Every MR can be read, so a provider with MRs implements ``mr_read``.
+Fixed MRs are listed in the provider descriptor.  Their property storage
+remains valid until the last ``attestation_put()``.
+
+The evidence capabilities list the objects ``evidence-get`` produces, each
+object's ID being its index.  An object gives its class and the formats it
+may have, ``ctlv-attestation``'s ``evidence-format`` or its family's
+``evidence-vendor-format``, and a signed object its challenge lengths.
+``attestation_alloc()`` refuses a format the family does not give.
+
+``evidence_get`` writes each object with ``attestation_evidence_write()``,
+which fails the operation with ``-EMSGSIZE`` where the caller's blob is too
+short, and tells the caller how long it has to be.  It fills in each object's
+result: the format written and its length.  A format the object does not list
+fails the call with ``-EPROTO``.
+
+Other writers
+=============
+
+This interface is not the only way to extend a measurement register, and it
+does not arbitrate between the ways there are.  Every program holding a
+read-write descriptor of the device can extend a register through
+``mr-extend``, and a register with a native interface of its own can be
+extended through that too, as a TPM's PCRs are by programs using
+``/dev/tpm0`` or ``/dev/tpmrm0`` and by IMA.  Nothing reserves a register for
+one writer.  A register two writers extend holds the extensions of both, in
+the order they arrived, and ``mr-read`` and the evidence report it as it is.
+A program that replays a register from its own records has to be its only
+writer, by agreement with whatever else runs on the system.
+
+Teardown
+========
+
+``attestation_unregister()`` first removes the node, so nothing opens the
+device again.  It then withdraws the device from the kernel registry, refusing
+the operations of files still open.  Only then does it unpublish the CTLV
+instance, which drains the operations in flight.  No provider callback runs
+once it returns, so the provider may release its native state before
+``attestation_put()``.  Open files keep the structure until they close.
+
+Kernel API
+==========
+
+.. kernel-doc:: include/linux/attestation.h
+.. kernel-doc:: drivers/attestation/core/core.c
+   :export:
diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst
index 0c5b9d2165d8..625355c504ef 100644
--- a/Documentation/driver-api/index.rst
+++ b/Documentation/driver-api/index.rst
@@ -38,6 +38,7 @@ of interest to most developers working on device drivers.
    early-userspace/index
    connector
    ctlv
+   attestation
    device-io
    devfreq
    dma-buf
diff --git a/Documentation/userspace-api/attestation.rst b/Documentation/userspace-api/attestation.rst
index 3c610acc3382..16646d8b60d7 100644
--- a/Documentation/userspace-api/attestation.rst
+++ b/Documentation/userspace-api/attestation.rst
@@ -16,7 +16,8 @@ The node speaks CTLV, described in :doc:`ctlv`, as a descendant of the
 belongs to ``ctlv-attestation``, so a tool written against it works with any
 provider. The specification is
 ``Documentation/ctlv/specs/ctlv-attestation.yaml`` and the constants are in
-``include/uapi/ctlv/ctlv_attestation_gen.h``.
+``include/uapi/ctlv/ctlv_attestation_gen.h``. Writing a provider is
+:doc:`/driver-api/attestation`.
 
 ======================  ======  ==========  ================================
 Operation               Type    Access      Purpose
diff --git a/MAINTAINERS b/MAINTAINERS
index e57fc8bb9c1d..172b20d5fdd1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4439,6 +4439,7 @@ L:	linux-security-module@vger.kernel.org
 S:	Supported
 F:	Documentation/ctlv/abi/ctlv-attestation*
 F:	Documentation/ctlv/specs/ctlv-attestation*
+F:	Documentation/driver-api/attestation.rst
 F:	Documentation/userspace-api/attestation*.rst
 F:	drivers/attestation/
 F:	include/linux/attestation*
-- 
2.56.0


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

* [PATCH RFC v2 7/9] tdx-guest: Expose the native attestation service API
  2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
                   ` (5 preceding siblings ...)
  2026-11-13 16:08 ` [PATCH RFC v2 6/9] Documentation: attestation: Describe how to write a provider Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
  2026-11-13 16:08 ` [PATCH RFC v2 8/9] attestation: Add the TDX family specification and generated files Jiri Pirko
  2026-11-13 16:08 ` [PATCH RFC v2 9/9] attestation: Add the TDX family implementation Jiri Pirko
  8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
  To: linux-coco
  Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
	lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
	James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
	alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
	elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
	peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
	nik.borisov, sergii.dmytruk

From: Jiri Pirko <jiri@nvidia.com>

Factor the Quote and measurement-register operations of the misc, TSM
report and TSM MR interfaces into helpers declared in a driver-internal
header, for an attestation provider built into the driver to share.

Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- kept the helpers inside tdx-guest, in place of a client API and its
  header
- dropped the MAINTAINERS lines, which N: tdx already covers
- sized the TSM report outblob to its Quote
- dropped an unreachable return in tdx_do_report()
- dropped the stale kernel client section from tdx-guest.rst
- numbered every enum member
---
 drivers/virt/coco/tdx-guest/internal.h  |  30 ++++++
 drivers/virt/coco/tdx-guest/tdx-guest.c | 130 +++++++++++++++++++-----
 2 files changed, 132 insertions(+), 28 deletions(-)
 create mode 100644 drivers/virt/coco/tdx-guest/internal.h

diff --git a/drivers/virt/coco/tdx-guest/internal.h b/drivers/virt/coco/tdx-guest/internal.h
new file mode 100644
index 000000000000..d57a8c385aff
--- /dev/null
+++ b/drivers/virt/coco/tdx-guest/internal.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _TDX_GUEST_INTERNAL_H
+#define _TDX_GUEST_INTERNAL_H
+
+#include <crypto/sha2.h>
+#include <linux/types.h>
+
+#define TDX_GUEST_QUOTE_MAX_LEN ((128 * 1024) - 24)
+
+struct tdx_guest_info {
+	u8 mrconfigid[SHA384_DIGEST_SIZE];
+	u8 mrowner[SHA384_DIGEST_SIZE];
+	u8 mrownerconfig[SHA384_DIGEST_SIZE];
+};
+
+enum tdx_guest_mr {
+	TDX_GUEST_MR_MRTD = 0,
+	TDX_GUEST_MR_RTMR0 = 1,
+	TDX_GUEST_MR_RTMR1 = 2,
+	TDX_GUEST_MR_RTMR2 = 3,
+	TDX_GUEST_MR_RTMR3 = 4,
+};
+
+/* @reportdata is TDX_REPORTDATA_LEN bytes, and @out holds TDX_GUEST_QUOTE_MAX_LEN. */
+int tdx_guest_quote_get(const u8 *reportdata, void *out, size_t *out_length);
+int tdx_guest_mr_read(enum tdx_guest_mr mr, u8 value[SHA384_DIGEST_SIZE]);
+int tdx_guest_rtmr_extend(unsigned int rtmr,
+			  const u8 value[SHA384_DIGEST_SIZE]);
+
+#endif /* _TDX_GUEST_INTERNAL_H */
diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.c b/drivers/virt/coco/tdx-guest/tdx-guest.c
index 11d741da3b35..c49454ec0b18 100644
--- a/drivers/virt/coco/tdx-guest/tdx-guest.c
+++ b/drivers/virt/coco/tdx-guest/tdx-guest.c
@@ -25,8 +25,11 @@
 #include <asm/cpu_device_id.h>
 #include <asm/tdx.h>
 
+#include "internal.h"
+
 /* TDREPORT buffer */
 static u8 *tdx_report_buf;
+static struct tdx_guest_info tdx_guest_info;
 
 /* Lock to serialize TDG.MR.REPORT and TDG.MR.RTMR.EXTEND TDCALLs */
 static DEFINE_MUTEX(mr_lock);
@@ -49,27 +52,32 @@ enum {
 	TDREPORT_servtd_hash = TDREPORT_rtmr3 + SHA384_DIGEST_SIZE,
 };
 
-static int tdx_do_report(sockptr_t data, sockptr_t tdreport)
+static int __tdx_do_report(sockptr_t data, sockptr_t tdreport)
 {
-	scoped_cond_guard(mutex_intr, return -EINTR, &mr_lock) {
-		u8 *reportdata = tdx_report_buf + TDREPORT_reportdata;
-		int ret;
+	u8 *reportdata = tdx_report_buf + TDREPORT_reportdata;
+	int ret;
 
-		if (!sockptr_is_null(data) &&
-		    copy_from_sockptr(reportdata, data, TDX_REPORTDATA_LEN))
-			return -EFAULT;
+	lockdep_assert_held(&mr_lock);
+	if (!sockptr_is_null(data) &&
+	    copy_from_sockptr(reportdata, data, TDX_REPORTDATA_LEN))
+		return -EFAULT;
 
-		ret = tdx_mcall_get_report0(reportdata, tdx_report_buf);
-		if (WARN_ONCE(ret, "tdx_mcall_get_report0() failed: %d", ret))
-			return ret;
+	ret = tdx_mcall_get_report0(reportdata, tdx_report_buf);
+	if (WARN_ONCE(ret, "tdx_mcall_get_report0() failed: %d", ret))
+		return ret;
 
-		if (!sockptr_is_null(tdreport) &&
-		    copy_to_sockptr(tdreport, tdx_report_buf, TDX_REPORT_LEN))
-			return -EFAULT;
-	}
+	if (!sockptr_is_null(tdreport) &&
+	    copy_to_sockptr(tdreport, tdx_report_buf, TDX_REPORT_LEN))
+		return -EFAULT;
 	return 0;
 }
 
+static int tdx_do_report(sockptr_t data, sockptr_t tdreport)
+{
+	scoped_cond_guard(mutex_intr, return -EINTR, &mr_lock)
+		return __tdx_do_report(data, tdreport);
+}
+
 static int tdx_do_extend(u8 mr_ind, const u8 *data)
 {
 	scoped_cond_guard(mutex_intr, return -EINTR, &mr_lock) {
@@ -90,6 +98,34 @@ static int tdx_do_extend(u8 mr_ind, const u8 *data)
 	return 0;
 }
 
+int tdx_guest_mr_read(enum tdx_guest_mr mr, u8 value[SHA384_DIGEST_SIZE])
+{
+	static const size_t offsets[] = {
+		[TDX_GUEST_MR_MRTD] = TDREPORT_mrtd,
+		[TDX_GUEST_MR_RTMR0] = TDREPORT_rtmr0,
+		[TDX_GUEST_MR_RTMR1] = TDREPORT_rtmr1,
+		[TDX_GUEST_MR_RTMR2] = TDREPORT_rtmr2,
+		[TDX_GUEST_MR_RTMR3] = TDREPORT_rtmr3,
+	};
+	int ret;
+
+	scoped_cond_guard(mutex_intr, return -EINTR, &mr_lock) {
+		ret = __tdx_do_report(KERNEL_SOCKPTR(NULL),
+				      KERNEL_SOCKPTR(NULL));
+		if (ret)
+			return ret;
+		memcpy(value, tdx_report_buf + offsets[mr],
+		       SHA384_DIGEST_SIZE);
+	}
+	return 0;
+}
+
+int tdx_guest_rtmr_extend(unsigned int rtmr,
+			  const u8 value[SHA384_DIGEST_SIZE])
+{
+	return tdx_do_extend(rtmr, value);
+}
+
 #define TDX_MR_(r) .mr_value = (void *)TDREPORT_##r, TSM_MR_(r, SHA384)
 static struct tsm_measurement_register tdx_mrs[] = {
 	{ TDX_MR_(rtmr0) | TSM_MR_F_RTMR },
@@ -135,6 +171,12 @@ static const struct attribute_group *tdx_mr_init(void)
 	rc = tdx_mr_refresh(&tdx_measurements);
 	if (rc)
 		return ERR_PTR(rc);
+	memcpy(tdx_guest_info.mrconfigid,
+	       buf + TDREPORT_mrconfigid, SHA384_DIGEST_SIZE);
+	memcpy(tdx_guest_info.mrowner,
+	       buf + TDREPORT_mrowner, SHA384_DIGEST_SIZE);
+	memcpy(tdx_guest_info.mrownerconfig,
+	       buf + TDREPORT_mrownerconfig, SHA384_DIGEST_SIZE);
 
 	/*
 	 * @mr_value was initialized with the offset only, while the base
@@ -189,6 +231,9 @@ struct tdx_quote_buf {
 	u8 data[];
 };
 
+static_assert(TDX_GUEST_QUOTE_MAX_LEN ==
+	      TDX_DEFAULT_QUOTE_SIZE - sizeof(struct tdx_quote_buf));
+
 static struct tdx_quote_buf *quote_buf;
 
 /* Lock to streamline quote requests */
@@ -285,15 +330,18 @@ static int wait_for_quote_completion(struct tdx_quote_buf *buf, u32 timeout)
 	return (i == timeout) ? -ETIMEDOUT : 0;
 }
 
-static int tdx_report_new_locked(struct tsm_report *report)
+/* Leaves the Quote in quote_buf; copy it out before dropping quote_lock. */
+static int __tdx_guest_quote_get(const u8 *reportdata, size_t length,
+				 size_t *out_length)
 {
-	u8 *buf;
-	struct tsm_report_desc *desc = &report->desc;
 	size_t quote_buf_size = get_quote_buf_size();
 	u32 out_len;
 	int ret;
 	u64 err;
 
+	lockdep_assert_held(&quote_lock);
+	if (!reportdata || length != TDX_REPORTDATA_LEN)
+		return -EINVAL;
 	/*
 	 * If the previous request is timedout or interrupted, and the
 	 * Quote buf status is still in GET_QUOTE_IN_FLIGHT (owned by
@@ -302,16 +350,13 @@ static int tdx_report_new_locked(struct tsm_report *report)
 	if (quote_buf->status == GET_QUOTE_IN_FLIGHT)
 		return -EBUSY;
 
-	if (desc->inblob_len != TDX_REPORTDATA_LEN)
-		return -EINVAL;
-
 	memset(quote_buf, 0, quote_buf_size);
 
 	/* Update Quote buffer header */
 	quote_buf->version = GET_QUOTE_CMD_VER;
 	quote_buf->in_len = TDX_REPORT_LEN;
 
-	ret = tdx_do_report(KERNEL_SOCKPTR(desc->inblob),
+	ret = tdx_do_report(KERNEL_SOCKPTR((void *)reportdata),
 			    KERNEL_SOCKPTR(quote_buf->data));
 	if (ret)
 		return ret;
@@ -337,13 +382,7 @@ static int tdx_report_new_locked(struct tsm_report *report)
 
 	if (struct_size(quote_buf, data, out_len) > quote_buf_size)
 		return -EFBIG;
-
-	buf = kvmemdup(quote_buf->data, out_len, GFP_KERNEL);
-	if (!buf)
-		return -ENOMEM;
-
-	report->outblob = buf;
-	report->outblob_len = out_len;
+	*out_length = out_len;
 
 	/*
 	 * TODO: parse the PEM-formatted cert chain out of the quote buffer when
@@ -353,6 +392,41 @@ static int tdx_report_new_locked(struct tsm_report *report)
 	return ret;
 }
 
+int tdx_guest_quote_get(const u8 *reportdata, void *out, size_t *out_length)
+{
+	int ret;
+
+	scoped_cond_guard(mutex_intr, return -EINTR, &quote_lock) {
+		ret = __tdx_guest_quote_get(reportdata, TDX_REPORTDATA_LEN,
+					    out_length);
+		if (ret)
+			return ret;
+		/* The TDX module may allow a Quote longer than @out. */
+		if (*out_length > TDX_GUEST_QUOTE_MAX_LEN)
+			return -EFBIG;
+		memcpy(out, quote_buf->data, *out_length);
+	}
+	return 0;
+}
+
+static int tdx_report_new_locked(struct tsm_report *report)
+{
+	struct tsm_report_desc *desc = &report->desc;
+	size_t length;
+	void *buf;
+	int ret;
+
+	ret = __tdx_guest_quote_get(desc->inblob, desc->inblob_len, &length);
+	if (ret)
+		return ret;
+	buf = kvmemdup(quote_buf->data, length, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+	report->outblob = buf;
+	report->outblob_len = length;
+	return 0;
+}
+
 static int tdx_report_new(struct tsm_report *report, void *unused)
 {
 	scoped_cond_guard(mutex_intr, return -EINTR, &quote_lock)
-- 
2.56.0


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

* [PATCH RFC v2 8/9] attestation: Add the TDX family specification and generated files
  2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
                   ` (6 preceding siblings ...)
  2026-11-13 16:08 ` [PATCH RFC v2 7/9] tdx-guest: Expose the native attestation service API Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
  2026-11-13 16:08 ` [PATCH RFC v2 9/9] attestation: Add the TDX family implementation Jiri Pirko
  8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
  To: linux-coco
  Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
	lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
	James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
	alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
	elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
	peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
	nik.borisov, sergii.dmytruk

From: Jiri Pirko <jiri@nvidia.com>

Define the TDX attestation family and reserve family ID 2. Add its ABI
snapshot, registry entry and generated kernel and UAPI files.

The ABI is intentionally specification-first. The generated descriptor
is linked with the provider implementation in the following patch.

Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- generated the kernel files into drivers/virt/coco/tdx-guest/
- dropped the family's priv lifecycle hook
- generated an extension fill in place of a stub fill
- wrote the spec as op trees and records
- numbered every enum member
- moved the generated UAPI headers to include/uapi/ctlv/
- documented every item of the spec
- put each field's doc above its generated helpers
- gave the legacy and DICE Quote formats as evidence-vendor-format
  values
- said message instead of wire
---
 .../ctlv/abi/ctlv-attestation-tdx.yaml        | 21 ++++++
 Documentation/ctlv/abi/families.registry.yaml |  1 +
 Documentation/ctlv/families.yaml              |  1 +
 Documentation/ctlv/generated-files.yaml       |  7 ++
 .../ctlv/specs/ctlv-attestation-tdx.yaml      | 25 +++++++
 MAINTAINERS                                   |  1 +
 .../coco/tdx-guest/ctlv_attestation_tdx_gen.c | 65 +++++++++++++++++++
 .../coco/tdx-guest/ctlv_attestation_tdx_gen.h | 44 +++++++++++++
 include/uapi/ctlv/ctlv_attestation_tdx_gen.h  | 42 ++++++++++++
 include/uapi/ctlv/ctlv_families_gen.h         |  1 +
 10 files changed, 208 insertions(+)
 create mode 100644 Documentation/ctlv/abi/ctlv-attestation-tdx.yaml
 create mode 100644 Documentation/ctlv/specs/ctlv-attestation-tdx.yaml
 create mode 100644 drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.c
 create mode 100644 drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.h
 create mode 100644 include/uapi/ctlv/ctlv_attestation_tdx_gen.h

diff --git a/Documentation/ctlv/abi/ctlv-attestation-tdx.yaml b/Documentation/ctlv/abi/ctlv-attestation-tdx.yaml
new file mode 100644
index 000000000000..f8dc3c085c71
--- /dev/null
+++ b/Documentation/ctlv/abi/ctlv-attestation-tdx.yaml
@@ -0,0 +1,21 @@
+# 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-tdx family, from
+# Documentation/ctlv/specs/ctlv-attestation-tdx.yaml, written by
+# "make -C tools/ctlv regen".
+#
+# Updating the file is how a UAPI change gets reviewed; an update that
+# removes or renumbers anything, narrows a bound, or makes a member required
+# is an ABI break, whatever the file says afterwards.
+family: {name: ctlv-attestation-tdx, id: 2, inherits: ctlv-attestation}
+enums:
+  evidence-vendor-format: {quote: 1, quote-dice: 2}
+records:
+  device-info:
+    id: 0x00010000
+    fields:
+      mrconfigid: {number: 1, type: binary, length: 48}
+      mrowner: {number: 2, type: binary, length: 48}
+      mrownerconfig: {number: 3, type: binary, length: 48}
+fills: {device-info: device-info}
diff --git a/Documentation/ctlv/abi/families.registry.yaml b/Documentation/ctlv/abi/families.registry.yaml
index 7a5c6af29565..0f55cc4f2e35 100644
--- a/Documentation/ctlv/abi/families.registry.yaml
+++ b/Documentation/ctlv/abi/families.registry.yaml
@@ -6,3 +6,4 @@
 families:
   - ctlv
   - ctlv-attestation
+  - ctlv-attestation-tdx
diff --git a/Documentation/ctlv/families.yaml b/Documentation/ctlv/families.yaml
index dbb0243b873d..27bafae3ca66 100644
--- a/Documentation/ctlv/families.yaml
+++ b/Documentation/ctlv/families.yaml
@@ -16,3 +16,4 @@
 families:
   - ctlv
   - ctlv-attestation
+  - ctlv-attestation-tdx
diff --git a/Documentation/ctlv/generated-files.yaml b/Documentation/ctlv/generated-files.yaml
index 00a56634805d..e78997f13334 100644
--- a/Documentation/ctlv/generated-files.yaml
+++ b/Documentation/ctlv/generated-files.yaml
@@ -23,3 +23,10 @@ families:
       uapi-header: include/uapi/ctlv/ctlv_attestation_gen.h
       kernel-header: drivers/attestation/generated/ctlv_attestation_gen.h
       kernel-source: drivers/attestation/generated/ctlv_attestation_gen.c
+
+  - name: ctlv-attestation-tdx
+    source: Documentation/ctlv/specs/ctlv-attestation-tdx.yaml
+    outputs:
+      uapi-header: include/uapi/ctlv/ctlv_attestation_tdx_gen.h
+      kernel-header: drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.h
+      kernel-source: drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.c
diff --git a/Documentation/ctlv/specs/ctlv-attestation-tdx.yaml b/Documentation/ctlv/specs/ctlv-attestation-tdx.yaml
new file mode 100644
index 000000000000..0093015b4991
--- /dev/null
+++ b/Documentation/ctlv/specs/ctlv-attestation-tdx.yaml
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+%YAML 1.2
+---
+# The attestation device of an Intel TDX guest: Quotes of its TDREPORT, the
+# CCEL event log, MRTD and RTMR0 to RTMR3.
+family: ctlv-attestation-tdx
+inherits: ctlv-attestation
+
+enums:
+  # Fills ctlv-attestation's evidence-vendor-format.
+  evidence-vendor-format:
+    # An Intel TDX DCAP Quote, from the host's quoting enclave.
+    - quote
+    # A DICE-based TDX Quote, an EAT in a CBOR Web Token.
+    - quote-dice
+
+records:
+  # Fills ctlv-attestation's device-info.
+  device-info:
+    # MRCONFIGID of the TD, the ID of its non-owner configuration.
+    mrconfigid: binary(48)
+    # MROWNER of the TD, the ID of its owner.
+    mrowner: binary(48)
+    # MROWNERCONFIG of the TD, the ID of its owner's configuration.
+    mrownerconfig: binary(48)
diff --git a/MAINTAINERS b/MAINTAINERS
index 172b20d5fdd1..0734efa1a78f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4442,6 +4442,7 @@ F:	Documentation/ctlv/specs/ctlv-attestation*
 F:	Documentation/driver-api/attestation.rst
 F:	Documentation/userspace-api/attestation*.rst
 F:	drivers/attestation/
+F:	drivers/virt/coco/tdx-guest/*attestation*
 F:	include/linux/attestation*
 F:	include/uapi/ctlv/ctlv_attestation*
 
diff --git a/drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.c b/drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.c
new file mode 100644
index 000000000000..8795febbdeeb
--- /dev/null
+++ b/drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/*
+ * Generated from Documentation/ctlv/specs/ctlv-attestation-tdx.yaml by
+ * tools/ctlv/ctlv-regen.sh. Do not edit.
+ */
+/* 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 <uapi/ctlv/ctlv_gen.h>
+#include <uapi/ctlv/ctlv_attestation_tdx_gen.h>
+
+#include "ctlv_attestation_tdx_gen.h"
+
+static const struct ctlv_node_schema device_info_fill_node_schemas[] = {
+	CTLV_NODE_SCHEMA_ROOT(1, 3, 3),
+	CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MRCONFIGID, 1, 48, 48),
+	CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MROWNER, 1, 48, 48),
+	CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MROWNERCONFIG, 1, 48, 48),
+};
+
+/* The extension points this family fills. */
+static const struct ctlv_ext_fill_schema ctlv_attestation_tdx_ext_fill_schemas[] = {
+	CTLV_EXT_FILL_SCHEMA(CTLV_FAMILY_ATTESTATION, CTLV_ATTESTATION_EXT_DEVICE_INFO,
+			     device_info),
+};
+
+/* The enum extension points this family fills. */
+static const struct ctlv_enum_fill_schema ctlv_attestation_tdx_enum_fill_schemas[] = {
+	CTLV_ENUM_FILL_SCHEMA(CTLV_FAMILY_ATTESTATION,
+			      CTLV_ATTESTATION_ENUM_EXT_EVIDENCE_VENDOR_FORMAT, 2),
+};
+
+/* The family ID is its position in the central registry. */
+const struct ctlv_family_schema ctlv_attestation_tdx_schema = {
+	.name = "ctlv-attestation-tdx",
+	.family_id = 2,
+	.ext_fill_schemas = ctlv_attestation_tdx_ext_fill_schemas,
+	.ext_fill_schemas_count = ARRAY_SIZE(ctlv_attestation_tdx_ext_fill_schemas),
+	.enum_fill_schemas = ctlv_attestation_tdx_enum_fill_schemas,
+	.enum_fill_schemas_count = ARRAY_SIZE(ctlv_attestation_tdx_enum_fill_schemas),
+};
+
+/* Maximum uevent cost for this family chain. */
+static_assert(150 <= CTLV_UEVENT_MAX_LEN);
+static_assert(5 <= CTLV_UEVENT_MAX_VARS);
+
+/* Extension-point callbacks. */
+static const struct ctlv_ext_ops ctlv_attestation_tdx_ext_ops[] = {
+	CTLV_EXT_OPS(CTLV_FAMILY_ATTESTATION, CTLV_ATTESTATION_EXT_DEVICE_INFO,
+		     ctlv_attestation_tdx_device_info),
+};
+
+/* Family descriptor used by ctlv_init(). */
+const struct ctlv_family ctlv_attestation_tdx_family = {
+	.schema = &ctlv_attestation_tdx_schema,
+	.parent = &ctlv_attestation_family,
+	.ext_ops = ctlv_attestation_tdx_ext_ops,
+	.ext_ops_count = ARRAY_SIZE(ctlv_attestation_tdx_ext_ops),
+};
+EXPORT_SYMBOL_NS_GPL(ctlv_attestation_tdx_family, "CTLV");
diff --git a/drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.h b/drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.h
new file mode 100644
index 000000000000..99c3cbc41a39
--- /dev/null
+++ b/drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * Generated from Documentation/ctlv/specs/ctlv-attestation-tdx.yaml by
+ * tools/ctlv/ctlv-regen.sh. Do not edit.
+ */
+/* CTLV-GEN kernel header */
+
+/* Kernel metadata and family API of the ctlv-attestation-tdx CTLV family. */
+
+#ifndef _LINUX_CTLV_ATTESTATION_TDX_GEN_H
+#define _LINUX_CTLV_ATTESTATION_TDX_GEN_H
+
+#include <linux/ctlv_family.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+
+#include <uapi/ctlv/ctlv_attestation_tdx_gen.h>
+
+#include "ctlv_attestation_gen.h"
+
+extern const struct ctlv_family_schema ctlv_attestation_tdx_schema;
+
+/* device-info of ctlv-attestation: fill callback. */
+int ctlv_attestation_tdx_device_info_fill(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+					  const struct ctlv_cursor *request,
+					  struct ctlv_cursor *record);
+
+extern const struct ctlv_family ctlv_attestation_tdx_family;
+
+#ifndef _CTLV_GENERATED_METADATA_ONLY
+
+/* Writing a reply. An append is atomic; a record ends with ctlv_record_end(). */
+/* MRCONFIGID of the TD, the ID of its non-owner configuration. */
+CTLV_PUT_BINARY(ctlv_attestation_tdx_device_info_mrconfigid,
+		CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MRCONFIGID, CTLV_SCHEMA_BINARY)
+/* MROWNER of the TD, the ID of its owner. */
+CTLV_PUT_BINARY(ctlv_attestation_tdx_device_info_mrowner,
+		CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MROWNER, CTLV_SCHEMA_BINARY)
+/* MROWNERCONFIG of the TD, the ID of its owner's configuration. */
+CTLV_PUT_BINARY(ctlv_attestation_tdx_device_info_mrownerconfig,
+		CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MROWNERCONFIG, CTLV_SCHEMA_BINARY)
+#endif /* _CTLV_GENERATED_METADATA_ONLY */
+
+#endif /* _LINUX_CTLV_ATTESTATION_TDX_GEN_H */
diff --git a/include/uapi/ctlv/ctlv_attestation_tdx_gen.h b/include/uapi/ctlv/ctlv_attestation_tdx_gen.h
new file mode 100644
index 000000000000..77339ca50ca5
--- /dev/null
+++ b/include/uapi/ctlv/ctlv_attestation_tdx_gen.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * Generated from Documentation/ctlv/specs/ctlv-attestation-tdx.yaml by
+ * tools/ctlv/ctlv-regen.sh. Do not edit.
+ */
+/* CTLV-GEN uapi header */
+
+/*
+ * Constants of the ctlv-attestation-tdx CTLV family. Types and op IDs are complete; the message
+ * format carrying them is in <ctlv/ctlv.h>.
+ *
+ * The inherited ctlv-attestation header is included here, so one include covers the whole effective
+ * schema.
+ */
+
+#ifndef _UAPI_CTLV_CTLV_ATTESTATION_TDX_GEN_H
+#define _UAPI_CTLV_CTLV_ATTESTATION_TDX_GEN_H
+
+#include <ctlv/ctlv.h>
+#include <ctlv/ctlv_attestation_gen.h>
+#include <ctlv/ctlv_families_gen.h>
+
+/* enum evidence-vendor-format, an extension point of ctlv-attestation */
+enum _ctlv_attestation_tdx_evidence_vendor_format {
+	/* An Intel TDX DCAP Quote, from the host's quoting enclave. */
+	CTLV_ATTESTATION_TDX_EVIDENCE_VENDOR_FORMAT_QUOTE = 1,
+	/* A DICE-based TDX Quote, an EAT in a CBOR Web Token. */
+	CTLV_ATTESTATION_TDX_EVIDENCE_VENDOR_FORMAT_QUOTE_DICE = 2,
+};
+
+/* device-info */
+/* MRCONFIGID of the TD, the ID of its non-owner configuration. */
+#define CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MRCONFIGID \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION_TDX, 0x00010000, 1)
+/* MROWNER of the TD, the ID of its owner. */
+#define CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MROWNER \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION_TDX, 0x00010000, 2)
+/* MROWNERCONFIG of the TD, the ID of its owner's configuration. */
+#define CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MROWNERCONFIG \
+	CTLV_TYPE(CTLV_FAMILY_ATTESTATION_TDX, 0x00010000, 3)
+
+#endif /* _UAPI_CTLV_CTLV_ATTESTATION_TDX_GEN_H */
diff --git a/include/uapi/ctlv/ctlv_families_gen.h b/include/uapi/ctlv/ctlv_families_gen.h
index 83f6a257ddf2..849671f8681d 100644
--- a/include/uapi/ctlv/ctlv_families_gen.h
+++ b/include/uapi/ctlv/ctlv_families_gen.h
@@ -13,6 +13,7 @@
 enum _ctlv_family {
 	CTLV_FAMILY_ROOT = 0,
 	CTLV_FAMILY_ATTESTATION = 1,
+	CTLV_FAMILY_ATTESTATION_TDX = 2,
 };
 
 #endif /* _UAPI_CTLV_CTLV_FAMILIES_GEN_H */
-- 
2.56.0


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

* [PATCH RFC v2 9/9] attestation: Add the TDX family implementation
  2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
                   ` (7 preceding siblings ...)
  2026-11-13 16:08 ` [PATCH RFC v2 8/9] attestation: Add the TDX family specification and generated files Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
  8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
  To: linux-coco
  Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
	lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
	James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
	alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
	elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
	peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
	nik.borisov, sergii.dmytruk

From: Jiri Pirko <jiri@nvidia.com>

Build the TDX attestation provider into tdx-guest when attestation is
enabled. Expose the Quote, MRTD and RTMR operations through the same
helpers as the native interfaces, which stay as they are, and label
each Quote as a legacy or a DICE one by its first bytes [1]. Where
ACPI describes a CCEL table, also return the firmware's event log as
evidence, for a verifier replaying the RTMRs.

[1] https://lore.kernel.org/all/avNfZ0lLl-OrkCHs@intel.com/

Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- built the provider into tdx-guest in place of the attestation-tdx
  module
- embedded struct attestation_dev in the provider's state
- accounted the evidence buffer to its caller's memcg
- dropped the MR-journal boundary claim from the TDX documentation
- returned the CCEL event log as an event-log evidence object
- dropped the total output length
- documented the provider as part of tdx-guest
- reported the 64-byte challenge length as a list entry
- used the helpers generated for the rewritten spec
- wrote the evidence objects with attestation_evidence_write()
- dropped the RTMR read caps, as every MR can be read
- said record instead of nest, the root being one
- took the UAPI headers from include/uapi/ctlv/
- dropped the TDREPORT evidence object, which /dev/tdx_guest keeps,
  and labelled each Quote as a legacy or a DICE one
- described the provider for userspace in a document of its own
---
 .../userspace-api/attestation-tdx.rst         |  59 ++++
 Documentation/userspace-api/attestation.rst   |  10 +
 Documentation/virt/coco/tdx-guest.rst         |   8 +
 drivers/attestation/Kconfig                   |   4 +
 drivers/virt/coco/tdx-guest/Kconfig           |   4 +-
 drivers/virt/coco/tdx-guest/Makefile          |   6 +
 drivers/virt/coco/tdx-guest/attestation.c     | 313 ++++++++++++++++++
 drivers/virt/coco/tdx-guest/internal.h        |  18 +
 .../coco/tdx-guest/{tdx-guest.c => main.c}    |   7 +
 9 files changed, 428 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/userspace-api/attestation-tdx.rst
 create mode 100644 drivers/virt/coco/tdx-guest/attestation.c
 rename drivers/virt/coco/tdx-guest/{tdx-guest.c => main.c} (98%)

diff --git a/Documentation/userspace-api/attestation-tdx.rst b/Documentation/userspace-api/attestation-tdx.rst
new file mode 100644
index 000000000000..ca0ed209143b
--- /dev/null
+++ b/Documentation/userspace-api/attestation-tdx.rst
@@ -0,0 +1,59 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===============
+TDX attestation
+===============
+
+With ``CONFIG_ATTESTATION``, the TDX guest driver registers an attestation
+device of the ``ctlv-attestation-tdx`` family, whose specification is
+``Documentation/ctlv/specs/ctlv-attestation-tdx.yaml``. Its ``device-info``,
+the ``info`` of ``device-get``, carries ``mrconfigid``, ``mrowner`` and
+``mrownerconfig``, 48 bytes each.
+
+Measurement registers
+=====================
+
+==  =====  ==============  =======  ======
+ID  Name   Register index  Bank     Extend
+==  =====  ==============  =======  ======
+0   mrtd   none            SHA-384  no
+1   rtmr0  0               SHA-384  yes
+2   rtmr1  1               SHA-384  yes
+3   rtmr2  2               SHA-384  yes
+4   rtmr3  3               SHA-384  yes
+==  =====  ==============  =======  ======
+
+Each ``mr-read`` takes a TDREPORT.
+
+The TSM-MR files,
+``/sys/devices/virtual/misc/tdx_guest/measurements/rtmr[0123]:sha384``, extend
+the same RTMRs as ``mr-extend``. Firmware and the boot loader usually extend
+RTMR0 to RTMR2 before the kernel starts, which leaves RTMR3 to the running
+system.
+
+Evidence
+========
+
+==  ========  =========  =================  =========  ========
+ID  Object    Class      Formats            Challenge  Binds
+==  ========  =========  =================  =========  ========
+0   Quote     signed     quote, quote-dice  64 bytes   every MR
+1   CCEL log  event-log  ccel               none       no MR
+==  ========  =========  =================  =========  ========
+
+The challenge goes into the REPORTDATA the Quote carries. The host's quoting
+service returns a DCAP Quote or a DICE-based one, and the device names it
+``quote`` by its header's version and TEE type, or ``quote-dice`` by the CBOR
+magic it starts with. Both are ``ctlv-attestation-tdx``'s vendor formats. A
+Quote of neither fails ``evidence-get`` with ``EPROTO``.
+
+The log is there where the firmware describes its CC event log with an ACPI
+CCEL table, and is the whole log area. It does not change after boot, and one
+call returns it with the Quote.
+
+The TDREPORT is MAC'd for the platform, not signed, so it is no object;
+``TDX_CMD_GET_REPORT0`` of ``/dev/tdx_guest`` returns it.
+
+A guest has one TDX device, which a udev rule can give a stable name::
+
+    ENV{CTLV_FAMILY}=="ctlv-attestation-tdx", SYMLINK+="attestation/tdx"
diff --git a/Documentation/userspace-api/attestation.rst b/Documentation/userspace-api/attestation.rst
index 16646d8b60d7..32ecaf5cba35 100644
--- a/Documentation/userspace-api/attestation.rst
+++ b/Documentation/userspace-api/attestation.rst
@@ -282,3 +282,13 @@ The reply carries ``mr-generation``, the MR generation after the extension.
     have or repeats one, or a digest has the wrong length.
 
 Access is read-write.
+
+Providers
+=========
+
+What each provider's family adds, and what its device produces:
+
+.. toctree::
+   :maxdepth: 1
+
+   attestation-tdx
diff --git a/Documentation/virt/coco/tdx-guest.rst b/Documentation/virt/coco/tdx-guest.rst
index 46e316db6bb4..16afc8e5a6ef 100644
--- a/Documentation/virt/coco/tdx-guest.rst
+++ b/Documentation/virt/coco/tdx-guest.rst
@@ -50,3 +50,11 @@ TDX reference material is collected here:
 https://www.intel.com/content/www/us/en/developer/articles/technical/intel-trust-domain-extensions.html
 
 The driver is based on TDX module specification v1.0 and TDX GHCI specification v1.0.
+
+Attestation device
+==================
+
+With ``CONFIG_ATTESTATION``, the driver also registers an attestation device,
+described in Documentation/userspace-api/attestation-tdx.rst. It shares the
+TDREPORT, Quote and RTMR paths, and their locks, with the misc ioctl, the
+TSM-report configfs interface and the TSM-MR sysfs attributes.
diff --git a/drivers/attestation/Kconfig b/drivers/attestation/Kconfig
index a6bfb17dd480..4307b28e679e 100644
--- a/drivers/attestation/Kconfig
+++ b/drivers/attestation/Kconfig
@@ -10,3 +10,7 @@ menuconfig ATTESTATION
 	  translate the common operations to their native service.
 
 	  If you are unsure, say N.
+
+if ATTESTATION
+
+endif # ATTESTATION
diff --git a/drivers/virt/coco/tdx-guest/Kconfig b/drivers/virt/coco/tdx-guest/Kconfig
index dbbdc14383b1..aa6687979a6f 100644
--- a/drivers/virt/coco/tdx-guest/Kconfig
+++ b/drivers/virt/coco/tdx-guest/Kconfig
@@ -1,12 +1,14 @@
 config TDX_GUEST_DRIVER
 	tristate "TDX Guest driver"
 	depends on INTEL_TDX_GUEST
+	depends on ATTESTATION || !ATTESTATION
 	select TSM_REPORTS
 	select TSM_MEASUREMENTS
 	help
 	  The driver provides userspace interface to communicate with
 	  the TDX module to request the TDX guest details like attestation
-	  report.
+	  report. With ATTESTATION, it also registers a TDX attestation
+	  device.
 
 	  To compile this driver as module, choose M here. The module will
 	  be called tdx-guest.
diff --git a/drivers/virt/coco/tdx-guest/Makefile b/drivers/virt/coco/tdx-guest/Makefile
index 775cb463f9c8..027cc16e1df3 100644
--- a/drivers/virt/coco/tdx-guest/Makefile
+++ b/drivers/virt/coco/tdx-guest/Makefile
@@ -1,2 +1,8 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_TDX_GUEST_DRIVER) += tdx-guest.o
+
+tdx-guest-y := main.o
+tdx-guest-$(CONFIG_ATTESTATION) += attestation.o ctlv_attestation_tdx_gen.o
+
+# The generated family header includes its parent's.
+ccflags-y += -I $(srctree)/drivers/attestation/generated
diff --git a/drivers/virt/coco/tdx-guest/attestation.c b/drivers/virt/coco/tdx-guest/attestation.c
new file mode 100644
index 000000000000..1bb2e7b66928
--- /dev/null
+++ b/drivers/virt/coco/tdx-guest/attestation.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <crypto/sha2.h>
+#include <linux/acpi.h>
+#include <linux/attestation.h>
+#include <linux/ctlv.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+
+#include <uapi/linux/tdx-guest.h>
+
+#include "ctlv_attestation_tdx_gen.h"
+#include "internal.h"
+
+struct tdx_attestation {
+	struct attestation_dev att;
+	const struct tdx_guest_info *info;
+	void __iomem *log;
+	size_t log_length;
+};
+
+static struct tdx_attestation *tdx_att;
+
+static struct tdx_attestation *to_tdx_attestation(struct attestation_dev *att)
+{
+	return container_of(att, struct tdx_attestation, att);
+}
+
+static const enum hash_algo tdx_attestation_algorithms[] = {
+	HASH_ALGO_SHA384,
+};
+
+static const struct attestation_mr_props tdx_attestation_mrs[] = {
+	{
+		.id = 0,
+		.name = "mrtd",
+		.algorithms = tdx_attestation_algorithms,
+		.algorithms_count = ARRAY_SIZE(tdx_attestation_algorithms),
+	},
+	{
+		.id = 1,
+		.name = "rtmr0",
+		.algorithms = tdx_attestation_algorithms,
+		.algorithms_count = ARRAY_SIZE(tdx_attestation_algorithms),
+		.has_register_index = true,
+		.register_index = 0,
+		.extendable = true,
+	},
+	{
+		.id = 2,
+		.name = "rtmr1",
+		.algorithms = tdx_attestation_algorithms,
+		.algorithms_count = ARRAY_SIZE(tdx_attestation_algorithms),
+		.has_register_index = true,
+		.register_index = 1,
+		.extendable = true,
+	},
+	{
+		.id = 3,
+		.name = "rtmr2",
+		.algorithms = tdx_attestation_algorithms,
+		.algorithms_count = ARRAY_SIZE(tdx_attestation_algorithms),
+		.has_register_index = true,
+		.register_index = 2,
+		.extendable = true,
+	},
+	{
+		.id = 4,
+		.name = "rtmr3",
+		.algorithms = tdx_attestation_algorithms,
+		.algorithms_count = ARRAY_SIZE(tdx_attestation_algorithms),
+		.has_register_index = true,
+		.register_index = 3,
+		.extendable = true,
+	},
+};
+
+#define TDX_QUOTE_HEADER_LEN 48
+#define TDX_QUOTE_TEE_TYPE_TDX 0x81
+
+static const struct attestation_evidence_format tdx_attestation_quote_formats[] = {
+	{ .vendor = true, .value = CTLV_ATTESTATION_TDX_EVIDENCE_VENDOR_FORMAT_QUOTE },
+	{ .vendor = true, .value = CTLV_ATTESTATION_TDX_EVIDENCE_VENDOR_FORMAT_QUOTE_DICE },
+};
+
+static const struct attestation_evidence_format tdx_attestation_log_formats[] = {
+	{ .value = CTLV_ATTESTATION_EVIDENCE_FORMAT_CCEL },
+};
+
+static const struct attestation_evidence_challenge_length
+tdx_attestation_challenge_lengths[] = {
+	{ .min = TDX_REPORTDATA_LEN, .max = TDX_REPORTDATA_LEN },
+};
+
+/* The log comes last, so a guest without a CCEL table can leave it out. */
+static struct attestation_evidence_supported_object
+tdx_attestation_evidence_objects[] = {
+	{
+		.evidence_class = ATTESTATION_EVIDENCE_SIGNED,
+		.formats = tdx_attestation_quote_formats,
+		.formats_count = ARRAY_SIZE(tdx_attestation_quote_formats),
+		.challenge_lengths = tdx_attestation_challenge_lengths,
+		.challenge_lengths_count =
+			ARRAY_SIZE(tdx_attestation_challenge_lengths),
+		.max_length = TDX_GUEST_QUOTE_MAX_LEN,
+		.binds_mrs = true,
+	},
+	{
+		.evidence_class = ATTESTATION_EVIDENCE_EVENT_LOG,
+		.formats = tdx_attestation_log_formats,
+		.formats_count = ARRAY_SIZE(tdx_attestation_log_formats),
+	},
+};
+
+/* The object count and the total length are set at init. */
+static struct attestation_evidence_caps tdx_attestation_evidence_caps = {
+	.objects = tdx_attestation_evidence_objects,
+};
+
+/*
+ * A DCAP Quote's header starts with its version, 4 or 5, and TEE type. A
+ * DICE-based Quote, a CBOR Web Token, starts with the CBOR magic, d9 d9 f7.
+ */
+static int tdx_attestation_quote_label(const u8 *quote, size_t length,
+				       struct attestation_evidence_format *format)
+{
+	format->vendor = true;
+	if (length >= TDX_QUOTE_HEADER_LEN &&
+	    (get_unaligned_le16(quote) == 4 || get_unaligned_le16(quote) == 5) &&
+	    get_unaligned_le32(quote + 4) == TDX_QUOTE_TEE_TYPE_TDX) {
+		format->value = CTLV_ATTESTATION_TDX_EVIDENCE_VENDOR_FORMAT_QUOTE;
+		return 0;
+	}
+	if (length >= 3 && !memcmp(quote, "\xd9\xd9\xf7", 3)) {
+		format->value = CTLV_ATTESTATION_TDX_EVIDENCE_VENDOR_FORMAT_QUOTE_DICE;
+		return 0;
+	}
+	return -EPROTO;
+}
+
+static void __iomem *tdx_attestation_log_map(size_t *length)
+{
+	struct acpi_table_header *header;
+	struct acpi_table_ccel *ccel;
+	void __iomem *log = NULL;
+
+	if (ACPI_FAILURE(acpi_get_table(ACPI_SIG_CCEL, 0, &header)))
+		return NULL;
+	ccel = container_of(header, struct acpi_table_ccel, header);
+	if (header->length >= sizeof(*ccel) && ccel->log_area_start_address &&
+	    ccel->log_area_minimum_length &&
+	    ccel->log_area_minimum_length <= UINT_MAX)
+		log = acpi_os_map_iomem(ccel->log_area_start_address,
+					ccel->log_area_minimum_length);
+	if (log)
+		*length = ccel->log_area_minimum_length;
+	acpi_put_table(header);
+	return log;
+}
+
+static int
+tdx_attestation_evidence_get(struct attestation_dev *att,
+			     const struct attestation_evidence_request *request,
+			     struct attestation_evidence_result *result)
+{
+	struct tdx_attestation *tdx = to_tdx_attestation(att);
+	size_t index, length;
+	void *buffer;
+	int ret = 0;
+
+	for (index = 0; !ret && index < request->objects_count; index++) {
+		const struct attestation_evidence_object_request *object =
+			&request->objects[index];
+		struct attestation_evidence_object_result *produced =
+			&result->objects[index];
+
+		buffer = kvmalloc(object->supported_object->max_length,
+				  GFP_KERNEL_ACCOUNT);
+		if (!buffer)
+			return -ENOMEM;
+
+		if (object->supported_object == &tdx_attestation_evidence_objects[0]) {
+			ret = tdx_guest_quote_get(request->challenge, buffer,
+						  &length);
+			if (!ret)
+				ret = tdx_attestation_quote_label(buffer, length,
+								  &produced->format);
+		} else {
+			memcpy_fromio(buffer, tdx->log, tdx->log_length);
+			length = tdx->log_length;
+			produced->format = tdx_attestation_log_formats[0];
+		}
+
+		if (!ret)
+			ret = attestation_evidence_write(object, buffer, length);
+		if (!ret)
+			produced->length = length;
+		kvfree(buffer);
+	}
+	return ret;
+}
+
+static int tdx_attestation_mr_read(struct attestation_dev *att, unsigned int mr_id,
+				   struct attestation_digest_set *values)
+{
+	static const enum tdx_guest_mr native_mrs[] = {
+		TDX_GUEST_MR_MRTD,
+		TDX_GUEST_MR_RTMR0,
+		TDX_GUEST_MR_RTMR1,
+		TDX_GUEST_MR_RTMR2,
+		TDX_GUEST_MR_RTMR3,
+	};
+
+	if (mr_id >= ARRAY_SIZE(native_mrs) ||
+	    values->values_count != 1 ||
+	    values->values[0].algorithm != HASH_ALGO_SHA384)
+		return -EINVAL;
+	return tdx_guest_mr_read(native_mrs[mr_id], values->values[0].data);
+}
+
+static int
+tdx_attestation_mr_extend(struct attestation_dev *att, unsigned int mr_id,
+			  const struct attestation_digest_set *values)
+{
+	if (mr_id < 1 || mr_id > 4)
+		return -EOPNOTSUPP;
+	if (values->values_count != 1 ||
+	    values->values[0].algorithm != HASH_ALGO_SHA384)
+		return -EINVAL;
+	return tdx_guest_rtmr_extend(mr_id - 1, values->values[0].data);
+}
+
+static const struct attestation_provider tdx_attestation_provider = {
+	.evidence_get = tdx_attestation_evidence_get,
+	.mr_read = tdx_attestation_mr_read,
+	.mr_extend = tdx_attestation_mr_extend,
+	.evidence_caps = &tdx_attestation_evidence_caps,
+	.mrs = tdx_attestation_mrs,
+	.mrs_count = ARRAY_SIZE(tdx_attestation_mrs),
+};
+
+int ctlv_attestation_tdx_device_info_fill(struct ctlv *ctlv,
+					  struct ctlv_op_ctx *ctx,
+					  const struct ctlv_cursor *request,
+					  struct ctlv_cursor *record)
+{
+	const struct tdx_guest_info *info =
+		to_tdx_attestation(attestation_from_ctlv(ctlv))->info;
+	bool ok;
+
+	ok = ctlv_attestation_tdx_device_info_mrconfigid_put(record, info->mrconfigid,
+							     sizeof(info->mrconfigid));
+	ok &= ctlv_attestation_tdx_device_info_mrowner_put(record, info->mrowner,
+							   sizeof(info->mrowner));
+	ok &= ctlv_attestation_tdx_device_info_mrownerconfig_put(record,
+								 info->mrownerconfig,
+								 sizeof(info->mrownerconfig));
+	return ok ? 0 : -EMSGSIZE;
+}
+
+int tdx_attestation_register(struct device *parent,
+			     const struct tdx_guest_info *info)
+{
+	struct attestation_evidence_caps *caps = &tdx_attestation_evidence_caps;
+	struct tdx_attestation *tdx;
+	size_t log_length = 0;
+	void __iomem *log;
+	int ret;
+
+	log = tdx_attestation_log_map(&log_length);
+	tdx_attestation_evidence_objects[1].max_length = log_length;
+	caps->objects_count = ARRAY_SIZE(tdx_attestation_evidence_objects);
+	if (!log)
+		caps->objects_count--;
+
+	tdx = attestation_alloc(parent, &tdx_attestation_provider,
+				&ctlv_attestation_tdx_family, struct tdx_attestation, att);
+	if (IS_ERR(tdx)) {
+		ret = PTR_ERR(tdx);
+		goto err_unmap;
+	}
+	tdx->info = info;
+	tdx->log = log;
+	tdx->log_length = log_length;
+	ret = attestation_register(&tdx->att);
+	if (ret) {
+		attestation_put(&tdx->att);
+		goto err_unmap;
+	}
+	tdx_att = tdx;
+	return 0;
+
+err_unmap:
+	if (log)
+		acpi_os_unmap_iomem(log, log_length);
+	return ret;
+}
+
+void tdx_attestation_unregister(void)
+{
+	if (!tdx_att)
+		return;
+	attestation_unregister(&tdx_att->att);
+	if (tdx_att->log)
+		acpi_os_unmap_iomem(tdx_att->log, tdx_att->log_length);
+	attestation_put(&tdx_att->att);
+	tdx_att = NULL;
+}
+
+MODULE_IMPORT_NS("ATTESTATION");
+MODULE_IMPORT_NS("CTLV");
diff --git a/drivers/virt/coco/tdx-guest/internal.h b/drivers/virt/coco/tdx-guest/internal.h
index d57a8c385aff..fc10e68d231a 100644
--- a/drivers/virt/coco/tdx-guest/internal.h
+++ b/drivers/virt/coco/tdx-guest/internal.h
@@ -5,6 +5,8 @@
 #include <crypto/sha2.h>
 #include <linux/types.h>
 
+struct device;
+
 #define TDX_GUEST_QUOTE_MAX_LEN ((128 * 1024) - 24)
 
 struct tdx_guest_info {
@@ -27,4 +29,20 @@ int tdx_guest_mr_read(enum tdx_guest_mr mr, u8 value[SHA384_DIGEST_SIZE]);
 int tdx_guest_rtmr_extend(unsigned int rtmr,
 			  const u8 value[SHA384_DIGEST_SIZE]);
 
+#if IS_ENABLED(CONFIG_ATTESTATION)
+int tdx_attestation_register(struct device *parent,
+			     const struct tdx_guest_info *info);
+void tdx_attestation_unregister(void);
+#else
+static inline int tdx_attestation_register(struct device *parent,
+					   const struct tdx_guest_info *info)
+{
+	return 0;
+}
+
+static inline void tdx_attestation_unregister(void)
+{
+}
+#endif
+
 #endif /* _TDX_GUEST_INTERNAL_H */
diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.c b/drivers/virt/coco/tdx-guest/main.c
similarity index 98%
rename from drivers/virt/coco/tdx-guest/tdx-guest.c
rename to drivers/virt/coco/tdx-guest/main.c
index c49454ec0b18..bcda631a0232 100644
--- a/drivers/virt/coco/tdx-guest/tdx-guest.c
+++ b/drivers/virt/coco/tdx-guest/main.c
@@ -522,6 +522,12 @@ static int __init tdx_guest_init(void)
 	if (ret)
 		goto free_quote;
 
+	/* The other interfaces stay up without the attestation device. */
+	ret = tdx_attestation_register(tdx_misc_dev.this_device,
+				       &tdx_guest_info);
+	if (ret)
+		pr_warn("attestation device registration failed: %d\n", ret);
+
 	return 0;
 
 free_quote:
@@ -537,6 +543,7 @@ module_init(tdx_guest_init);
 
 static void __exit tdx_guest_exit(void)
 {
+	tdx_attestation_unregister();
 	tsm_report_unregister(&tdx_tsm_ops);
 	free_quote_buf(quote_buf);
 	misc_deregister(&tdx_misc_dev);
-- 
2.56.0


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

end of thread, other threads:[~2026-11-13 16:09 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 1/9] attestation: Add the base family specification and generated files Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 2/9] Documentation: attestation: Add userspace ABI Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 3/9] attestation: Prepare provider and instance core Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 4/9] attestation: Prepare measurement registers Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 5/9] attestation: Add CTLV operation handling Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 6/9] Documentation: attestation: Describe how to write a provider Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 7/9] tdx-guest: Expose the native attestation service API Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 8/9] attestation: Add the TDX family specification and generated files Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 9/9] attestation: Add the TDX family implementation Jiri Pirko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®