* [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP)
@ 2026-07-05 22:08 Dan Williams
2026-07-05 22:08 ` [PATCH 01/15] netlink: specs: Introduce multi-message blobs for SPDM Dan Williams
` (15 more replies)
0 siblings, 16 replies; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-pci, driver-core, ankita, Aaron Tomlin,
Alexey Kardashevskiy, Alistair Francis, Aneesh Kumar K.V,
Arnd Bergmann, Bjorn Helgaas, Daniel Gomez, Danilo Krummrich,
Dexuan Cui, Donald Hunter, Greg Kroah-Hartman, Jakub Kicinski,
Jason Gunthorpe, Luis Chamberlain, Lukas Wunner, Petr Pavlu,
Rafael J. Wysocki, Robin Murphy, Sami Tolvanen, Samuel Ortiz,
Saravana Kannan, Will Deacon, Xu Yilun
Major changes since: "[PATCH v2 00/19] PCI/TSM: TEE I/O infrastructure" [1]
- Implement a new netlink schema for dumping large SPDM blobs (Jakub,
Lukas)
- Introduce a device core implementation for evidence gathering given
SPDM is applicable to devices outside of PCI (Lukas)
- Replace the "CC acceptance" mechanism with "Device Trust" (Greg,
Jason)
- Fix TDISP report parsing to use bytes instead of pfns for the start
value of an MMIO report (PCI spec clarification)
- Support the CCA expectation that MMIO reports are BAR aligned (Aneesh)
[1]: http://lore.kernel.org/20260303000207.1836586-1-dan.j.williams@intel.com
[ Note, this series is back to a v1 posting given the new theme of
"Evidence and Trust". It defers some of the lower level details, like
touching arch-level ioremap implementations to consult encrypted_iomem,
until the higher order details are settled ].
---
Summary:
A threat model wants to arrange for devices to not be operated until
they present evidence. Add device evidence gathering, device trust
levels, and hook those up for the PCI/TSM (PCI device security protocol)
implementation.
---
Confidential Computing and other use cases specify a threat model where
devices are potentially adversarial. To date the device core trusts
devices and their drivers, and only specific buses have local
"adversary" device mitigations. The task is define a general trust
concept to up-level existing trust mitigations, and allow for
incremental tightening of mitigations across bus types and IOMMU
drivers.
Goals:
* Create a mechanism for booting with a limited set
devices + drivers and permit selectively enabling more devices +
drivers at runtime.
* Up-level bus specific trust mitigations like PCI
untrusted to a core concept.
* Let buses coordinate entry into the TCB via the existing
->dma_configure() callback
The proposal considers existing trust mitigation in various buses. The
PCI device "untrusted" flags is an example of limiting but not fully
blocking operation of a device. The USB interface authorized flag is an
example of not trusting a device to operate at all. The implementation
starts with these trust level DEVICE_TRUST_NONE, no operation of the
device, and DEVICE_TRUST_ADVERSARY, limited operation of the device.
For example, the PCI "untrusted" flag turns off device translation
capabilities (ATS) and strictly enforces IOMMU mappings, but otherwise
allows operation of the device.
The proposal does not currently incorporate other bus specific security
mechanisms that are independent of individual device operational trust.
For example, USB device (not interface) authorization and Thunderbolt
switch authorization are transport gates to other devices. PCIe Link
Encryption is also a transport security mechanism that informs a trust
decision, but it is still possible to distrust a device with Link
Encryption and conversely distrust a device protected by Link
Encryption. Those security mechanisms are orthogonal to trust policy for
driver bind, address space access, and acceleration feature enabling.
The result is device trust is an input to the driver core and IOMMU
layers. A bus can opt to sync its local bind policy, address space
access, and acceleration features to the trust level, or treat it as an
overriding second-stage policy from the bus's first stage mechanism.
The trust levels established at enumeration and enforced at driver
attach are DEVICE_TRUST_{NONE,ADVERSARY,AUTO,TCB}:
* NONE: no usage of the device unless the trust is explicitly overridden
by user policy specified via a driver flag, module flag, or uapi (TBD).
* ADVERSARY: needs acknowledgement from the bus and IOMMU / DMA layers
that the device is limited to strict IOMMU translation behavior. Drivers
can use this as a signal to limit functionality. This designation
implies follow-on IOMMU and bus enabling work for features like
arranging for the device to attach to a blocked IOMMU domain when
detached from a driver.
* AUTO: typical / historical Linux driver model.
* TCB: a trust level that only exists in Confidential Computing
environments. When acked by the IOMMU / DMA layer it enables the device
to issue direct-DMA to private/encrypted addresses or otherwise attach to
a secure vIOMMU within the TCB.
Overview of changes:
Patches 1-6: Update netlink to be able to support large blob
transfers that the SPDM standard requires. SPDM can be associated with
any device type, so the implementation lands in the device core with
PCI/TSM as the first consumer.
Patches 7-9: Introduce DEVICE_TRUST_NONE and DEVICE_TRUST_ADVERSARY.
Support blocking driver bind by default, and allow either build policy
or module policy to override the the trust level.
Patches 10-12,14-15: Plumb the PCI/TSM support for the PCI device
security (TDISP) protocol to transition the device through
UNLOCKED->LOCKED->RUN.
Patch 13: Support access to private memory either by the bus either
coordinating with a TSM driver, or relying on the bus provider to have
already arranged access (paravisor case).
This passes basic checkout with the sample TSM driver and a sample
netlink client for the device evidence facility. Those patches are only
on the git tree for now [2].
[2]: https://git.kernel.org/pub/scm/linux/kernel/git/devsec/tsm.git/log/?h=devsec-phase2
Dan Williams (15):
netlink: specs: Introduce multi-message blobs for SPDM
tools: ynl: Teach pyynl to handle blobs
tools: ynl: Teach ynl_gen_c to validate and dump 'blob' attributes
device core: Introduce "device evidence" over netlink
device core: Add "device evidence" 'validate' command
PCI/TSM: Add device evidence support
modules: Document the global async_probe parameter
device core: Initial device trust infrastructure
PCI, device core: Move "untrusted" concept to DEVICE_TRUST_ADVERSARY
PCI/TSM: Add device interface security LOCKED support
PCI/TSM: Add device interface security RUN support
PCI/TSM: Add device interface security DMA enable/disable
PCI, device core: Add private memory access for DEVICE_TRUST_TCB
PCI/TSM: Create MMIO descriptors via TDISP Report
PCI/TSM: Add relative MMIO offset support?
drivers/base/Kconfig | 91 +++
drivers/pci/Kconfig | 2 +
drivers/base/Makefile | 2 +
drivers/pci/Makefile | 2 +-
drivers/pci/tsm/Makefile | 8 +
Documentation/ABI/stable/sysfs-module | 17 +
Documentation/ABI/testing/sysfs-bus-pci | 47 +-
Documentation/ABI/testing/sysfs-class-tsm | 19 +
Documentation/driver-api/pci/tsm.rst | 47 ++
Documentation/netlink/genetlink-legacy.yaml | 6 +
Documentation/netlink/genetlink.yaml | 7 +
Documentation/netlink/netlink-raw.yaml | 7 +
.../netlink/specs/device-evidence.yaml | 203 ++++++
drivers/base/base.h | 15 +
drivers/base/device-evidence-netlink.h | 25 +
include/linux/device.h | 3 +
include/linux/device/evidence.h | 88 +++
include/linux/device/trust.h | 56 ++
include/linux/ioport.h | 2 +
include/linux/module.h | 6 +-
include/linux/pci-tsm.h | 104 +++-
include/linux/pci.h | 13 +-
include/uapi/linux/device-evidence.h | 111 ++++
tools/net/ynl/lib/ynl-priv.h | 4 +
tools/net/ynl/lib/ynl.h | 14 +
drivers/base/core.c | 7 +
drivers/base/dd.c | 5 +
drivers/base/device-evidence-netlink.c | 58 ++
drivers/base/evidence.c | 495 +++++++++++++++
drivers/base/trust.c | 103 ++++
drivers/iommu/amd/iommu.c | 2 +-
drivers/iommu/dma-iommu.c | 13 +-
drivers/iommu/intel/iommu.c | 2 +-
drivers/iommu/iommu.c | 2 +-
drivers/pci/ats.c | 2 +-
drivers/pci/pci-driver.c | 25 +-
drivers/pci/pci.c | 2 +-
drivers/pci/probe.c | 10 +-
drivers/pci/quirks.c | 4 +-
drivers/pci/{tsm.c => tsm/core.c} | 579 +++++++++++++++++-
drivers/pci/tsm/evidence.c | 110 ++++
drivers/virt/coco/tsm-core.c | 43 +-
kernel/module/main.c | 13 +
kernel/resource.c | 8 +
tools/net/ynl/lib/ynl.c | 33 +
MAINTAINERS | 6 +-
tools/net/ynl/pyynl/lib/nlspec.py | 12 +
tools/net/ynl/pyynl/lib/ynl.py | 62 +-
tools/net/ynl/pyynl/ynl_gen_c.py | 80 ++-
49 files changed, 2509 insertions(+), 66 deletions(-)
create mode 100644 drivers/pci/tsm/Makefile
create mode 100644 Documentation/netlink/specs/device-evidence.yaml
create mode 100644 drivers/base/device-evidence-netlink.h
create mode 100644 include/linux/device/evidence.h
create mode 100644 include/linux/device/trust.h
create mode 100644 include/uapi/linux/device-evidence.h
create mode 100644 drivers/base/device-evidence-netlink.c
create mode 100644 drivers/base/evidence.c
create mode 100644 drivers/base/trust.c
rename drivers/pci/{tsm.c => tsm/core.c} (61%)
create mode 100644 drivers/pci/tsm/evidence.c
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
--
2.54.0
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 01/15] netlink: specs: Introduce multi-message blobs for SPDM
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
@ 2026-07-05 22:08 ` Dan Williams
2026-07-05 22:08 ` [PATCH 02/15] tools: ynl: Teach pyynl to handle blobs Dan Williams
` (14 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-pci, driver-core, ankita, Alistair Francis, Lukas Wunner,
Jakub Kicinski, Donald Hunter
The SPDM, Security Protocol and Data Model, underpins PCI device security
and other use cases. It defines objects that allow for verification of
device identity and configuration. These objects can be large in size 16MB.
Netlink is otherwise suitable to define the operations, with optional
parameters, and notifications for working with these objects. For example,
operations like "regenerate evidence with nonce", "mark evidence
validated", and "broadcast evidence / security state change events".
A netlink 'blob' is introduced as a way to teach YNL that one instance of a
attribute may span multiple messages. It enables netlink to convey all the
data needed for verification and manipulation of SPDM transported evidence.
The schema change to allows YNL to infer that an attribute may span
multiple messages and interrogate its length to preallocate an
appropriately sized receive buffer.
The design direction to extend the netlink schema for a "multi-message
object receive" case was the result of this discussion [1].
Cc: Alistair Francis <alistair.francis@wdc.com>
Cc: Lukas Wunner <lukas@wunner.de>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Donald Hunter <donald.hunter@gmail.com>
Link: http://lore.kernel.org/20260318170014.6650d2bf@kernel.org [1]
Signed-off-by: Dan Williams <djbw@kernel.org>
---
Documentation/netlink/genetlink-legacy.yaml | 6 ++++++
Documentation/netlink/genetlink.yaml | 7 +++++++
Documentation/netlink/netlink-raw.yaml | 7 +++++++
3 files changed, 20 insertions(+)
diff --git a/Documentation/netlink/genetlink-legacy.yaml b/Documentation/netlink/genetlink-legacy.yaml
index f9c44747729a..fff9d7b76dbc 100644
--- a/Documentation/netlink/genetlink-legacy.yaml
+++ b/Documentation/netlink/genetlink-legacy.yaml
@@ -239,6 +239,12 @@ properties:
enum: [ little-endian, big-endian ]
multi-attr:
type: boolean
+ bloblen:
+ description: |
+ Marks this attribute as a variable-length blob that may be split across
+ multiple messages. The value names a separate scalar attribute that carries
+ the total blob length, and which is sent ahead of the blob data so a consumer
+ can preallocate the full payload.
nested-attributes:
description: Name of the space (sub-space) used inside the attribute.
type: string
diff --git a/Documentation/netlink/genetlink.yaml b/Documentation/netlink/genetlink.yaml
index d3f3f3399ddf..4caee7d55303 100644
--- a/Documentation/netlink/genetlink.yaml
+++ b/Documentation/netlink/genetlink.yaml
@@ -153,6 +153,13 @@ properties:
enum: [ little-endian, big-endian ]
multi-attr:
type: boolean
+ bloblen:
+ description: |
+ Marks this attribute as a variable-length blob that may be split across
+ multiple messages. The value names a separate scalar attribute that carries
+ the total blob length, and which is sent ahead of the blob data so a consumer
+ can preallocate the full payload.
+ type: string
nested-attributes:
description: Name of the space (sub-space) used inside the attribute.
type: string
diff --git a/Documentation/netlink/netlink-raw.yaml b/Documentation/netlink/netlink-raw.yaml
index 4c436b59a34b..dd85f0c56631 100644
--- a/Documentation/netlink/netlink-raw.yaml
+++ b/Documentation/netlink/netlink-raw.yaml
@@ -263,6 +263,13 @@ properties:
enum: [ little-endian, big-endian ]
multi-attr:
type: boolean
+ bloblen:
+ description: |
+ Marks this attribute as a variable-length blob that may be split across
+ multiple messages. The value names a separate scalar attribute that carries
+ the total blob length, and which is sent ahead of the blob data so a consumer
+ can preallocate the full payload.
+ type: string
nested-attributes:
description: Name of the space (sub-space) used inside the attribute.
type: string
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 02/15] tools: ynl: Teach pyynl to handle blobs
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
2026-07-05 22:08 ` [PATCH 01/15] netlink: specs: Introduce multi-message blobs for SPDM Dan Williams
@ 2026-07-05 22:08 ` Dan Williams
2026-07-05 22:08 ` [PATCH 03/15] tools: ynl: Teach ynl_gen_c to validate and dump 'blob' attributes Dan Williams
` (13 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-pci, driver-core, ankita, Alistair Francis, Lukas Wunner,
Donald Hunter, Jakub Kicinski
The generic "device evidence" facility wants to convey large objects
(certificate chains, measurements) and support various security commands
relative to that evidence (like validate) [1].
Update pyynl to understand the "bloblen" property indicates:
* the associated attribute is a variable length byte array
* it is multi-attr where each message with the same attribute represents
chunks of the byte array
* the byte array's size is conveyed in a scalar attribute named by the
"bloblen" property.
* the attribute identified by "bloblen" always arrives in a message before
the first chunk of the associated blob
A "multi-attr" without "bloblen" retains the legacy behavior where repeated
reception of the same attribute across messages results in a new instance
of the object. The new behavior causes repeated reception of an
attribute to be treated as filling consecutive bytes of an array.
Assisted-by: Codex:GPT-5
[codex: drafted initial python changes]
Cc: Alistair Francis <alistair.francis@wdc.com>
Cc: Lukas Wunner <lukas@wunner.de>
Cc: Donald Hunter <donald.hunter@gmail.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Link: http://lore.kernel.org/20260318170014.6650d2bf@kernel.org [1]
Signed-off-by: Dan Williams <djbw@kernel.org>
---
tools/net/ynl/pyynl/lib/nlspec.py | 12 ++++++
tools/net/ynl/pyynl/lib/ynl.py | 62 ++++++++++++++++++++++++++++++-
2 files changed, 72 insertions(+), 2 deletions(-)
diff --git a/tools/net/ynl/pyynl/lib/nlspec.py b/tools/net/ynl/pyynl/lib/nlspec.py
index 0469a0e270d0..4764263968ea 100644
--- a/tools/net/ynl/pyynl/lib/nlspec.py
+++ b/tools/net/ynl/pyynl/lib/nlspec.py
@@ -172,6 +172,7 @@ class SpecAttr(SpecElement):
selector string, name of attribute used to select
sub-message type
+ bloblen string, name of attr which reports the blob length
is_auto_scalar bool, attr is a variable-size scalar
"""
def __init__(self, family, attr_set, yaml, value):
@@ -181,6 +182,7 @@ class SpecAttr(SpecElement):
self.value = value
self.attr_set = attr_set
self.is_multi = yaml.get('multi-attr', False)
+ self.bloblen = yaml.get('bloblen')
self.struct_name = yaml.get('struct')
self.sub_type = yaml.get('sub-type')
self.byte_order = yaml.get('byte-order')
@@ -191,6 +193,10 @@ class SpecAttr(SpecElement):
self.is_auto_scalar = self.type in ("sint", "uint")
+ if self.bloblen and not self.is_multi:
+ raise SpecException(
+ f"Attribute '{attr_set.name}.{self.name}' has bloblen without multi-attr")
+
class SpecAttrSet(SpecElement):
""" Netlink Attribute Set class.
@@ -355,6 +361,8 @@ class SpecOperation(SpecElement):
attr_set attribute set name
fixed_header string, optional name of fixed header struct
+ dump_blob_attrs set, reply blob attribute names which may span messages
+
yaml raw spec as loaded from the spec file
"""
def __init__(self, family, yaml, req_value, rsp_value):
@@ -369,6 +377,7 @@ class SpecOperation(SpecElement):
self.is_async = 'notify' in yaml or 'event' in yaml
self.is_resv = not self.is_async and not self.is_call
self.fixed_header = self.yaml.get('fixed-header', family.fixed_header)
+ self.dump_blob_attrs = frozenset()
# Added by resolve:
self.attr_set = None
@@ -388,6 +397,9 @@ class SpecOperation(SpecElement):
raise SpecException(f"Can't resolve attribute set for op '{self.name}'")
if attr_set_name:
self.attr_set = self.family.attr_sets[attr_set_name]
+ reply_attrs = self.yaml.get('dump', {}).get('reply', {}).get('attributes', ())
+ self.dump_blob_attrs = frozenset(
+ name for name in reply_attrs if self.attr_set[name].bloblen)
class SpecMcastGroup(SpecElement):
diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py
index 092d132edec1..f21b68043bd3 100644
--- a/tools/net/ynl/pyynl/lib/ynl.py
+++ b/tools/net/ynl/pyynl/lib/ynl.py
@@ -998,6 +998,47 @@ class YnlFamily(SpecFamily):
else:
rsp[name] = [decoded]
+ def _blob_fill(self, buf, off, chunks, name):
+ for chunk in chunks:
+ end = off + len(chunk)
+ if end > len(buf):
+ raise YnlException(f"Blob '{name}' data exceeds reported length")
+ buf[off:end] = chunk
+ off = end
+ return off
+
+ def _blob_init(self, op, rsp, blob_attrs):
+ """
+ Start a new blob object. Preallocate each blob payload using the
+ length carried ahead of the data and copy in the first chunk(s).
+ Return the per-attribute write offsets for any following messages.
+ """
+ offsets = {}
+ for name in blob_attrs:
+ if name not in rsp:
+ continue
+ length = rsp.get(op.attr_set[name].bloblen, 0)
+ buf = bytearray(length)
+ offsets[name] = self._blob_fill(buf, 0, rsp[name], name)
+ rsp[name] = buf
+ return offsets
+
+ def _blob_extend(self, rsp, update, blob_attrs, offsets):
+ """
+ Continue filling the previous object's blob(s) when the current
+ message carries only blob continuation attributes.
+ """
+ if any(name in rsp and name not in blob_attrs for name in update):
+ return False
+
+ for name, value in update.items():
+ if name in blob_attrs and name in rsp:
+ offsets[name] = self._blob_fill(rsp[name], offsets.get(name, 0),
+ value, name)
+ else:
+ rsp[name] = value
+ return True
+
def _resolve_selector(self, attr_spec, search_attrs):
sub_msg = attr_spec.sub_message
if sub_msg not in self.sub_msgs:
@@ -1356,7 +1397,10 @@ class YnlFamily(SpecFamily):
for (method, vals, flags) in ops:
op = self.ops[method]
msg = self._encode_message(op, vals, flags, req_seq)
- reqs_by_seq[req_seq] = (op, vals, msg, flags)
+ blob_attrs = None
+ if Netlink.NLM_F_DUMP in flags:
+ blob_attrs = op.dump_blob_attrs or None
+ reqs_by_seq[req_seq] = (op, vals, msg, flags, blob_attrs)
payload += msg
req_seq += 1
@@ -1365,6 +1409,7 @@ class YnlFamily(SpecFamily):
done = False
rsp = []
op_rsp = []
+ blob_off = {}
while not done:
reply, ancdata = self._recvmsg()
nsid = self._decode_nsid(ancdata)
@@ -1372,13 +1417,14 @@ class YnlFamily(SpecFamily):
self._recv_dbg_print(reply, nms)
for nl_msg in nms:
if nl_msg.nl_seq in reqs_by_seq:
- (op, vals, req_msg, req_flags) = reqs_by_seq[nl_msg.nl_seq]
+ (op, vals, req_msg, req_flags, blob_attrs) = reqs_by_seq[nl_msg.nl_seq]
if nl_msg.extack:
nl_msg.annotate_extack(op.attr_set)
self._decode_extack(req_msg, op, nl_msg.extack, vals)
else:
op = None
req_flags = []
+ blob_attrs = None
if nl_msg.error:
raise NlError(nl_msg)
@@ -1388,6 +1434,11 @@ class YnlFamily(SpecFamily):
print(nl_msg)
if Netlink.NLM_F_DUMP in req_flags:
+ if blob_attrs:
+ for obj in op_rsp:
+ for name in blob_attrs:
+ if isinstance(obj.get(name), bytearray):
+ obj[name] = bytes(obj[name])
rsp.append(op_rsp)
elif not op_rsp:
rsp.append(None)
@@ -1414,6 +1465,13 @@ class YnlFamily(SpecFamily):
rsp_msg = self._decode(decoded.raw_attrs, op.attr_set.name)
if op.fixed_header:
rsp_msg.update(self._decode_struct(decoded.raw, op.fixed_header))
+
+ if blob_attrs:
+ if op_rsp and self._blob_extend(op_rsp[-1], rsp_msg,
+ blob_attrs, blob_off):
+ continue
+ blob_off = self._blob_init(op, rsp_msg, blob_attrs)
+
op_rsp.append(rsp_msg)
return rsp
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 03/15] tools: ynl: Teach ynl_gen_c to validate and dump 'blob' attributes
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
2026-07-05 22:08 ` [PATCH 01/15] netlink: specs: Introduce multi-message blobs for SPDM Dan Williams
2026-07-05 22:08 ` [PATCH 02/15] tools: ynl: Teach pyynl to handle blobs Dan Williams
@ 2026-07-05 22:08 ` Dan Williams
2026-07-05 22:08 ` [PATCH 04/15] device core: Introduce "device evidence" over netlink Dan Williams
` (12 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-pci, driver-core, ankita, Alistair Francis, Lukas Wunner,
Donald Hunter, Jakub Kicinski
Now that blobs are recognized by pyynl and coalesced over successive
netlink messages, do the same for the generated C. Recall that blobs
support large objects for protocols like SPDM.
With blobs the generator can assume that the length of the blob will be
conveyed in the response messages before the blob data message(s). Update
the C generation to:
* Mark when a given operation may have blobs (objects that need more than
one message to fill) with a new ynl_more_cb_t in the dump state.
* Use @more() determine when to continue filling a partially received
blob, and check if the end of the message stream is reached without
receiving all of its data.
* Pre-allocate the blob and parse successive messages into that shared
object.
This proposal is the follow up to a discussion [1] where an alternative
solution of adding an @offset attribute to make each chunk standalone was
discarded. That approach commits userspace to inefficient attribute
reassembly where it needs to retrieve a series of structures with offset
properties and then reassemble them into the full blob.
Assisted-by: Codex:GPT-5.5
[codex: helped with typing out the code generation]
Cc: Alistair Francis <alistair.francis@wdc.com>
Cc: Lukas Wunner <lukas@wunner.de>
Cc: Donald Hunter <donald.hunter@gmail.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Link: http://lore.kernel.org/20260318170014.6650d2bf@kernel.org [1]
Signed-off-by: Dan Williams <djbw@kernel.org>
---
tools/net/ynl/lib/ynl-priv.h | 4 ++
tools/net/ynl/lib/ynl.h | 14 ++++++
tools/net/ynl/lib/ynl.c | 33 +++++++++++++
tools/net/ynl/pyynl/ynl_gen_c.py | 80 +++++++++++++++++++++++++++++---
4 files changed, 124 insertions(+), 7 deletions(-)
diff --git a/tools/net/ynl/lib/ynl-priv.h b/tools/net/ynl/lib/ynl-priv.h
index ced7dce44efb..dba98cf78204 100644
--- a/tools/net/ynl/lib/ynl-priv.h
+++ b/tools/net/ynl/lib/ynl-priv.h
@@ -41,6 +41,9 @@ enum ynl_parse_result {
typedef int (*ynl_parse_cb_t)(const struct nlmsghdr *nlh,
struct ynl_parse_arg *yarg);
+typedef bool (*ynl_more_cb_t)(void *data);
+
+struct ynl_blob *ynl_blob_alloc(unsigned int len);
struct ynl_policy_attr {
enum ynl_policy_type type:8;
@@ -123,6 +126,7 @@ struct ynl_dump_state {
struct ynl_dump_list_type *last;
size_t alloc_sz;
ynl_parse_cb_t cb;
+ ynl_more_cb_t more;
__u32 rsp_cmd;
};
diff --git a/tools/net/ynl/lib/ynl.h b/tools/net/ynl/lib/ynl.h
index db7c0591a63f..9c50b068ee83 100644
--- a/tools/net/ynl/lib/ynl.h
+++ b/tools/net/ynl/lib/ynl.h
@@ -100,6 +100,20 @@ struct ynl_string {
char str[];
};
+/**
+ * struct ynl_blob - variable-length blob attribute
+ * @len: length of the blob data
+ * @data: value of the blob attribute
+ *
+ * Parsed binary value. This struct is used for variable-length blob values
+ * that may be built from one or more attributes spanning multiple netlink
+ * messages.
+ */
+struct ynl_blob {
+ unsigned int len;
+ unsigned char data[];
+};
+
struct ynl_sock *
ynl_sock_create(const struct ynl_family *yf, struct ynl_error *e);
void ynl_sock_destroy(struct ynl_sock *ys);
diff --git a/tools/net/ynl/lib/ynl.c b/tools/net/ynl/lib/ynl.c
index 2bcd781111d7..933ee0ad2c51 100644
--- a/tools/net/ynl/lib/ynl.c
+++ b/tools/net/ynl/lib/ynl.c
@@ -1003,6 +1003,27 @@ int ynl_exec(struct ynl_sock *ys, struct nlmsghdr *req_nlh,
return err;
}
+struct ynl_blob *ynl_blob_alloc(unsigned int len)
+{
+ size_t size = sizeof(struct ynl_blob) + len;
+ struct ynl_blob *blob;
+
+ if (size < len)
+ return NULL;
+ blob = malloc(size);
+ if (blob)
+ blob->len = len;
+ return blob;
+}
+
+/* continue filling a blob attribute? */
+static bool ynl_check_more(struct ynl_dump_state *ds)
+{
+ struct ynl_dump_list_type *prev = ds->last;
+
+ return prev && ds->more && ds->more(&prev->data);
+}
+
static int
ynl_dump_trampoline(const struct nlmsghdr *nlh, struct ynl_parse_arg *data)
{
@@ -1015,6 +1036,13 @@ ynl_dump_trampoline(const struct nlmsghdr *nlh, struct ynl_parse_arg *data)
if (ret)
return ret < 0 ? YNL_PARSE_CB_ERROR : YNL_PARSE_CB_OK;
+ if (ynl_check_more(ds)) {
+ yarg = ds->yarg;
+ yarg.data = &ds->last->data;
+
+ return ds->cb(nlh, &yarg);
+ }
+
obj = calloc(1, ds->alloc_sz);
if (!obj)
return YNL_PARSE_CB_ERROR;
@@ -1059,6 +1087,11 @@ int ynl_exec_dump(struct ynl_sock *ys, struct nlmsghdr *req_nlh,
goto err_close_list;
} while (err > 0);
+ if (ynl_check_more(yds)) {
+ yerr(ys, YNL_ERROR_ATTR_MISSING, "Dump ended with an incomplete blob");
+ goto err_close_list;
+ }
+
yds->first = ynl_dump_end(yds);
return 0;
diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py
index cdc3646f2642..f0387004b7cc 100755
--- a/tools/net/ynl/pyynl/ynl_gen_c.py
+++ b/tools/net/ynl/pyynl/ynl_gen_c.py
@@ -727,14 +727,20 @@ class TypeMultiAttr(Type):
def is_multi_val(self):
return True
+ def is_blob(self):
+ return bool(self.bloblen)
+
def presence_type(self):
- return 'count'
+ return 'len' if self.is_blob() else 'count'
def _complex_member_type(self, ri):
if 'type' not in self.attr or self.attr['type'] == 'nest':
return self.nested_struct_type
- if self.attr['type'] == 'binary' and 'struct' in self.attr:
- return None # use arg_member()
+ if self.attr['type'] == 'binary':
+ if 'struct' in self.attr:
+ return None # use arg_member()
+ if self.is_blob():
+ return 'struct ynl_blob'
if self.attr['type'] == 'string':
return 'struct ynl_string *'
if self.attr['type'] in scalars:
@@ -747,9 +753,12 @@ class TypeMultiAttr(Type):
raise Exception(f"Sub-type {self.attr['type']} not supported yet")
def arg_member(self, ri):
- if self.type == 'binary' and 'struct' in self.attr:
- return [f'struct {c_lower(self.attr["struct"])} *{self.c_name}',
- f'unsigned int n_{self.c_name}']
+ if self.type == 'binary':
+ if 'struct' in self.attr:
+ return [f'struct {c_lower(self.attr["struct"])} *{self.c_name}',
+ f'unsigned int n_{self.c_name}']
+ if self.is_blob():
+ return [f'struct ynl_blob *{self.c_name}']
return super().arg_member(ri)
def free_needs_iter(self):
@@ -784,6 +793,24 @@ class TypeMultiAttr(Type):
return self.base_type._attr_typol()
def _attr_get(self, ri, var):
+ if self.is_blob():
+ length = self.attr_set[self.bloblen]
+ return [
+ f"if (!{var}->{self.c_name})",
+ f"{var}->{self.c_name} = ynl_blob_alloc({var}->{length.c_name});",
+ f"if (!{var}->{self.c_name})",
+ "return YNL_PARSE_CB_ERROR;",
+ f"if ({var}->_len.{self.c_name} > {var}->{self.c_name}->len || " +
+ f"len > {var}->{self.c_name}->len - {var}->_len.{self.c_name})",
+ f'return ynl_error_parse(yarg, "blob data exceeds length '
+ f'({self.attr_set.name}.{self.name})");',
+ f"memcpy({var}->{self.c_name}->data + {var}->_len.{self.c_name}, " +
+ "ynl_attr_data(attr), len);",
+ f"{var}->_len.{self.c_name} += len;"], \
+ ['if (ynl_attr_validate(yarg, attr))', 'return YNL_PARSE_CB_ERROR;',
+ 'len = ynl_attr_data_len(attr);'], \
+ ['unsigned int len;']
+
return f'n_{self.c_name}++;', None, None
def attr_put(self, ri, var):
@@ -791,6 +818,10 @@ class TypeMultiAttr(Type):
put_type = self.type
ri.cw.p(f"for (i = 0; i < {var}->_count.{self.c_name}; i++)")
ri.cw.p(f"ynl_attr_put_{put_type}(nlh, {self.enum_name}, {var}->{self.c_name}[i]);")
+ elif self.attr['type'] == 'binary' and self.is_blob():
+ ri.cw.p(f"if ({var}->{self.c_name})")
+ ri.cw.p(f"ynl_attr_put(nlh, {self.enum_name}, " +
+ f"{var}->{self.c_name}->data, {var}->{self.c_name}->len);")
elif self.attr['type'] == 'binary' and 'struct' in self.attr:
ri.cw.p(f"for (i = 0; i < {var}->_count.{self.c_name}; i++)")
ri.cw.p(f"ynl_attr_put(nlh, {self.enum_name}, &{var}->{self.c_name}[i], sizeof(struct {c_lower(self.attr['struct'])}));")
@@ -805,6 +836,10 @@ class TypeMultiAttr(Type):
raise Exception(f"Put of MultiAttr sub-type {self.attr['type']} not supported yet")
def _setter_lines(self, ri, member, presence):
+ if self.is_blob():
+ return [f"{presence} = {self.c_name}->len;",
+ f"{member} = malloc(sizeof(*{member}) + {presence});",
+ f"memcpy({member}, {self.c_name}, sizeof(*{member}) + {presence});"]
return [f"{member} = {self.c_name};",
f"{presence} = n_{self.c_name};"]
@@ -2164,7 +2199,8 @@ def _multi_parse(ri, struct, init_lines, local_vars):
else:
raise Exception(f'Not supported sub-type {aspec["sub-type"]}')
if 'multi-attr' in aspec:
- multi_attrs.add(arg)
+ if not aspec.bloblen:
+ multi_attrs.add(arg)
needs_parg |= 'nested-attributes' in aspec
needs_parg |= 'sub-message' in aspec
@@ -2388,6 +2424,33 @@ def parse_rsp_msg(ri, deref=False):
ri.cw.nl()
+def _blob_attrs(ri):
+ return [attr for _, attr in ri.struct['reply'].member_list() if attr.bloblen]
+
+
+def print_rsp_more(ri):
+ blob_attrs = _blob_attrs(ri)
+ if not blob_attrs:
+ return
+
+ local_vars = [f'{type_name(ri, "reply", deref=True)} *rsp = data;']
+
+ ri.cw.write_func_prot('static bool',
+ f'{op_prefix(ri, "reply", deref=True)}_more',
+ ['void *data'], suffix='')
+ ri.cw.block_start()
+ ri.cw.write_func_lvar(local_vars)
+
+ for attr in blob_attrs:
+ ri.cw.p(f"if (rsp->{attr.c_name} && " +
+ f"rsp->_len.{attr.c_name} < rsp->{attr.c_name}->len)")
+ ri.cw.p('return true;')
+
+ ri.cw.p('return false;')
+ ri.cw.block_end()
+ ri.cw.nl()
+
+
def print_req(ri):
ret_ok = '0'
ret_err = '-1'
@@ -2482,6 +2545,8 @@ def print_dump(ri):
ri.cw.p("yds.yarg.data = NULL;")
ri.cw.p(f"yds.alloc_sz = sizeof({type_name(ri, rdir(direction))});")
ri.cw.p(f"yds.cb = {op_prefix(ri, 'reply', deref=True)}_parse;")
+ if _blob_attrs(ri):
+ ri.cw.p(f"yds.more = {op_prefix(ri, 'reply', deref=True)}_more;")
if ri.op.value is not None:
ri.cw.p(f'yds.rsp_cmd = {ri.op.enum_name};')
else:
@@ -3731,6 +3796,7 @@ def main():
parse_rsp_msg(ri, deref=True)
print_req_free(ri)
print_dump_type_free(ri)
+ print_rsp_more(ri)
print_dump(ri)
cw.nl()
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 04/15] device core: Introduce "device evidence" over netlink
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
` (2 preceding siblings ...)
2026-07-05 22:08 ` [PATCH 03/15] tools: ynl: Teach ynl_gen_c to validate and dump 'blob' attributes Dan Williams
@ 2026-07-05 22:08 ` Dan Williams
2026-07-05 22:08 ` [PATCH 05/15] device core: Add "device evidence" 'validate' command Dan Williams
` (11 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-pci, driver-core, ankita, Alistair Francis, Lukas Wunner,
Donald Hunter, Jakub Kicinski, Bjorn Helgaas, Xu Yilun,
Aneesh Kumar K.V (Arm), Alexey Kardashevskiy
Multiple device security mechanisms are built on top of the SPDM protocol
from the DMTF. The protocol arranges for devices to verify their identity
and establish secure sessions between requesters and responders. In turn,
device security protocols like PCIe IDE use SPDM sessions to program link
encryption keys, and protocols like PCIe TDISP use exclusive secure
sessions for managing the device's security state.
All of these uses share a need to retrieve the device's installed
certificates, its measurements, and other protocol specific objects like
interface reports. Unify all of those object retrieval and regeneration
cases into a device-scoped ABI that buses can leverage.
When PCI CMA was presented at Linux Plumbers the consensus reaction was
that it was not suitable to be driven via sysfs. Given the varied use
cases of the same fundamental objects the consensus converged on
netlink. With the new 'blob' extensions to netlink, it can support up to
the 16MB signed transcripts that SPDM generates.
A bus opts in to evidence gathering via device_evidence_register() and
passing device_evidence_ops. The ops resolve a device handle to its
corresponding 'struct device_evidence' context.
See inline documentation in
Documentation/netlink/specs/device-evidence.yaml for the object types and
options. This includes objects like TDISP interface reports and options
like digests instead of full blobs.
Cc: Alistair Francis <alistair.francis@wdc.com>
Cc: Lukas Wunner <lukas@wunner.de>
Cc: Donald Hunter <donald.hunter@gmail.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Xu Yilun <yilun.xu@linux.intel.com>
Cc: "Aneesh Kumar K.V (Arm)" <aneesh.kumar@kernel.org>
Cc: Alexey Kardashevskiy <aik@amd.com>
Signed-off-by: Dan Williams <djbw@kernel.org>
---
drivers/base/Kconfig | 4 +
drivers/base/Makefile | 1 +
.../netlink/specs/device-evidence.yaml | 186 +++++++
drivers/base/device-evidence-netlink.h | 23 +
include/linux/device/evidence.h | 85 ++++
include/uapi/linux/device-evidence.h | 110 +++++
drivers/base/device-evidence-netlink.c | 44 ++
drivers/base/evidence.c | 453 ++++++++++++++++++
MAINTAINERS | 4 +
9 files changed, 910 insertions(+)
create mode 100644 Documentation/netlink/specs/device-evidence.yaml
create mode 100644 drivers/base/device-evidence-netlink.h
create mode 100644 include/linux/device/evidence.h
create mode 100644 include/uapi/linux/device-evidence.h
create mode 100644 drivers/base/device-evidence-netlink.c
create mode 100644 drivers/base/evidence.c
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index f7d385cbd3ba..e3929fe6b240 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -190,6 +190,10 @@ config HMEM_REPORTING
Enable reporting for heterogeneous memory access attributes under
their non-uniform memory nodes.
+config DEVICE_EVIDENCE
+ bool
+ depends on NET
+
source "drivers/base/test/Kconfig"
config SYS_HYPERVISOR
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index 8074a10183dc..02bdc4f74019 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_DEV_COREDUMP) += devcoredump.o
obj-$(CONFIG_GENERIC_MSI_IRQ) += platform-msi.o
obj-$(CONFIG_GENERIC_ARCH_TOPOLOGY) += arch_topology.o
obj-$(CONFIG_GENERIC_ARCH_NUMA) += arch_numa.o
+obj-$(CONFIG_DEVICE_EVIDENCE) += evidence.o device-evidence-netlink.o
obj-$(CONFIG_ACPI) += physical_location.o
obj-y += test/
diff --git a/Documentation/netlink/specs/device-evidence.yaml b/Documentation/netlink/specs/device-evidence.yaml
new file mode 100644
index 000000000000..44b278bc20e1
--- /dev/null
+++ b/Documentation/netlink/specs/device-evidence.yaml
@@ -0,0 +1,186 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+#
+---
+name: device-evidence
+protocol: genetlink
+uapi-header: linux/device-evidence.h
+doc: Device evidence retrieval over generic netlink
+
+definitions:
+ -
+ type: const
+ name: max-object-size
+ value: 0x01000000
+ -
+ type: const
+ name: max-nonce-size
+ value: 32
+ -
+ name: type
+ type: enum
+ doc: Device security evidence objects
+ render-max: true
+ entries:
+ -
+ name: cert0
+ doc: SPDM certificate chain from device slot0
+ -
+ name: cert1
+ doc: SPDM certificate chain from device slot1
+ -
+ name: cert2
+ doc: SPDM certificate chain from device slot2
+ -
+ name: cert3
+ doc: SPDM certificate chain from device slot3
+ -
+ name: cert4
+ doc: SPDM certificate chain from device slot4
+ -
+ name: cert5
+ doc: SPDM certificate chain from device slot5
+ -
+ name: cert6
+ doc: SPDM certificate chain from device slot6
+ -
+ name: cert7
+ doc: SPDM certificate chain from device slot7
+ -
+ name: vca
+ doc: |
+ SPDM version, capabilities, and algorithms transcript
+ negotiated at session establishment. An implementation may not
+ provide this separately and instead include it in the
+ measurements transcript.
+ -
+ name: measurements
+ doc: SPDM GET_MEASUREMENTS response
+ -
+ name: report
+ doc: |
+ A bus that implements a device interface security protocol
+ like TDISP may convey an interface report that details
+ interface settings and capabilities.
+ -
+ name: type-flag
+ type: flags
+ doc: Device security evidence request flags
+ render-max: true
+ # NOTE! these values must match the type enum name and order
+ entries:
+ -
+ name: cert0
+ -
+ name: cert1
+ -
+ name: cert2
+ -
+ name: cert3
+ -
+ name: cert4
+ -
+ name: cert5
+ -
+ name: cert6
+ -
+ name: cert7
+ -
+ name: vca
+ -
+ name: measurements
+ -
+ name: report
+ -
+ name: flag
+ type: flags
+ render-max: true
+ doc: Flags to control evidence retrieval
+ entries:
+ -
+ name: digest
+ doc: |
+ Request a secure hash of objects like vca and measurements.
+ The expectation is that this digest is produced by a responder
+ within the TCB like a platform TSM. It validates a blob that
+ may have traversed a transport without integrity protections.
+
+attribute-sets:
+ -
+ name: object
+ attributes:
+ -
+ name: type
+ type: u32
+ doc: evidence type-id
+ -
+ name: type-mask
+ type: u32
+ doc: evidence types as a flag mask
+ -
+ name: flags
+ type: u32
+ doc: evidence modifier flags like 'request object digest'
+ -
+ name: subsys
+ type: string
+ doc: device subsystem name (e.g. bus or class name)
+ -
+ name: dev-name
+ type: string
+ doc: device name
+ -
+ name: nonce
+ type: binary
+ checks:
+ max-len: max-nonce-size
+ -
+ name: generation
+ type: u32
+ doc: detect local threads racing evidence refresh
+ -
+ name: count
+ type: u64
+ doc: TSM, if present, observed count of evidence refresh events
+ -
+ name: length
+ type: u32
+ checks:
+ max: max-object-size
+ doc: |
+ Final size of 'val' blob after all messages received.
+ -
+ name: val
+ type: binary
+ multi-attr: true
+ bloblen: length
+ doc: |
+ Ordered evidence object value chunk. Large evidence objects
+ may be split over several dump reply messages up to 'length'.
+ 'length' is transmitted before the first chunk.
+
+operations:
+ list:
+ -
+ name: read
+ doc: |
+ Read device evidence objects of a given type mask. The dump reply
+ reports the total value length before val attributes fill the evidence
+ object, possibly across multiple messages.
+ attribute-set: object
+ flags: [admin-perm]
+ dump:
+ pre: device-evidence-nl-read-pre
+ post: device-evidence-nl-read-post
+ request:
+ attributes:
+ - type-mask
+ - flags
+ - subsys
+ - dev-name
+ - nonce
+ reply:
+ attributes:
+ - type
+ - generation
+ - length
+ - val
diff --git a/drivers/base/device-evidence-netlink.h b/drivers/base/device-evidence-netlink.h
new file mode 100644
index 000000000000..a7a3110630a6
--- /dev/null
+++ b/drivers/base/device-evidence-netlink.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/device-evidence.yaml */
+/* YNL-GEN kernel header */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#ifndef _LINUX_DEVICE_EVIDENCE_GEN_H
+#define _LINUX_DEVICE_EVIDENCE_GEN_H
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include <uapi/linux/device-evidence.h>
+
+int device_evidence_nl_read_pre(struct netlink_callback *cb);
+int device_evidence_nl_read_post(struct netlink_callback *cb);
+
+int device_evidence_nl_read_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb);
+
+extern struct genl_family device_evidence_nl_family;
+
+#endif /* _LINUX_DEVICE_EVIDENCE_GEN_H */
diff --git a/include/linux/device/evidence.h b/include/linux/device/evidence.h
new file mode 100644
index 000000000000..5036fbe63e30
--- /dev/null
+++ b/include/linux/device/evidence.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2026 Intel Corporation */
+/* Copyright (C) 2026, NVIDIA Corporation & Affiliates */
+#ifndef __DEVICE_EVIDENCE_H
+#define __DEVICE_EVIDENCE_H
+
+#include <linux/errno.h>
+#include <linux/rwsem.h>
+#include <linux/types.h>
+#include <uapi/linux/device-evidence.h>
+#include <uapi/linux/hash_info.h>
+
+struct device;
+struct module;
+
+/**
+ * struct device_evidence_object - General device evidence blob descriptor
+ * @data: pointer to the evidence data blob
+ * @len: length of the evidence data blob
+ * @digest: TSM generated digest of the data blob
+ */
+struct device_evidence_object {
+ void *data;
+ size_t len;
+ void *digest;
+};
+
+/**
+ * struct device_evidence - Retrieved device evidence
+ * @slot: certificate slot used by a link TSM for connect
+ * @generation: refresh_evidence() invocation detection
+ * @digest_algo: payload size of DEVICE_EVIDENCE_FLAG_DIGEST requests
+ * @lock: synchronize dumps vs refresh_evidence()
+ * @obj: array of evidence objects a TSM might populate
+ *
+ * An increment of @generation causes in flight dumps to fail with -EAGAIN.
+ */
+struct device_evidence {
+ int slot;
+ u32 generation;
+ enum hash_algo digest_algo;
+ struct rw_semaphore lock;
+ struct device_evidence_object obj[DEVICE_EVIDENCE_TYPE_MAX + 1];
+};
+
+/**
+ * struct device_evidence_ops - subsys-specific evidence lookup operations
+ * @subsys_name: /sys/{bus,class}/@subsys_name
+ * @find_device: fetch device object for ../@subsys_name/devices/@name
+ * @evidence_read_begin: hold evidence stable over read
+ * @evidence_read_end: hold evidence stable over read
+ * @refresh_evidence: generate fresh device evidence
+ *
+ * Connect the generic "device-evidence" netlink transport to a source
+ * of device evidence that conveys SPDM collateral and its extensions
+ * like PCIe TDISP interface reports
+ */
+struct device_evidence_ops {
+ const char *subsys_name;
+ struct device *(*find_device)(const char *name);
+ struct device_evidence *(*evidence_read_begin)(struct device *dev);
+ void (*evidence_read_end)(struct device_evidence *evidence);
+ int (*refresh_evidence)(struct device *dev, const void *nonce,
+ size_t nonce_len);
+};
+
+#ifdef CONFIG_DEVICE_EVIDENCE
+struct device_evidence *device_evidence_create(int slot,
+ enum hash_algo digest_algo);
+int device_evidence_register(const struct device_evidence_ops *ops);
+void device_evidence_unregister(const struct device_evidence_ops *ops);
+#else
+static inline int
+device_evidence_register(const struct device_evidence_ops *ops)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void
+device_evidence_unregister(const struct device_evidence_ops *ops)
+{
+}
+#endif
+
+#endif /* __DEVICE_EVIDENCE_H */
diff --git a/include/uapi/linux/device-evidence.h b/include/uapi/linux/device-evidence.h
new file mode 100644
index 000000000000..b184174a695d
--- /dev/null
+++ b/include/uapi/linux/device-evidence.h
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/device-evidence.yaml */
+/* YNL-GEN uapi header */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#ifndef _UAPI_LINUX_DEVICE_EVIDENCE_H
+#define _UAPI_LINUX_DEVICE_EVIDENCE_H
+
+#define DEVICE_EVIDENCE_FAMILY_NAME "device-evidence"
+#define DEVICE_EVIDENCE_FAMILY_VERSION 1
+
+#define DEVICE_EVIDENCE_MAX_OBJECT_SIZE 16777216
+#define DEVICE_EVIDENCE_MAX_NONCE_SIZE 32
+
+/**
+ * enum device_evidence_type - Device security evidence objects
+ * @DEVICE_EVIDENCE_TYPE_CERT0: SPDM certificate chain from device slot0
+ * @DEVICE_EVIDENCE_TYPE_CERT1: SPDM certificate chain from device slot1
+ * @DEVICE_EVIDENCE_TYPE_CERT2: SPDM certificate chain from device slot2
+ * @DEVICE_EVIDENCE_TYPE_CERT3: SPDM certificate chain from device slot3
+ * @DEVICE_EVIDENCE_TYPE_CERT4: SPDM certificate chain from device slot4
+ * @DEVICE_EVIDENCE_TYPE_CERT5: SPDM certificate chain from device slot5
+ * @DEVICE_EVIDENCE_TYPE_CERT6: SPDM certificate chain from device slot6
+ * @DEVICE_EVIDENCE_TYPE_CERT7: SPDM certificate chain from device slot7
+ * @DEVICE_EVIDENCE_TYPE_VCA: SPDM version, capabilities, and algorithms
+ * transcript negotiated at session establishment. An implementation may not
+ * provide this separately and instead include it in the measurements
+ * transcript.
+ * @DEVICE_EVIDENCE_TYPE_MEASUREMENTS: SPDM GET_MEASUREMENTS response
+ * @DEVICE_EVIDENCE_TYPE_REPORT: A bus that implements a device interface
+ * security protocol like TDISP may convey an interface report that details
+ * interface settings and capabilities.
+ */
+enum device_evidence_type {
+ DEVICE_EVIDENCE_TYPE_CERT0,
+ DEVICE_EVIDENCE_TYPE_CERT1,
+ DEVICE_EVIDENCE_TYPE_CERT2,
+ DEVICE_EVIDENCE_TYPE_CERT3,
+ DEVICE_EVIDENCE_TYPE_CERT4,
+ DEVICE_EVIDENCE_TYPE_CERT5,
+ DEVICE_EVIDENCE_TYPE_CERT6,
+ DEVICE_EVIDENCE_TYPE_CERT7,
+ DEVICE_EVIDENCE_TYPE_VCA,
+ DEVICE_EVIDENCE_TYPE_MEASUREMENTS,
+ DEVICE_EVIDENCE_TYPE_REPORT,
+
+ /* private: */
+ __DEVICE_EVIDENCE_TYPE_MAX,
+ DEVICE_EVIDENCE_TYPE_MAX = (__DEVICE_EVIDENCE_TYPE_MAX - 1)
+};
+
+/*
+ * Device security evidence request flags
+ */
+enum device_evidence_type_flag {
+ DEVICE_EVIDENCE_TYPE_FLAG_CERT0 = 1,
+ DEVICE_EVIDENCE_TYPE_FLAG_CERT1 = 2,
+ DEVICE_EVIDENCE_TYPE_FLAG_CERT2 = 4,
+ DEVICE_EVIDENCE_TYPE_FLAG_CERT3 = 8,
+ DEVICE_EVIDENCE_TYPE_FLAG_CERT4 = 16,
+ DEVICE_EVIDENCE_TYPE_FLAG_CERT5 = 32,
+ DEVICE_EVIDENCE_TYPE_FLAG_CERT6 = 64,
+ DEVICE_EVIDENCE_TYPE_FLAG_CERT7 = 128,
+ DEVICE_EVIDENCE_TYPE_FLAG_VCA = 256,
+ DEVICE_EVIDENCE_TYPE_FLAG_MEASUREMENTS = 512,
+ DEVICE_EVIDENCE_TYPE_FLAG_REPORT = 1024,
+
+ /* private: */
+ DEVICE_EVIDENCE_TYPE_FLAG_MASK = 2047,
+};
+
+/**
+ * enum device_evidence_flag - Flags to control evidence retrieval
+ * @DEVICE_EVIDENCE_FLAG_DIGEST: Request a secure hash of objects like vca and
+ * measurements. The expectation is that this digest is produced by a
+ * responder within the TCB like a platform TSM. It validates a blob that may
+ * have traversed a transport without integrity protections.
+ */
+enum device_evidence_flag {
+ DEVICE_EVIDENCE_FLAG_DIGEST = 1,
+
+ /* private: */
+ DEVICE_EVIDENCE_FLAG_MASK = 1,
+};
+
+enum {
+ DEVICE_EVIDENCE_A_OBJECT_TYPE = 1,
+ DEVICE_EVIDENCE_A_OBJECT_TYPE_MASK,
+ DEVICE_EVIDENCE_A_OBJECT_FLAGS,
+ DEVICE_EVIDENCE_A_OBJECT_SUBSYS,
+ DEVICE_EVIDENCE_A_OBJECT_DEV_NAME,
+ DEVICE_EVIDENCE_A_OBJECT_NONCE,
+ DEVICE_EVIDENCE_A_OBJECT_GENERATION,
+ DEVICE_EVIDENCE_A_OBJECT_COUNT,
+ DEVICE_EVIDENCE_A_OBJECT_LENGTH,
+ DEVICE_EVIDENCE_A_OBJECT_VAL,
+
+ __DEVICE_EVIDENCE_A_OBJECT_MAX,
+ DEVICE_EVIDENCE_A_OBJECT_MAX = (__DEVICE_EVIDENCE_A_OBJECT_MAX - 1)
+};
+
+enum {
+ DEVICE_EVIDENCE_CMD_READ = 1,
+
+ __DEVICE_EVIDENCE_CMD_MAX,
+ DEVICE_EVIDENCE_CMD_MAX = (__DEVICE_EVIDENCE_CMD_MAX - 1)
+};
+
+#endif /* _UAPI_LINUX_DEVICE_EVIDENCE_H */
diff --git a/drivers/base/device-evidence-netlink.c b/drivers/base/device-evidence-netlink.c
new file mode 100644
index 000000000000..3a739601f107
--- /dev/null
+++ b/drivers/base/device-evidence-netlink.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/device-evidence.yaml */
+/* YNL-GEN kernel source */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include "device-evidence-netlink.h"
+
+#include <uapi/linux/device-evidence.h>
+
+/* DEVICE_EVIDENCE_CMD_READ - dump */
+static const struct nla_policy device_evidence_read_nl_policy[DEVICE_EVIDENCE_A_OBJECT_NONCE + 1] = {
+ [DEVICE_EVIDENCE_A_OBJECT_TYPE_MASK] = { .type = NLA_U32, },
+ [DEVICE_EVIDENCE_A_OBJECT_FLAGS] = { .type = NLA_U32, },
+ [DEVICE_EVIDENCE_A_OBJECT_SUBSYS] = { .type = NLA_NUL_STRING, },
+ [DEVICE_EVIDENCE_A_OBJECT_DEV_NAME] = { .type = NLA_NUL_STRING, },
+ [DEVICE_EVIDENCE_A_OBJECT_NONCE] = NLA_POLICY_MAX_LEN(DEVICE_EVIDENCE_MAX_NONCE_SIZE),
+};
+
+/* Ops table for device_evidence */
+static const struct genl_split_ops device_evidence_nl_ops[] = {
+ {
+ .cmd = DEVICE_EVIDENCE_CMD_READ,
+ .start = device_evidence_nl_read_pre,
+ .dumpit = device_evidence_nl_read_dumpit,
+ .done = device_evidence_nl_read_post,
+ .policy = device_evidence_read_nl_policy,
+ .maxattr = DEVICE_EVIDENCE_A_OBJECT_NONCE,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP,
+ },
+};
+
+struct genl_family device_evidence_nl_family __ro_after_init = {
+ .name = DEVICE_EVIDENCE_FAMILY_NAME,
+ .version = DEVICE_EVIDENCE_FAMILY_VERSION,
+ .netnsok = true,
+ .parallel_ops = true,
+ .module = THIS_MODULE,
+ .split_ops = device_evidence_nl_ops,
+ .n_split_ops = ARRAY_SIZE(device_evidence_nl_ops),
+};
diff --git a/drivers/base/evidence.c b/drivers/base/evidence.c
new file mode 100644
index 000000000000..8e019669db07
--- /dev/null
+++ b/drivers/base/evidence.c
@@ -0,0 +1,453 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2026 NVIDIA Corporation & Affiliates */
+
+#include <crypto/hash_info.h>
+#include <linux/cleanup.h>
+#include <linux/device/evidence.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/export.h>
+#include <linux/find.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/netlink.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <net/genetlink.h>
+#include <net/netlink.h>
+#include "device-evidence-netlink.h"
+
+#define DEVICE_EVIDENCE_NAME_LEN 32
+#define DEVICE_EVIDENCE_START U32_MAX
+#define DEVICE_EVIDENCE_OBJECT_START (U32_MAX - 1)
+
+struct device_evidence_subsys {
+ const struct device_evidence_ops *ops;
+ struct list_head list;
+ atomic_t busy;
+};
+
+static LIST_HEAD(device_evidence_subsystems);
+static DEFINE_MUTEX(device_evidence_lock);
+static DECLARE_WAIT_QUEUE_HEAD(device_evidence_waitqueue);
+
+struct device_evidence_ctx {
+ struct device_evidence_subsys *subsys;
+ struct device *dev;
+ void *nonce;
+ u64 generation;
+ u32 type_mask;
+ u32 flags;
+ u32 offset;
+ u16 nonce_len;
+};
+
+struct device_evidence *device_evidence_create(int slot,
+ enum hash_algo digest_algo)
+{
+ struct device_evidence *evidence = kzalloc_obj(*evidence);
+
+ if (!evidence)
+ return NULL;
+
+ evidence->slot = slot;
+ evidence->generation = 1;
+ evidence->digest_algo = digest_algo;
+ init_rwsem(&evidence->lock);
+ return evidence;
+}
+EXPORT_SYMBOL_GPL(device_evidence_create);
+
+int device_evidence_register(const struct device_evidence_ops *ops)
+{
+ struct device_evidence_subsys *subsys;
+
+ if (!ops || !ops->subsys_name || !ops->find_device ||
+ !ops->evidence_read_begin || !ops->evidence_read_end)
+ return -EINVAL;
+
+ struct device_evidence_subsys *new_subsys __free(kfree) =
+ kzalloc_obj(*new_subsys);
+ if (!new_subsys)
+ return -ENOMEM;
+
+ guard(mutex)(&device_evidence_lock);
+ list_for_each_entry(subsys, &device_evidence_subsystems, list)
+ if (subsys->ops == ops)
+ return -EEXIST;
+
+ new_subsys->ops = ops;
+ list_add_tail(&no_free_ptr(new_subsys)->list, &device_evidence_subsystems);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(device_evidence_register);
+
+static struct device_evidence_subsys *
+block_subsys(const struct device_evidence_ops *ops)
+{
+ struct device_evidence_subsys *subsys;
+
+ /* trusts callers do not re-register @ops while awaiting unregistration */
+ guard(mutex)(&device_evidence_lock);
+ list_for_each_entry(subsys, &device_evidence_subsystems, list) {
+ if (subsys->ops == ops) {
+ list_del(&subsys->list);
+ return subsys;
+ }
+ }
+ return NULL;
+}
+
+void device_evidence_unregister(const struct device_evidence_ops *ops)
+{
+ /* stop new requests */
+ struct device_evidence_subsys *subsys = block_subsys(ops);
+
+ if (!subsys)
+ return;
+
+ /* flush all usage of @ops */
+ wait_event(device_evidence_waitqueue, atomic_read(&subsys->busy) == 0);
+ kfree(subsys);
+}
+EXPORT_SYMBOL_GPL(device_evidence_unregister);
+
+static struct device_evidence_subsys *
+device_evidence_subsys(const char *subsys_name)
+{
+ struct device_evidence_subsys *subsys;
+
+ list_for_each_entry(subsys, &device_evidence_subsystems, list) {
+ const struct device_evidence_ops *ops = subsys->ops;
+
+ if (!strcmp(subsys_name, ops->subsys_name))
+ return subsys;
+ }
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
+static void device_evidence_ctx_teardown(struct device_evidence_ctx *ctx)
+{
+ put_device(ctx->dev);
+ kfree(ctx->nonce);
+}
+
+DEFINE_FREE(put_ctx, struct device_evidence_ctx *,
+ if (!IS_ERR_OR_NULL(_T)) device_evidence_ctx_teardown(_T))
+
+static struct device_evidence_ctx *
+device_evidence_ctx_setup(const struct genl_info *info,
+ struct device_evidence_ctx *ctx)
+{
+ char subsys[DEVICE_EVIDENCE_NAME_LEN];
+ char name[DEVICE_EVIDENCE_NAME_LEN];
+ struct nlattr *attr;
+
+ *ctx = (struct device_evidence_ctx) { };
+
+ if (GENL_REQ_ATTR_CHECK(info, DEVICE_EVIDENCE_A_OBJECT_SUBSYS)) {
+ NL_SET_ERR_MSG(info->extack, "missing subsys name");
+ return ERR_PTR(-EINVAL);
+ }
+
+ attr = info->attrs[DEVICE_EVIDENCE_A_OBJECT_SUBSYS];
+ if (nla_strscpy(subsys, attr, sizeof(subsys)) < 0) {
+ NL_SET_BAD_ATTR(info->extack, attr);
+ return ERR_PTR(-EINVAL);
+ }
+
+ if (GENL_REQ_ATTR_CHECK(info, DEVICE_EVIDENCE_A_OBJECT_DEV_NAME)) {
+ NL_SET_ERR_MSG(info->extack, "missing device name");
+ return ERR_PTR(-EINVAL);
+ }
+
+ attr = info->attrs[DEVICE_EVIDENCE_A_OBJECT_DEV_NAME];
+ if (nla_strscpy(name, attr, sizeof(name)) < 0) {
+ NL_SET_BAD_ATTR(info->extack, attr);
+ return ERR_PTR(-EINVAL);
+ }
+
+ ctx->subsys = device_evidence_subsys(subsys);
+ if (IS_ERR(ctx->subsys)) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "no evidence provider for subsys '%s'", subsys);
+ return ERR_CAST(ctx->subsys);
+ }
+
+ ctx->dev = ctx->subsys->ops->find_device(name);
+ if (!ctx->dev) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "device '%s:%s' evidence not found", subsys,
+ name);
+ return ERR_PTR(-ENODEV);
+ }
+
+ return ctx;
+}
+
+static struct device_evidence_ctx *to_ctx(struct netlink_callback *cb)
+{
+ return (struct device_evidence_ctx *)cb->ctx;
+}
+
+int device_evidence_nl_read_pre(struct netlink_callback *cb)
+{
+ const struct genl_info *info = genl_info_dump(cb);
+ struct nlattr *attr;
+ u32 unknown_types;
+ int rc;
+
+ NL_ASSERT_CTX_FITS(struct device_evidence_ctx);
+
+ ACQUIRE(mutex_intr, lock)(&device_evidence_lock);
+ if ((rc = ACQUIRE_ERR(mutex_intr, &lock)))
+ return rc;
+
+ struct device_evidence_ctx *ctx __free(put_ctx) =
+ device_evidence_ctx_setup(info, to_ctx(cb));
+ if (IS_ERR(ctx))
+ return PTR_ERR(ctx);
+
+ if (GENL_REQ_ATTR_CHECK(info,
+ DEVICE_EVIDENCE_A_OBJECT_TYPE_MASK)) {
+ NL_SET_ERR_MSG(info->extack, "missing object request mask");
+ return -EINVAL;
+ }
+
+ attr = info->attrs[DEVICE_EVIDENCE_A_OBJECT_TYPE_MASK];
+ ctx->type_mask = nla_get_u32(attr);
+ unknown_types = ctx->type_mask & ~DEVICE_EVIDENCE_TYPE_FLAG_MASK;
+ if (unknown_types) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "unsupported object request %#x",
+ unknown_types);
+ return -EINVAL;
+ }
+
+ if (!ctx->type_mask) {
+ NL_SET_ERR_MSG(info->extack, "no evidence type requested");
+ return -EINVAL;
+ }
+
+ attr = info->attrs[DEVICE_EVIDENCE_A_OBJECT_FLAGS];
+ if (attr) {
+ ctx->flags = nla_get_u32(attr);
+ if (ctx->flags & ~DEVICE_EVIDENCE_FLAG_MASK) {
+ NL_SET_BAD_ATTR(info->extack, attr);
+ return -EINVAL;
+ }
+ }
+
+ attr = info->attrs[DEVICE_EVIDENCE_A_OBJECT_NONCE];
+ if (attr) {
+ ctx->nonce = nla_memdup(attr, GFP_KERNEL);
+ if (!ctx->nonce) {
+ NL_SET_BAD_ATTR(info->extack, attr);
+ return -ENOMEM;
+ }
+ ctx->nonce_len = nla_len(attr);
+ }
+
+ ctx->offset = DEVICE_EVIDENCE_START;
+ atomic_inc(&ctx->subsys->busy);
+ retain_and_null_ptr(ctx);
+
+ return 0;
+}
+
+int device_evidence_nl_read_post(struct netlink_callback *cb)
+{
+ struct device_evidence_ctx *ctx = to_ctx(cb);
+
+ device_evidence_ctx_teardown(ctx);
+
+ guard(mutex)(&device_evidence_lock);
+ if (atomic_dec_and_test(&ctx->subsys->busy))
+ wake_up_all(&device_evidence_waitqueue);
+
+ return 0;
+}
+
+static size_t evidence_len(struct device_evidence *evidence,
+ struct device_evidence_object *obj,
+ unsigned long flags)
+{
+ if (flags & DEVICE_EVIDENCE_FLAG_DIGEST) {
+ if (obj->digest)
+ return hash_digest_size[evidence->digest_algo];
+ return 0;
+ }
+ return obj->len;
+}
+
+static void *evidence_data(struct device_evidence_object *obj,
+ unsigned long flags)
+{
+ if (flags & DEVICE_EVIDENCE_FLAG_DIGEST)
+ return obj->digest;
+ return obj->data;
+}
+
+static int current_type(struct device_evidence_ctx *ctx)
+{
+ return __ffs(ctx->type_mask);
+}
+
+static struct device_evidence_object *
+current_obj(struct device_evidence_ctx *ctx, struct device_evidence *evidence)
+{
+ return &evidence->obj[current_type(ctx)];
+}
+
+static int __device_evidence_read(struct sk_buff *skb,
+ struct netlink_callback *cb,
+ struct device_evidence *evidence)
+{
+ struct device_evidence_ctx *ctx = (struct device_evidence_ctx *)cb->ctx;
+ struct device_evidence_object *obj = current_obj(ctx, evidence);
+ size_t object_len = evidence_len(evidence, obj, ctx->flags);
+ void *object_data = evidence_data(obj, ctx->flags);
+ size_t available, overhead, len;
+ void *hdr;
+ void *val;
+ int rc;
+
+ hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
+ &device_evidence_nl_family, NLM_F_MULTI,
+ DEVICE_EVIDENCE_CMD_READ);
+ if (!hdr)
+ return -EMSGSIZE;
+
+ if (ctx->offset == DEVICE_EVIDENCE_OBJECT_START) {
+ if (nla_put_u32(skb, DEVICE_EVIDENCE_A_OBJECT_TYPE,
+ current_type(ctx)) ||
+ nla_put_u32(skb, DEVICE_EVIDENCE_A_OBJECT_GENERATION,
+ ctx->generation) ||
+ nla_put_u32(skb, DEVICE_EVIDENCE_A_OBJECT_LENGTH,
+ object_len))
+ goto out_cancel;
+
+ ctx->offset = 0;
+ }
+
+ available = skb_tailroom(skb);
+ overhead = nla_total_size(0) + NLA_ALIGNTO;
+ if (available <= overhead) {
+ rc = -EMSGSIZE;
+ goto out_cancel;
+ }
+
+ if (object_len)
+ len = min(available - overhead, object_len - ctx->offset);
+ else
+ len = 0;
+
+ val = len ? object_data + ctx->offset : NULL;
+ rc = nla_put(skb, DEVICE_EVIDENCE_A_OBJECT_VAL, len, val);
+ if (rc)
+ goto out_end;
+
+ ctx->offset += len;
+ if (ctx->offset < object_len) {
+ rc = 1;
+ goto out_end;
+ }
+
+ /* Move to the next bit in the request mask */
+ ctx->type_mask ^= 1U << current_type(ctx);
+
+ /* no more evidence types requested */
+ if (!ctx->type_mask) {
+ rc = 0;
+ goto out_end;
+ }
+ ctx->offset = DEVICE_EVIDENCE_OBJECT_START;
+ rc = 1;
+
+out_end:
+ genlmsg_end(skb, hdr);
+ if (rc > 0)
+ return skb->len;
+ return rc;
+
+out_cancel:
+ genlmsg_cancel(skb, hdr);
+ return -EMSGSIZE;
+}
+
+static int device_evidence_read(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ struct device_evidence_ctx *ctx = (struct device_evidence_ctx *)cb->ctx;
+ const struct device_evidence_ops *ops = ctx->subsys->ops;
+ const struct genl_info *info = genl_info_dump(cb);
+ struct device_evidence *evidence;
+ int rc;
+
+ /* Sync against provider removing device evidence */
+ evidence = ops->evidence_read_begin(ctx->dev);
+ if (IS_ERR(evidence)) {
+ NL_SET_ERR_MSG(info->extack,
+ "failed to acquire evidence context");
+ return PTR_ERR(evidence);
+ }
+
+ /* Check that evidence stays consistent over multi-message dumps */
+ if (ctx->offset == DEVICE_EVIDENCE_START) {
+ ctx->generation = evidence->generation;
+ ctx->offset = DEVICE_EVIDENCE_OBJECT_START;
+ }
+
+ if (ctx->generation == evidence->generation)
+ rc = __device_evidence_read(skb, cb, evidence);
+ else {
+ NL_SET_ERR_MSG(info->extack, "evidence updated during read");
+ rc = -EAGAIN;
+ }
+
+ ops->evidence_read_end(evidence);
+ return rc;
+}
+
+static int device_evidence_refresh(struct device_evidence_ctx *ctx)
+{
+ const struct device_evidence_ops *ops = ctx->subsys->ops;
+
+ if (!ops->refresh_evidence)
+ return -EOPNOTSUPP;
+
+ return ops->refresh_evidence(ctx->dev, ctx->nonce, ctx->nonce_len);
+}
+
+int device_evidence_nl_read_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ struct device_evidence_ctx *ctx = (struct device_evidence_ctx *)cb->ctx;
+ const struct genl_info *info = genl_info_dump(cb);
+
+ /*
+ * When a nonce is provided, refresh the dynamic evidence, if
+ * specified by @ctx, before the dump operation.
+ */
+ if (ctx->offset == DEVICE_EVIDENCE_START && ctx->nonce) {
+ int rc = device_evidence_refresh(ctx);
+
+ if (rc) {
+ NL_SET_ERR_MSG_FMT(info->extack,
+ "evidence refresh failed: %pe", ERR_PTR(rc));
+ return rc;
+ }
+ kfree(ctx->nonce);
+ ctx->nonce = NULL;
+ }
+ return device_evidence_read(skb, cb);
+}
+
+static int __init device_evidence_nl_init(void)
+{
+ return genl_register_family(&device_evidence_nl_family);
+}
+subsys_initcall(device_evidence_nl_init);
diff --git a/MAINTAINERS b/MAINTAINERS
index 15011f5752a9..cb4f74957f69 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27470,8 +27470,12 @@ S: Maintained
F: Documentation/ABI/testing/configfs-tsm-report
F: Documentation/driver-api/coco/
F: Documentation/driver-api/pci/tsm.rst
+F: Documentation/netlink/specs/device-evidence.yaml
+F: drivers/base/*evidence*
F: drivers/pci/tsm.c
F: drivers/virt/coco/guest/
+F: include/uapi/linux/device-evidence.h
+F: include/linux/device/evidence.h
F: include/linux/*tsm*.h
F: samples/tsm-mr/
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 05/15] device core: Add "device evidence" 'validate' command
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
` (3 preceding siblings ...)
2026-07-05 22:08 ` [PATCH 04/15] device core: Introduce "device evidence" over netlink Dan Williams
@ 2026-07-05 22:08 ` Dan Williams
2026-07-05 22:08 ` [PATCH 06/15] PCI/TSM: Add device evidence support Dan Williams
` (10 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-pci, driver-core, ankita, Alistair Francis, Lukas Wunner,
Donald Hunter, Greg Kroah-Hartman, Rafael J. Wysocki,
Danilo Krummrich, Aneesh Kumar K.V (Arm)
When DMA is to be enabled for a PCI TDISP device the CC VM passes a digest
of the device evidence used to make the trust decision. Require the CC VM
to ack and not invalidate the evidence before the next DMA enable event.
Each evidence gathering conveys a current "generation" number that rolls
each time the evidence is requested to be refreshed by the CC VM. The
validate command is a debug aid setups that should not be racing evidence
gathering, device RUN state transitions and DMA enable events.
Note that this generation number does count host side events that
invalidate the evidence. That TOCTOU race belongs to the TSM to resolve and
tell the CC VM that the digests it used to enable DMA are stale.
Cc: Alistair Francis <alistair.francis@wdc.com>
Cc: Lukas Wunner <lukas@wunner.de>
Cc: Donald Hunter <donald.hunter@gmail.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: "Rafael J. Wysocki" <rafael@kernel.org>
Cc: Danilo Krummrich <dakr@kernel.org>
Suggested-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
Signed-off-by: Dan Williams <djbw@kernel.org>
---
.../netlink/specs/device-evidence.yaml | 17 ++++++++
drivers/base/device-evidence-netlink.h | 2 +
include/linux/device/evidence.h | 5 ++-
include/uapi/linux/device-evidence.h | 1 +
drivers/base/device-evidence-netlink.c | 14 +++++++
drivers/base/evidence.c | 42 +++++++++++++++++++
6 files changed, 80 insertions(+), 1 deletion(-)
diff --git a/Documentation/netlink/specs/device-evidence.yaml b/Documentation/netlink/specs/device-evidence.yaml
index 44b278bc20e1..65ed0ed87bba 100644
--- a/Documentation/netlink/specs/device-evidence.yaml
+++ b/Documentation/netlink/specs/device-evidence.yaml
@@ -184,3 +184,20 @@ operations:
- generation
- length
- val
+ -
+ name: validate
+ doc: |
+ Assert that userspace accepts the evidence at 'generation' (e.g. as a
+ precondition for enabling DMA). If 'generation' matches the device's
+ current evidence generation the acceptance is recorded; it is
+ implicitly revoked when the evidence is regenerated.
+ attribute-set: object
+ flags: [admin-perm]
+ do:
+ request:
+ attributes:
+ - subsys
+ - dev-name
+ - generation
+ reply:
+ attributes: []
diff --git a/drivers/base/device-evidence-netlink.h b/drivers/base/device-evidence-netlink.h
index a7a3110630a6..659a0a2c9c49 100644
--- a/drivers/base/device-evidence-netlink.h
+++ b/drivers/base/device-evidence-netlink.h
@@ -17,6 +17,8 @@ int device_evidence_nl_read_post(struct netlink_callback *cb);
int device_evidence_nl_read_dumpit(struct sk_buff *skb,
struct netlink_callback *cb);
+int device_evidence_nl_validate_doit(struct sk_buff *skb,
+ struct genl_info *info);
extern struct genl_family device_evidence_nl_family;
diff --git a/include/linux/device/evidence.h b/include/linux/device/evidence.h
index 5036fbe63e30..eea1bda609b0 100644
--- a/include/linux/device/evidence.h
+++ b/include/linux/device/evidence.h
@@ -29,15 +29,18 @@ struct device_evidence_object {
* struct device_evidence - Retrieved device evidence
* @slot: certificate slot used by a link TSM for connect
* @generation: refresh_evidence() invocation detection
+ * @validated_generation: generation last accepted by userspace via "validate"
* @digest_algo: payload size of DEVICE_EVIDENCE_FLAG_DIGEST requests
* @lock: synchronize dumps vs refresh_evidence()
* @obj: array of evidence objects a TSM might populate
*
- * An increment of @generation causes in flight dumps to fail with -EAGAIN.
+ * An increment of @generation causes in flight dumps to fail with -EAGAIN and
+ * implicitly revokes a prior userspace validation.
*/
struct device_evidence {
int slot;
u32 generation;
+ u32 validated_generation;
enum hash_algo digest_algo;
struct rw_semaphore lock;
struct device_evidence_object obj[DEVICE_EVIDENCE_TYPE_MAX + 1];
diff --git a/include/uapi/linux/device-evidence.h b/include/uapi/linux/device-evidence.h
index b184174a695d..180d45fdf6cf 100644
--- a/include/uapi/linux/device-evidence.h
+++ b/include/uapi/linux/device-evidence.h
@@ -102,6 +102,7 @@ enum {
enum {
DEVICE_EVIDENCE_CMD_READ = 1,
+ DEVICE_EVIDENCE_CMD_VALIDATE,
__DEVICE_EVIDENCE_CMD_MAX,
DEVICE_EVIDENCE_CMD_MAX = (__DEVICE_EVIDENCE_CMD_MAX - 1)
diff --git a/drivers/base/device-evidence-netlink.c b/drivers/base/device-evidence-netlink.c
index 3a739601f107..3148fc523ed1 100644
--- a/drivers/base/device-evidence-netlink.c
+++ b/drivers/base/device-evidence-netlink.c
@@ -20,6 +20,13 @@ static const struct nla_policy device_evidence_read_nl_policy[DEVICE_EVIDENCE_A_
[DEVICE_EVIDENCE_A_OBJECT_NONCE] = NLA_POLICY_MAX_LEN(DEVICE_EVIDENCE_MAX_NONCE_SIZE),
};
+/* DEVICE_EVIDENCE_CMD_VALIDATE - do */
+static const struct nla_policy device_evidence_validate_nl_policy[DEVICE_EVIDENCE_A_OBJECT_GENERATION + 1] = {
+ [DEVICE_EVIDENCE_A_OBJECT_SUBSYS] = { .type = NLA_NUL_STRING, },
+ [DEVICE_EVIDENCE_A_OBJECT_DEV_NAME] = { .type = NLA_NUL_STRING, },
+ [DEVICE_EVIDENCE_A_OBJECT_GENERATION] = { .type = NLA_U32, },
+};
+
/* Ops table for device_evidence */
static const struct genl_split_ops device_evidence_nl_ops[] = {
{
@@ -31,6 +38,13 @@ static const struct genl_split_ops device_evidence_nl_ops[] = {
.maxattr = DEVICE_EVIDENCE_A_OBJECT_NONCE,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP,
},
+ {
+ .cmd = DEVICE_EVIDENCE_CMD_VALIDATE,
+ .doit = device_evidence_nl_validate_doit,
+ .policy = device_evidence_validate_nl_policy,
+ .maxattr = DEVICE_EVIDENCE_A_OBJECT_GENERATION,
+ .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+ },
};
struct genl_family device_evidence_nl_family __ro_after_init = {
diff --git a/drivers/base/evidence.c b/drivers/base/evidence.c
index 8e019669db07..3a7942b2362a 100644
--- a/drivers/base/evidence.c
+++ b/drivers/base/evidence.c
@@ -446,6 +446,48 @@ int device_evidence_nl_read_dumpit(struct sk_buff *skb,
return device_evidence_read(skb, cb);
}
+int device_evidence_nl_validate_doit(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ const struct device_evidence_ops *ops;
+ struct device_evidence *evidence;
+ struct device_evidence_ctx _ctx;
+ u32 generation;
+ int rc;
+
+ if (GENL_REQ_ATTR_CHECK(info, DEVICE_EVIDENCE_A_OBJECT_GENERATION)) {
+ NL_SET_ERR_MSG(info->extack, "missing evidence generation");
+ return -EINVAL;
+ }
+ generation = nla_get_u32(info->attrs[DEVICE_EVIDENCE_A_OBJECT_GENERATION]);
+
+ ACQUIRE(mutex_intr, lock)(&device_evidence_lock);
+ if ((rc = ACQUIRE_ERR(mutex_intr, &lock)))
+ return rc;
+
+ struct device_evidence_ctx *ctx __free(put_ctx) =
+ device_evidence_ctx_setup(info, &_ctx);
+ if (IS_ERR(ctx))
+ return PTR_ERR(ctx);
+
+ ops = ctx->subsys->ops;
+ evidence = ops->evidence_read_begin(ctx->dev);
+ if (IS_ERR(evidence)) {
+ NL_SET_ERR_MSG(info->extack, "failed to acquire evidence context");
+ return PTR_ERR(evidence);
+ }
+
+ if (generation == evidence->generation) {
+ evidence->validated_generation = evidence->generation;
+ } else {
+ NL_SET_ERR_MSG(info->extack, "evidence generation is stale");
+ rc = -ESTALE;
+ }
+
+ ops->evidence_read_end(evidence);
+ return rc;
+}
+
static int __init device_evidence_nl_init(void)
{
return genl_register_family(&device_evidence_nl_family);
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 06/15] PCI/TSM: Add device evidence support
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
` (4 preceding siblings ...)
2026-07-05 22:08 ` [PATCH 05/15] device core: Add "device evidence" 'validate' command Dan Williams
@ 2026-07-05 22:08 ` Dan Williams
2026-07-05 22:08 ` [PATCH 07/15] modules: Document the global async_probe parameter Dan Williams
` (9 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-pci, driver-core, ankita, Bjorn Helgaas, Alistair Francis,
Lukas Wunner, Xu Yilun, Aneesh Kumar K.V (Arm),
Alexey Kardashevskiy
Register the PCI Trusted Execution Environment Security Manager (TSM)
framework with the device-evidence netlink ABI. The security operations
that PCI core coordinates with a device security manager (DSM) through a
platform TEE security manager (TSM), builds upon the SPDM protocol.
A TSM owns an SPDM session and publishes the corresponding evidence through
TSM firwmware ABIs. A low level TSM driver is responsible for creating a
'struct device_evidence' context and carrying out refresh_evidence()
requests for regenerating measurement transcripts with a nonce.
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Alistair Francis <alistair.francis@wdc.com>
Cc: Lukas Wunner <lukas@wunner.de>
Cc: Xu Yilun <yilun.xu@linux.intel.com>
Cc: "Aneesh Kumar K.V (Arm)" <aneesh.kumar@kernel.org>
Cc: Alexey Kardashevskiy <aik@amd.com>
Signed-off-by: Dan Williams <djbw@kernel.org>
---
drivers/pci/Kconfig | 1 +
drivers/pci/Makefile | 2 +-
drivers/pci/tsm/Makefile | 8 +++
include/linux/pci-tsm.h | 14 ++++
drivers/pci/{tsm.c => tsm/core.c} | 9 +--
drivers/pci/tsm/evidence.c | 110 ++++++++++++++++++++++++++++++
MAINTAINERS | 2 +-
7 files changed, 137 insertions(+), 9 deletions(-)
create mode 100644 drivers/pci/tsm/Makefile
rename drivers/pci/{tsm.c => tsm/core.c} (99%)
create mode 100644 drivers/pci/tsm/evidence.c
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 0c7408509ba2..ed17b5d2d5ae 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -126,6 +126,7 @@ config PCI_IDE
config PCI_TSM
bool "PCI TSM: Device security protocol support"
+ select DEVICE_EVIDENCE if NET
select PCI_IDE
select PCI_DOE
select TSM
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 41ebc3b9a518..211f195ff2c9 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -35,7 +35,7 @@ obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o
obj-$(CONFIG_VGA_ARB) += vgaarb.o
obj-$(CONFIG_PCI_DOE) += doe.o
obj-$(CONFIG_PCI_IDE) += ide.o
-obj-$(CONFIG_PCI_TSM) += tsm.o
+obj-$(CONFIG_PCI_TSM) += tsm/
obj-$(CONFIG_PCI_DYNAMIC_OF_NODES) += of_property.o
obj-$(CONFIG_PCI_NPEM) += npem.o
obj-$(CONFIG_PCIE_TPH) += tph.o
diff --git a/drivers/pci/tsm/Makefile b/drivers/pci/tsm/Makefile
new file mode 100644
index 000000000000..fd7ac2e862f1
--- /dev/null
+++ b/drivers/pci/tsm/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the PCI/TSM infrastructure
+
+obj-$(CONFIG_PCI_TSM) += tsm.o
+
+tsm-y := core.o
+tsm-$(CONFIG_DEVICE_EVIDENCE) += evidence.o
diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
index a6435aba03f9..8bc16029d31e 100644
--- a/include/linux/pci-tsm.h
+++ b/include/linux/pci-tsm.h
@@ -3,7 +3,10 @@
#define __PCI_TSM_H
#include <linux/mutex.h>
#include <linux/pci.h>
+#include <linux/rwsem.h>
#include <linux/sockptr.h>
+#include <linux/tsm.h>
+#include <linux/device/evidence.h>
struct pci_tsm;
struct tsm_dev;
@@ -18,6 +21,7 @@ enum pci_tsm_req_scope;
* @devsec_ops: Lock, unlock, and interrogate the security state of the
* function via the platform TSM (typically virtual function
* operations).
+ * @refresh_evidence: Common operation to regenerate attestation objects
*
* This operations are mutually exclusive either a tsm_dev instance
* manages physical link properties or it manages function security
@@ -75,6 +79,9 @@ struct pci_tsm_ops {
struct pci_dev *pdev);
void (*unlock)(struct pci_tsm *tsm);
);
+
+ int (*refresh_evidence)(struct pci_tsm *tsm, const void *nonce,
+ size_t nonce_len);
};
/**
@@ -96,6 +103,8 @@ struct pci_tdi {
* @tsm_dev: PCI TEE Security Manager device for Link Confidentiality or Device
* Function Security operations
* @tdi: TDI context established by the @bind link operation
+ * @evidence: cached evidence from SPDM session establishment (connect), or
+ * TDISP bind (lock)
*
* This structure is wrapped by low level TSM driver data and returned by
* probe()/lock(), it is freed by the corresponding remove()/unlock().
@@ -112,6 +121,7 @@ struct pci_tsm {
struct pci_dev *dsm_dev;
struct tsm_dev *tsm_dev;
struct pci_tdi *tdi;
+ struct device_evidence *evidence;
};
/**
@@ -216,6 +226,10 @@ void pci_tsm_tdi_constructor(struct pci_dev *pdev, struct pci_tdi *tdi,
ssize_t pci_tsm_guest_req(struct pci_dev *pdev, enum pci_tsm_req_scope scope,
sockptr_t req_in, size_t in_len, sockptr_t req_out,
size_t out_len, u64 *tsm_code);
+static inline const struct pci_tsm_ops *to_pci_tsm_ops(struct pci_tsm *tsm)
+{
+ return tsm->tsm_dev->pci_ops;
+}
#else
static inline int pci_tsm_register(struct tsm_dev *tsm_dev)
{
diff --git a/drivers/pci/tsm.c b/drivers/pci/tsm/core.c
similarity index 99%
rename from drivers/pci/tsm.c
rename to drivers/pci/tsm/core.c
index 5fdcd7f2e820..220842df42bc 100644
--- a/drivers/pci/tsm.c
+++ b/drivers/pci/tsm/core.c
@@ -15,13 +15,13 @@
#include <linux/sysfs.h>
#include <linux/tsm.h>
#include <linux/xarray.h>
-#include "pci.h"
+#include "../pci.h"
/*
* Provide a read/write lock against the init / exit of pdev tsm
* capabilities and arrival/departure of a TSM instance
*/
-static DECLARE_RWSEM(pci_tsm_rwsem);
+DECLARE_RWSEM(pci_tsm_rwsem);
/*
* Count of TSMs registered that support physical link operations vs device
@@ -30,11 +30,6 @@ static DECLARE_RWSEM(pci_tsm_rwsem);
static int pci_tsm_link_count;
static int pci_tsm_devsec_count;
-static const struct pci_tsm_ops *to_pci_tsm_ops(struct pci_tsm *tsm)
-{
- return tsm->tsm_dev->pci_ops;
-}
-
static inline bool is_dsm(struct pci_dev *pdev)
{
return pdev->tsm && pdev->tsm->dsm_dev == pdev;
diff --git a/drivers/pci/tsm/evidence.c b/drivers/pci/tsm/evidence.c
new file mode 100644
index 000000000000..ffb08208f0c0
--- /dev/null
+++ b/drivers/pci/tsm/evidence.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2026 NVIDIA Corporation & Affiliates */
+
+#include <linux/device/evidence.h>
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <linux/pci-tsm.h>
+#include <linux/tsm.h>
+
+extern struct rw_semaphore pci_tsm_rwsem;
+
+static bool evidence_available(struct pci_dev *pdev)
+{
+ return pdev->tsm && pdev->tsm->evidence;
+}
+
+static struct device *pci_tsm_evidence_find_device(const char *name)
+{
+ ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
+ if (ACQUIRE_ERR(rwsem_read_intr, &lock))
+ return NULL;
+
+ struct device *dev __free(put_device) =
+ bus_find_device_by_name(&pci_bus_type, NULL, name);
+
+ /*
+ * Bail evidence gathering early if we know at this point that
+ * the device has no valid evidence provider, but still need to
+ * revalidate the same in pci_tsm_evidence_read_begin().
+ */
+ if (!dev || !evidence_available(to_pci_dev(dev)))
+ return NULL;
+
+ return no_free_ptr(dev);
+}
+
+static struct device_evidence *pci_tsm_evidence_read_begin(struct device *dev)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct device_evidence *evidence;
+ int rc;
+
+ rc = down_read_interruptible(&pci_tsm_rwsem);
+ if (rc)
+ return ERR_PTR(rc);
+
+ if (!evidence_available(pdev))
+ goto err;
+
+ /* Hold the evidence stable against conflicting refresh updates */
+ evidence = pdev->tsm->evidence;
+ rc = down_read_interruptible(&evidence->lock);
+ if (rc)
+ goto err;
+
+ return evidence;
+err:
+ up_read(&pci_tsm_rwsem);
+ return ERR_PTR(-ENXIO);
+}
+
+static void pci_tsm_evidence_read_end(struct device_evidence *evidence)
+{
+ up_read(&evidence->lock);
+ up_read(&pci_tsm_rwsem);
+}
+
+static int pci_tsm_refresh_evidence(struct device *dev, const void *nonce,
+ size_t nonce_len)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct device_evidence *evidence;
+ const struct pci_tsm_ops *ops;
+ int rc;
+
+ /* Sync against disconnect */
+ ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
+ if ((rc = ACQUIRE_ERR(rwsem_read_intr, &lock)))
+ return rc;
+
+ if (!pdev->tsm)
+ return -ENXIO;
+
+ ops = to_pci_tsm_ops(pdev->tsm);
+ if (!ops->refresh_evidence)
+ return -EOPNOTSUPP;
+
+ /* Sync against pci_tsm_evidence_read_begin */
+ evidence = pdev->tsm->evidence;
+ ACQUIRE(rwsem_write_kill, elock)(&evidence->lock);
+ if ((rc = ACQUIRE_ERR(rwsem_write_kill, &elock)))
+ return rc;
+
+ return ops->refresh_evidence(pdev->tsm, nonce, nonce_len);
+}
+
+static const struct device_evidence_ops pci_tsm_evidence_ops = {
+ .subsys_name = "pci",
+ .find_device = pci_tsm_evidence_find_device,
+ .evidence_read_begin = pci_tsm_evidence_read_begin,
+ .evidence_read_end = pci_tsm_evidence_read_end,
+ .refresh_evidence = pci_tsm_refresh_evidence,
+};
+
+static int __init pci_tsm_evidence_init(void)
+{
+ return device_evidence_register(&pci_tsm_evidence_ops);
+}
+subsys_initcall(pci_tsm_evidence_init);
diff --git a/MAINTAINERS b/MAINTAINERS
index cb4f74957f69..6cefaddc1120 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27472,7 +27472,7 @@ F: Documentation/driver-api/coco/
F: Documentation/driver-api/pci/tsm.rst
F: Documentation/netlink/specs/device-evidence.yaml
F: drivers/base/*evidence*
-F: drivers/pci/tsm.c
+F: drivers/pci/tsm/
F: drivers/virt/coco/guest/
F: include/uapi/linux/device-evidence.h
F: include/linux/device/evidence.h
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 07/15] modules: Document the global async_probe parameter
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
` (5 preceding siblings ...)
2026-07-05 22:08 ` [PATCH 06/15] PCI/TSM: Add device evidence support Dan Williams
@ 2026-07-05 22:08 ` Dan Williams
2026-07-05 22:08 ` [PATCH 08/15] device core: Initial device trust infrastructure Dan Williams
` (8 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-pci, driver-core, ankita, Saravana Kannan, Luis Chamberlain
In preparation for adding another /sys/module/module/parameters entry,
document the existing async_probe parameter.
Cc: Saravana Kannan <saravanak@google.com>
Cc: Luis Chamberlain <mcgrof@kernel.org>
Signed-off-by: Dan Williams <djbw@kernel.org>
---
Documentation/ABI/stable/sysfs-module | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/Documentation/ABI/stable/sysfs-module b/Documentation/ABI/stable/sysfs-module
index 41b1f16e8795..397c5c850894 100644
--- a/Documentation/ABI/stable/sysfs-module
+++ b/Documentation/ABI/stable/sysfs-module
@@ -45,3 +45,13 @@ Date: Jun 2005
Description:
If the module source has MODULE_VERSION, this file will contain
the version of the source code.
+
+What: /sys/module/module/parameters/async_probe
+Description:
+ (RW) Emits "1" if drivers from loadable modules attempt async
+ probing by default. Emits "0" if drivers from loadable modules
+ attempt synchronous probing by default. This value is overridden
+ (in priority order) by: the module's built-in "PROBE_FORCE_*"
+ requests, the "driver_async_probe=..." kernel command line, the
+ "async_probe" module option, then this default. Write a valid
+ boolean value to toggle this policy.
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 08/15] device core: Initial device trust infrastructure
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
` (6 preceding siblings ...)
2026-07-05 22:08 ` [PATCH 07/15] modules: Document the global async_probe parameter Dan Williams
@ 2026-07-05 22:08 ` Dan Williams
2026-07-05 22:08 ` [PATCH 09/15] PCI, device core: Move "untrusted" concept to DEVICE_TRUST_ADVERSARY Dan Williams
` (7 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-pci, driver-core, ankita, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Luis Chamberlain, Petr Pavlu,
Daniel Gomez, Sami Tolvanen, Aaron Tomlin, Jason Gunthorpe,
Bjorn Helgaas, Samuel Ortiz, Alexey Kardashevskiy, Xu Yilun,
Aneesh Kumar K.V, Dexuan Cui
Introduce a per-device trust concept [1]. To start, it plumbs "auto" and
"none" levels to enable a scheme of requiring explicit opt-in to operate a
device. This supports a Confidential Computing use case of operating a
limited / vetted device and driver set to bootstrap the environment. It
leans on module policy to effect changes to device's trust level.
The motivations for using module policy to effect trust are:
1/ It exists, no new ABI needed to inject "device firewall" policy into the
kernel [2].
2/ Trust is a function of how the device is operated. A driver can be
trusted to carefully operate an untrusted device.
It may prove too coarse for some situations, like a driver that talks to a
mix of trusted and untrusted devices, or a module that contains multiple
drivers, but it is a place to start.
Specifically this "module trust" scheme allows for only attaching drivers
from built-in modules by default, or modules that explicitly pass a
"trust=auto" option by building with CONFIG_DEVICE_TRUST_NONE=y. Modules
can also be configured to skip device attach by default by setting
/sys/module/module/parameters/require_trust.
The new dev->p->trust level is the operational trust level of a device. It is
only in effect after device_add(). It is private to the device core because
the "tcb" level is not suitable to be changed while a driver is attached to
the device. The result force_dma_unencrypted() must be stable while the
device is attached to its driver. That core coordination responsibility is
not suitable to export to non-core code.
Note that the overly trivial helper builtin_driver_trust() grows support
for another trust level in a follow-on change.
Also note that the IOMMU claims in the Kconfig commentary, where it says
"possible" and "where available" imply follow-on work to assign
DEVICE_TRUST_NONE devices to an IOMMU blocked domain.
Link: http://lore.kernel.org/2026032621-astound-mounted-07a6@gregkh [1]
Link: https://lwn.net/Articles/865918/ [2]
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: "Rafael J. Wysocki" <rafael@kernel.org>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: Luis Chamberlain <mcgrof@kernel.org>
Cc: Petr Pavlu <petr.pavlu@suse.com>
Cc: Daniel Gomez <da.gomez@kernel.org>
Cc: Sami Tolvanen <samitolvanen@google.com>
Cc: Aaron Tomlin <atomlin@atomlin.com>
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Samuel Ortiz <sameo@rivosinc.com>
Cc: Alexey Kardashevskiy <aik@amd.com>
Cc: Xu Yilun <yilun.xu@linux.intel.com>
Cc: "Aneesh Kumar K.V" <aneesh.kumar@kernel.org>
Cc: Dexuan Cui <decui@microsoft.com>
Signed-off-by: Dan Williams <djbw@kernel.org>
---
drivers/base/Kconfig | 57 ++++++++++++++++++++
drivers/base/Makefile | 1 +
Documentation/ABI/stable/sysfs-module | 7 +++
drivers/base/base.h | 15 ++++++
include/linux/device/trust.h | 42 +++++++++++++++
include/linux/module.h | 6 ++-
drivers/base/core.c | 7 +++
drivers/base/dd.c | 5 ++
drivers/base/trust.c | 77 +++++++++++++++++++++++++++
kernel/module/main.c | 13 +++++
10 files changed, 229 insertions(+), 1 deletion(-)
create mode 100644 include/linux/device/trust.h
create mode 100644 drivers/base/trust.c
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index e3929fe6b240..a08523d348d8 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -262,4 +262,61 @@ config FW_DEVLINK_SYNC_STATE_TIMEOUT
command line option on every system/board your kernel is expected to
work on.
+config DEVICE_TRUST
+ bool "Device core support for specifying trust levels for devices"
+ help
+ Enable support for generic device trust levels when building a kernel
+ that needs to operate in the presence of potentially adversarial
+ devices. This is selected by buses that want to operate devices with
+ reduced privileges, like externally connected devices, and it is a
+ pre-requisite for operating devices with access to private memory in a
+ Confidential Computing VM. See
+ Documentation/ABI/testing/sysfs-kernel-iommu_groups for the type of
+ IOMMU enforcement in effect.
+
+choice
+ prompt "Default device trust"
+ default DEVICE_TRUST_AUTO
+ depends on DEVICE_TRUST
+ help
+ Specify the device trust level at initial attachment. Any choice other
+ than "Auto" assumes auditing the built-in driver set for trusted
+ drivers, and an enlightened userspace modprobe policy for other
+ devices + drivers.
+
+config DEVICE_TRUST_NONE
+ bool "None"
+ help
+ Devices are disallowed from attaching to a driver, and where
+ possible, the device is blocked by an IOMMU from accessing
+ assets.
+
+config DEVICE_TRUST_AUTO
+ bool "Auto"
+ help
+ Typical historical driver model, devices eagerly attempt to attach to
+ a driver and deploy all available mechanisms to allow performant
+ direct memory access.
+
+endchoice
+
+choice
+ prompt "Trust devices with built-in drivers"
+ default BUILTIN_DEVICE_TRUST_AUTO
+ depends on DEVICE_TRUST
+ help
+ Built-in drivers always bind to devices they match. Only
+ select a setting other than "Auto" when building a kernel image
+ targeted for use in Confidential Computing or other known
+ adversarial environments.
+
+config BUILTIN_DEVICE_TRUST_AUTO
+ bool "Auto"
+ help
+ Typical historical driver model, devices eagerly attempt to attach to
+ a driver and deploy all available mechanisms to allow performant
+ direct memory access This trust level does not grant TCB privileges.
+
+endchoice
+
endmenu
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index 02bdc4f74019..0744511c0599 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -8,6 +8,7 @@ obj-y := component.o core.o bus.o dd.o syscore.o \
topology.o container.o property.o cacheinfo.o \
swnode.o faux.o
obj-$(CONFIG_AUXILIARY_BUS) += auxiliary.o
+obj-$(CONFIG_DEVICE_TRUST) += trust.o
obj-$(CONFIG_DEVTMPFS) += devtmpfs.o
obj-y += power/
obj-$(CONFIG_ISA_BUS_API) += isa.o
diff --git a/Documentation/ABI/stable/sysfs-module b/Documentation/ABI/stable/sysfs-module
index 397c5c850894..5c4863f65d03 100644
--- a/Documentation/ABI/stable/sysfs-module
+++ b/Documentation/ABI/stable/sysfs-module
@@ -55,3 +55,10 @@ Description:
requests, the "driver_async_probe=..." kernel command line, the
"async_probe" module option, then this default. Write a valid
boolean value to toggle this policy.
+
+What: /sys/module/module/parameters/require_trust
+Description:
+ (RW) Emits "1" if modules are required to specify a trust=
+ option on load to permit driver(s) in the module to attach to
+ matched devices. By default drivers always attach on match
+ unless the kernel was compiled with DEVICE_TRUST_NONE.
diff --git a/drivers/base/base.h b/drivers/base/base.h
index a5b7abc10ff0..e9d508521b5e 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -103,6 +103,7 @@ struct driver_private {
* dev_err_probe() for later retrieval via debugfs
* @device: pointer back to the struct device that this structure is
* associated with.
+ * @trust: device operational trust level
* @dead: This device is currently either in the process of or has been
* removed from the system. Any asynchronous events scheduled for this
* device should exit without taking any action.
@@ -119,6 +120,7 @@ struct device_private {
const struct device_driver *async_driver;
char *deferred_probe_reason;
struct device *device;
+ enum device_trust trust;
u8 dead:1;
};
#define to_device_private_parent(obj) \
@@ -149,6 +151,19 @@ void auxiliary_bus_init(void);
#else
static inline void auxiliary_bus_init(void) { }
#endif
+#ifdef CONFIG_DEVICE_TRUST
+bool device_trust_bind(const struct device_driver *drv, struct device *dev);
+void device_initialize_trust(struct device *dev);
+#else
+static inline void device_initialize_trust(struct device *dev)
+{
+}
+static inline bool device_trust_bind(const struct device_driver *drv,
+ struct device *dev)
+{
+ return true;
+}
+#endif
struct kobject *virtual_device_parent(void);
diff --git a/include/linux/device/trust.h b/include/linux/device/trust.h
new file mode 100644
index 000000000000..3377d26dc485
--- /dev/null
+++ b/include/linux/device/trust.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2026 NVIDIA Corporation & Affiliates */
+#ifndef __DEVICE_TRUST_H__
+#define __DEVICE_TRUST_H__
+
+/**
+ * enum device_trust - Level of restrictions and privileges for a
+ * device. Trust is initially assigned by the bus, and the bus is
+ * responsible for coordinating transitions between trust levels with
+ * DMA/IOMMU and its own device security mechanisms.
+ *
+ * @DEVICE_TRUST_UNSET: Unregistered device object with no current bus
+ * @DEVICE_TRUST_NONE: Blocked when idle, cannot bind
+ * @DEVICE_TRUST_AUTO: All typical privileges granted
+ */
+enum device_trust {
+ DEVICE_TRUST_UNSET,
+ DEVICE_TRUST_NONE,
+ DEVICE_TRUST_AUTO,
+};
+
+#define DEVICE_DEFAULT_TRUST \
+ (IS_ENABLED(CONFIG_DEVICE_TRUST_NONE) ? DEVICE_TRUST_NONE : \
+ DEVICE_TRUST_AUTO)
+
+struct device;
+struct device_driver;
+
+#ifdef CONFIG_DEVICE_TRUST
+void module_driver_trust(struct module *mod, const char *val);
+void module_driver_trust_init(struct module *mod, bool distrust);
+#else
+static inline void module_driver_trust(struct module *mod, const char *val)
+{
+ pr_warn("module: %s: trust= support disabled\n", mod->name);
+}
+static inline void module_driver_trust_init(struct module *mod, bool distrust)
+{
+}
+#endif
+
+#endif /* __DEVICE_TRUST_H__ */
diff --git a/include/linux/module.h b/include/linux/module.h
index 7566815fabbe..0315035e6b3b 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -29,6 +29,7 @@
#include <linux/srcu.h>
#include <linux/static_call_types.h>
#include <linux/dynamic_debug.h>
+#include <linux/device/trust.h>
#include <linux/percpu.h>
#include <asm/module.h>
@@ -443,6 +444,10 @@ struct module {
#endif
bool async_probe_requested;
+#ifdef CONFIG_DEVICE_TRUST
+ /* Trust level override for devices matching this module's drivers. */
+ enum device_trust trust;
+#endif
/* Exception table */
unsigned int num_exentries;
@@ -865,7 +870,6 @@ static inline bool module_requested_async_probing(struct module *module)
return false;
}
-
/* Dereference module function descriptor */
static inline
void *dereference_module_function_descriptor(struct module *mod, void *ptr)
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 4d026682944f..155ff5852286 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -3754,6 +3754,13 @@ int device_add(struct device *dev)
fw_devlink_link_device(dev);
}
+ /*
+ * The bus and all device add notifiers have had a chance to
+ * initialize trust, set a fallback default in case the trust
+ * level is not yet established.
+ */
+ device_initialize_trust(dev);
+
/*
* The moment the device was linked into the bus's "klist_devices" in
* bus_add_device() then it's possible that probe could have been
diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index 60c005223844..f0b2d58c89a0 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -857,6 +857,11 @@ static int __driver_probe_device(const struct device_driver *drv, struct device
dev_dbg(dev, "bus: '%s': %s: matched device with driver %s\n",
drv->bus->name, __func__, drv->name);
+ if (!device_trust_bind(drv, dev)) {
+ dev_dbg(dev, "not trusted to bind\n");
+ return -ENODEV;
+ }
+
pm_runtime_get_suppliers(dev);
if (dev->parent)
pm_runtime_get_sync(dev->parent);
diff --git a/drivers/base/trust.c b/drivers/base/trust.c
new file mode 100644
index 000000000000..0fd494e1557d
--- /dev/null
+++ b/drivers/base/trust.c
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2026 NVIDIA Corporation & Affiliates */
+
+#include <linux/device.h>
+#include <linux/device/trust.h>
+#include <linux/module.h>
+#include "base.h"
+
+void device_initialize_trust(struct device *dev)
+{
+ if (dev->p->trust == DEVICE_TRUST_UNSET)
+ dev->p->trust = DEVICE_DEFAULT_TRUST;
+}
+
+/* Driver trust policy requires modules, builtin drivers always attach */
+static enum device_trust builtin_driver_trust(void)
+{
+ return DEVICE_TRUST_AUTO;
+}
+
+static enum device_trust driver_trust(struct module *mod)
+{
+ if (!mod)
+ return builtin_driver_trust();
+ return mod->trust;
+}
+
+/*
+ * @dev matches @drv and is locked for probe. Check if the driver has
+ * policy on trusting devices it attaches, update the device's trust
+ * level from that policy. Trust privileges beyond driver bind are
+ * realized in a bus's ->dma_configure().
+ */
+bool device_trust_bind(const struct device_driver *drv, struct device *dev)
+{
+ enum device_trust drv_trust = driver_trust(drv->owner);
+
+ if (drv_trust != DEVICE_TRUST_UNSET)
+ dev->p->trust = drv_trust;
+ return dev->p->trust > DEVICE_TRUST_NONE;
+}
+
+static const char * const device_trust_names[] = {
+ [DEVICE_TRUST_NONE] = "none",
+ [DEVICE_TRUST_AUTO] = "auto",
+};
+
+static enum device_trust device_trust_parse(const char *name)
+{
+ int i;
+
+ if (!name)
+ return DEVICE_TRUST_UNSET;
+
+ for (i = 0; i < ARRAY_SIZE(device_trust_names); i++)
+ if (device_trust_names[i] &&
+ sysfs_streq(name, device_trust_names[i]))
+ return i;
+ return DEVICE_TRUST_UNSET;
+}
+
+void module_driver_trust(struct module *mod, const char *val)
+{
+ mod->trust = device_trust_parse(val);
+}
+
+/*
+ * Honor the module core forcing all modules to no trust by default,
+ * otherwise fallback to the compile-time default for all devices
+ */
+void module_driver_trust_init(struct module *mod, bool require_trust)
+{
+ if (require_trust)
+ mod->trust = DEVICE_TRUST_NONE;
+ else
+ mod->trust = DEVICE_DEFAULT_TRUST;
+}
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 46dd8d25a605..3e1fe9966d50 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -36,6 +36,7 @@
#include <linux/notifier.h>
#include <linux/sched.h>
#include <linux/device.h>
+#include <linux/device/trust.h>
#include <linux/string.h>
#include <linux/mutex.h>
#include <linux/rculist.h>
@@ -3068,6 +3069,12 @@ void flush_module_init_free_work(void)
static bool async_probe;
module_param(async_probe, bool, 0644);
+#ifdef CONFIG_DEVICE_TRUST
+/* Default value for module trust. When true, trust= override required */
+static bool require_trust;
+module_param(require_trust, bool, 0644);
+#endif
+
/*
* This is where the real work happens.
*
@@ -3375,6 +3382,11 @@ static int unknown_module_param_cb(char *param, char *val, const char *modname,
return 0;
}
+ if (strcmp(param, "trust") == 0) {
+ module_driver_trust(mod, val);
+ return 0;
+ }
+
/* Check for magic 'dyndbg' arg */
ret = ddebug_dyndbg_module_param_cb(param, val, modname);
if (ret != 0)
@@ -3544,6 +3556,7 @@ static int load_module(struct load_info *info, const char __user *uargs,
goto bug_cleanup;
mod->async_probe_requested = async_probe;
+ module_driver_trust_init(mod, require_trust);
/* Module is ready to execute: parsing args may do that. */
after_dashes = parse_args(mod->name, mod->args, mod->kp, mod->num_kp,
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 09/15] PCI, device core: Move "untrusted" concept to DEVICE_TRUST_ADVERSARY
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
` (7 preceding siblings ...)
2026-07-05 22:08 ` [PATCH 08/15] device core: Initial device trust infrastructure Dan Williams
@ 2026-07-05 22:08 ` Dan Williams
2026-07-05 22:08 ` [PATCH 10/15] PCI/TSM: Add device interface security LOCKED support Dan Williams
` (6 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-pci, driver-core, ankita, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Will Deacon, Robin Murphy,
Jason Gunthorpe, Bjorn Helgaas, Alexey Kardashevskiy, Xu Yilun,
Aneesh Kumar K.V, Dexuan Cui
An "adversary" device is one that the kernel is allowed to operate, but
with extra safety / paranoia added. It aims to keep the device bounded in
both address space and time (no batched invalidation) for DMA accesses. The
concept of adversarial devices already exists in the PCI core. The
categorization is applied to externally attached PCI devices by default
(like Thunderbolt attached devices). Unlike DEVICE_TRUST_NONE that says "do
not allow driver bind", DEVICE_TRUST_ADVERSARY says "allow driver bind as
long as the driver and/or IOMMU are taking precautions." Uplevel this trust
mechanism from a PCI device "untrusted" boolean flag to the core device
trust level.
The rationale for dev->bus_trust separate from dev->p->trust is to give
buses the ability to establish a trust level before device_add(). Then,
after device_add(), the dependency on 'struct device_private' mandates that
the device-core own and coordinate changes to the device's trust level.
There are implications of the bus expressing less than full trust in a
device and a module later expressing higher levels of trust. For PCI, the
"adversary" / pci_untrusted() determination is made prior to initial
iommu_probe_device(). The default IOMMU domain determination is latched
from bus's specified trust value. Later changes to the trust level, for
example by "modprobe $module trust=auto" skip that IOMMU probing. The
result is that moving a device to DEVICE_TRUST_AUTO may lift translation
blocking (PCI_ACS_TB), but not change the default IOMMU domain from
IOMMU_DOMAIN_DMA.
The default domain can be validated / changed via iommu_group sysfs. The
longer term goal is to dynamically adjust iommu domain at the next
$bus_dma_configure() event, but that needs more surgery and consideration
for multi-device iommu groups.
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: "Rafael J. Wysocki" <rafael@kernel.org>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: Will Deacon <will@kernel.org>
Cc: Robin Murphy <robin.murphy@arm.com>
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Alexey Kardashevskiy <aik@amd.com>
Cc: Xu Yilun <yilun.xu@linux.intel.com>
Cc: "Aneesh Kumar K.V" <aneesh.kumar@kernel.org>
Cc: Dexuan Cui <decui@microsoft.com>
Signed-off-by: Dan Williams <djbw@kernel.org>
---
drivers/base/Kconfig | 13 +++++++++++++
drivers/pci/Kconfig | 1 +
include/linux/device.h | 3 +++
include/linux/device/trust.h | 20 +++++++++++++++-----
include/linux/pci.h | 13 +++++--------
drivers/base/trust.c | 20 +++++++++++++++++++-
drivers/iommu/amd/iommu.c | 2 +-
drivers/iommu/dma-iommu.c | 13 ++++---------
drivers/iommu/intel/iommu.c | 2 +-
drivers/iommu/iommu.c | 2 +-
drivers/pci/ats.c | 2 +-
drivers/pci/pci.c | 2 +-
drivers/pci/probe.c | 10 +++++++---
drivers/pci/quirks.c | 4 ++--
14 files changed, 74 insertions(+), 33 deletions(-)
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index a08523d348d8..a4233bdf9804 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -291,6 +291,14 @@ config DEVICE_TRUST_NONE
possible, the device is blocked by an IOMMU from accessing
assets.
+config DEVICE_TRUST_ADVERSARY
+ bool "Adversary"
+ help
+ Device is allowed to bind. Bus, IOMMU, and driver layers may
+ react to this trust level by disabling access validation
+ bypass mechanisms like PCI ATS. When device is unbound from a
+ driver the device is blocked by an IOMMU where possible.
+
config DEVICE_TRUST_AUTO
bool "Auto"
help
@@ -317,6 +325,11 @@ config BUILTIN_DEVICE_TRUST_AUTO
a driver and deploy all available mechanisms to allow performant
direct memory access This trust level does not grant TCB privileges.
+config BUILTIN_DEVICE_TRUST_ADVERSARY
+ bool "Adversary"
+ help
+ Deploy mitigations in the IOMMU layer and driver to limit access.
+
endchoice
endmenu
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index ed17b5d2d5ae..185b42cefe20 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -22,6 +22,7 @@ menuconfig PCI
bool "PCI support"
depends on HAVE_PCI
depends on MMU
+ select DEVICE_TRUST
help
This option enables support for the PCI local bus, including
support for PCI-X and the foundations for PCI Express support.
diff --git a/include/linux/device.h b/include/linux/device.h
index 7b2baffdd2f5..3e203d573a58 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -688,6 +688,8 @@ enum struct_device_flags {
* @removable: Whether the device can be removed from the system. This
* should be set by the subsystem / bus driver that discovered
* the device.
+ * @bus_trust: Device's initial / prior to device_add() trust level.
+ *
* @flags: DEV_FLAG_XXX flags. Use atomic bitfield operations to modify.
*
* At the lowest level, every device in a Linux system is represented by an
@@ -791,6 +793,7 @@ struct device {
struct device_physical_location *physical_location;
enum device_removable removable;
+ enum device_trust bus_trust;
DECLARE_BITMAP(flags, DEV_FLAG_COUNT);
};
diff --git a/include/linux/device/trust.h b/include/linux/device/trust.h
index 3377d26dc485..283d3196e5e6 100644
--- a/include/linux/device/trust.h
+++ b/include/linux/device/trust.h
@@ -11,30 +11,40 @@
*
* @DEVICE_TRUST_UNSET: Unregistered device object with no current bus
* @DEVICE_TRUST_NONE: Blocked when idle, cannot bind
+ * @DEVICE_TRUST_ADVERSARY: Blocked when idle, constrained when active.
* @DEVICE_TRUST_AUTO: All typical privileges granted
+ *
+ * Devices flagged as adversarial are the ones that can potentially
+ * execute DMA attacks and similar. They are typically connected through
+ * external ports such as Thunderbolt but not limited to that. When an
+ * IOMMU is enabled they should be getting full mappings to make sure
+ * they cannot access arbitrary memory.
*/
enum device_trust {
DEVICE_TRUST_UNSET,
DEVICE_TRUST_NONE,
+ DEVICE_TRUST_ADVERSARY,
DEVICE_TRUST_AUTO,
};
-#define DEVICE_DEFAULT_TRUST \
- (IS_ENABLED(CONFIG_DEVICE_TRUST_NONE) ? DEVICE_TRUST_NONE : \
- DEVICE_TRUST_AUTO)
+#define DEVICE_DEFAULT_TRUST \
+ (IS_ENABLED(CONFIG_DEVICE_TRUST_NONE) ? DEVICE_TRUST_NONE : \
+ IS_ENABLED(CONFIG_DEVICE_TRUST_ADVERSARY) ? DEVICE_TRUST_ADVERSARY : \
+ DEVICE_TRUST_AUTO)
struct device;
struct device_driver;
#ifdef CONFIG_DEVICE_TRUST
+bool device_untrusted(struct device *dev);
void module_driver_trust(struct module *mod, const char *val);
-void module_driver_trust_init(struct module *mod, bool distrust);
+void module_driver_trust_init(struct module *mod, bool require_trust);
#else
static inline void module_driver_trust(struct module *mod, const char *val)
{
pr_warn("module: %s: trust= support disabled\n", mod->name);
}
-static inline void module_driver_trust_init(struct module *mod, bool distrust)
+static inline void module_driver_trust_init(struct module *mod, bool require_trust)
{
}
#endif
diff --git a/include/linux/pci.h b/include/linux/pci.h
index ebb5b9d76360..095ea37fc6d0 100644
--- a/include/linux/pci.h
+++ b/include/linux/pci.h
@@ -485,14 +485,6 @@ struct pci_dev {
unsigned int shpc_managed:1; /* SHPC owned by shpchp */
unsigned int is_thunderbolt:1; /* Thunderbolt controller */
unsigned int is_cxl:1; /* Compute Express Link (CXL) */
- /*
- * Devices marked being untrusted are the ones that can potentially
- * execute DMA attacks and similar. They are typically connected
- * through external ports such as Thunderbolt but not limited to
- * that. When an IOMMU is enabled they should be getting full
- * mappings to make sure they cannot access arbitrary memory.
- */
- unsigned int untrusted:1;
/*
* Info from the platform, e.g., ACPI or device tree, may mark a
* device as "external-facing". An external-facing device is
@@ -812,6 +804,11 @@ static inline bool pcie_is_cxl(struct pci_dev *pci_dev)
return pci_dev->is_cxl;
}
+static inline bool pci_untrusted(struct pci_dev *pdev)
+{
+ return device_untrusted(&pdev->dev);
+}
+
#define for_each_pci_bridge(dev, bus) \
list_for_each_entry(dev, &bus->devices, bus_list) \
if (!pci_is_bridge(dev)) {} else
diff --git a/drivers/base/trust.c b/drivers/base/trust.c
index 0fd494e1557d..8efbe5c51250 100644
--- a/drivers/base/trust.c
+++ b/drivers/base/trust.c
@@ -6,15 +6,28 @@
#include <linux/module.h>
#include "base.h"
+/* If the bus did not initialize trust, set a default */
void device_initialize_trust(struct device *dev)
{
+ dev->p->trust = dev->bus_trust;
if (dev->p->trust == DEVICE_TRUST_UNSET)
dev->p->trust = DEVICE_DEFAULT_TRUST;
}
+/*
+ * ->bus_trust is evaluated / manipulated prior to device_add() and
+ * synced with dev->p->trust post device_add() under device_lock().
+ */
+bool device_untrusted(struct device *dev)
+{
+ return dev->bus_trust && dev->bus_trust <= DEVICE_TRUST_ADVERSARY;
+}
+
/* Driver trust policy requires modules, builtin drivers always attach */
static enum device_trust builtin_driver_trust(void)
{
+ if (IS_ENABLED(CONFIG_BUILTIN_DEVICE_TRUST_ADVERSARY))
+ return DEVICE_TRUST_ADVERSARY;
return DEVICE_TRUST_AUTO;
}
@@ -30,18 +43,23 @@ static enum device_trust driver_trust(struct module *mod)
* policy on trusting devices it attaches, update the device's trust
* level from that policy. Trust privileges beyond driver bind are
* realized in a bus's ->dma_configure().
+ *
+ * Reflect the operational trust level back to the public indicator.
*/
bool device_trust_bind(const struct device_driver *drv, struct device *dev)
{
enum device_trust drv_trust = driver_trust(drv->owner);
- if (drv_trust != DEVICE_TRUST_UNSET)
+ if (drv_trust != DEVICE_TRUST_UNSET) {
dev->p->trust = drv_trust;
+ dev->bus_trust = drv_trust;
+ }
return dev->p->trust > DEVICE_TRUST_NONE;
}
static const char * const device_trust_names[] = {
[DEVICE_TRUST_NONE] = "none",
+ [DEVICE_TRUST_ADVERSARY] = "adversary",
[DEVICE_TRUST_AUTO] = "auto",
};
diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c
index 563f9c2672d5..ef663e3efc70 100644
--- a/drivers/iommu/amd/iommu.c
+++ b/drivers/iommu/amd/iommu.c
@@ -3121,7 +3121,7 @@ static int amd_iommu_def_domain_type(struct device *dev)
return 0;
/* Always use DMA domain for untrusted device */
- if (dev_is_pci(dev) && to_pci_dev(dev)->untrusted)
+ if (device_untrusted(dev))
return IOMMU_DOMAIN_DMA;
/*
diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 9abaec0703ef..957ef77911a9 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -588,16 +588,11 @@ static int iova_reserve_iommu_regions(struct device *dev,
return ret;
}
-static bool dev_is_untrusted(struct device *dev)
-{
- return dev_is_pci(dev) && to_pci_dev(dev)->untrusted;
-}
-
static bool dev_use_swiotlb(struct device *dev, size_t size,
enum dma_data_direction dir)
{
return IS_ENABLED(CONFIG_SWIOTLB) &&
- (dev_is_untrusted(dev) ||
+ (device_untrusted(dev) ||
dma_kmalloc_needs_bounce(dev, size, dir));
}
@@ -610,7 +605,7 @@ static bool dev_use_sg_swiotlb(struct device *dev, struct scatterlist *sg,
if (!IS_ENABLED(CONFIG_SWIOTLB))
return false;
- if (dev_is_untrusted(dev))
+ if (device_untrusted(dev))
return true;
/*
@@ -1188,7 +1183,7 @@ static phys_addr_t iommu_dma_map_swiotlb(struct device *dev, phys_addr_t phys,
* swiotlb_tbl_map_single() has initialized the bounce buffer proper to
* the contents of the original memory buffer.
*/
- if (phys != (phys_addr_t)DMA_MAPPING_ERROR && dev_is_untrusted(dev)) {
+ if (phys != (phys_addr_t)DMA_MAPPING_ERROR && device_untrusted(dev)) {
size_t start, virt = (size_t)phys_to_virt(phys);
/* Pre-padding */
@@ -1761,7 +1756,7 @@ size_t iommu_dma_opt_mapping_size(void)
size_t iommu_dma_max_mapping_size(struct device *dev)
{
- if (dev_is_untrusted(dev))
+ if (device_untrusted(dev))
return swiotlb_max_mapping_size(dev);
return SIZE_MAX;
diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c
index 849d06dfe1ae..82d9abe87d99 100644
--- a/drivers/iommu/intel/iommu.c
+++ b/drivers/iommu/intel/iommu.c
@@ -3501,7 +3501,7 @@ static bool intel_iommu_is_attach_deferred(struct device *dev)
*/
static bool risky_device(struct pci_dev *pdev)
{
- if (pdev->untrusted) {
+ if (pci_untrusted(pdev)) {
pci_info(pdev,
"Skipping IOMMU quirk for dev [%04X:%04X] on untrusted PCI link\n",
pdev->vendor, pdev->device);
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index e8f13dcebbde..4769d2b548d6 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -1901,7 +1901,7 @@ static int iommu_get_default_domain_type(struct iommu_group *group,
driver_type = iommu_get_def_domain_type(group, gdev->dev,
driver_type);
- if (dev_is_pci(gdev->dev) && to_pci_dev(gdev->dev)->untrusted) {
+ if (device_untrusted(gdev->dev)) {
/*
* No ARM32 using systems will set untrusted, it cannot
* work.
diff --git a/drivers/pci/ats.c b/drivers/pci/ats.c
index 96efa00d9743..7296f0ce7530 100644
--- a/drivers/pci/ats.c
+++ b/drivers/pci/ats.c
@@ -43,7 +43,7 @@ bool pci_ats_supported(struct pci_dev *dev)
if (!dev->ats_cap)
return false;
- return (dev->untrusted == 0);
+ return !pci_untrusted(dev);
}
EXPORT_SYMBOL_GPL(pci_ats_supported);
diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
index 77b17b13ee61..ff1ca74f30b1 100644
--- a/drivers/pci/pci.c
+++ b/drivers/pci/pci.c
@@ -994,7 +994,7 @@ static void pci_std_enable_acs(struct pci_dev *dev, struct pci_acs *caps)
caps->ctrl |= (dev->acs_capabilities & PCI_ACS_UF);
/* Enable Translation Blocking for external devices and noats */
- if (pci_ats_disabled() || dev->external_facing || dev->untrusted)
+ if (pci_ats_disabled() || dev->external_facing || pci_untrusted(dev))
caps->ctrl |= (dev->acs_capabilities & PCI_ACS_TB);
}
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index dd0abbc63e18..91a7bbcfdecb 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -6,6 +6,7 @@
#include <linux/array_size.h>
#include <linux/kernel.h>
#include <linux/delay.h>
+#include <linux/device/trust.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/msi.h>
@@ -1737,20 +1738,23 @@ static void set_pcie_untrusted(struct pci_dev *dev)
{
struct pci_dev *parent = pci_upstream_bridge(dev);
+ dev->dev.bus_trust = DEVICE_DEFAULT_TRUST;
if (!parent)
return;
/*
* If the upstream bridge is untrusted we treat this device as
* untrusted as well.
*/
- if (parent->untrusted) {
- dev->untrusted = true;
+ if (pci_untrusted(parent)) {
+ dev->dev.bus_trust =
+ min(dev->dev.bus_trust, parent->dev.bus_trust);
return;
}
if (arch_pci_dev_is_removable(dev)) {
pci_dbg(dev, "marking as untrusted\n");
- dev->untrusted = true;
+ dev->dev.bus_trust =
+ min(dev->dev.bus_trust, DEVICE_TRUST_ADVERSARY);
}
}
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index b09f27f7846f..73e473856999 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -5370,7 +5370,7 @@ static void pci_quirk_enable_intel_rp_mpc_acs(struct pci_dev *dev)
* PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF
*
* TODO: This quirk also needs to do equivalent of PCI_ACS_TB,
- * if dev->external_facing || dev->untrusted
+ * if dev->external_facing || pci_untrusted(dev)
*/
static int pci_quirk_enable_intel_pch_acs(struct pci_dev *dev)
{
@@ -5411,7 +5411,7 @@ static int pci_quirk_enable_intel_spt_pch_acs(struct pci_dev *dev)
ctrl |= (cap & PCI_ACS_CR);
ctrl |= (cap & PCI_ACS_UF);
- if (pci_ats_disabled() || dev->external_facing || dev->untrusted)
+ if (pci_ats_disabled() || dev->external_facing || pci_untrusted(dev))
ctrl |= (cap & PCI_ACS_TB);
pci_write_config_dword(dev, pos + INTEL_SPT_ACS_CTRL, ctrl);
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 10/15] PCI/TSM: Add device interface security LOCKED support
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
` (8 preceding siblings ...)
2026-07-05 22:08 ` [PATCH 09/15] PCI, device core: Move "untrusted" concept to DEVICE_TRUST_ADVERSARY Dan Williams
@ 2026-07-05 22:08 ` Dan Williams
2026-07-05 22:08 ` [PATCH 11/15] PCI/TSM: Add device interface security RUN support Dan Williams
` (5 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-pci, driver-core, ankita, Bjorn Helgaas, Xu Yilun,
Aneesh Kumar K.V (Arm)
PCIe Trusted Execution Environment Device Interface Security Protocol
(TDISP) has two distinct sets of operations. The first, already enabled in
driver/pci/tsm.c, enables the VMM to authenticate the physical function
(PCIe Component Measurement and Authentication (CMA)), establish a secure
message passing session (DMTF SPDM), and establish physical link security
(PCIe Integrity and Data Encryption (IDE)). The second set of operations
lets a VM manage the security state of assigned devices (TEE Device
Interfaces (TDIs)). Implement the LOCK/UNLOCK operations in pci_tsm_ops, to
be followed with an operation to transition the interface to RUN.
- lock(): Transition the device to the TDISP LOCKED state. In this state
measurements can validated to ensure that the VM trusts the device's
security manager (DSM).
- unlock(): Transition the device to the TDISP UNLOCKED state.
The device shall not be attached to a driver during these transitions.
Cc: Bjorn Helgaas <bhelgaas@google.com>
Co-developed-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Co-developed-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
Signed-off-by: Dan Williams <djbw@kernel.org>
---
Documentation/ABI/testing/sysfs-bus-pci | 34 +++-
Documentation/ABI/testing/sysfs-class-tsm | 19 ++
Documentation/driver-api/pci/tsm.rst | 47 +++++
include/linux/pci-tsm.h | 21 ++-
drivers/pci/tsm/core.c | 213 +++++++++++++++++++++-
drivers/virt/coco/tsm-core.c | 43 ++++-
6 files changed, 366 insertions(+), 11 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
index b767db2c52cb..1ed77b9402a6 100644
--- a/Documentation/ABI/testing/sysfs-bus-pci
+++ b/Documentation/ABI/testing/sysfs-bus-pci
@@ -647,13 +647,16 @@ Description:
Encryption) establishment. Reads from this attribute return the
name of the connected TSM or the empty string if not
connected. A TSM device signals its readiness to accept PCI
- connection via a KOBJ_CHANGE event.
+ connection via a KOBJ_CHANGE event. This is a "link" TSM
+ attribute, see Documentation/ABI/testing/sysfs-class-tsm.
What: /sys/bus/pci/devices/.../tsm/disconnect
Contact: linux-coco@lists.linux.dev
Description:
(WO) Write the name of the TSM device that was specified
- to 'connect' to teardown the connection.
+ to 'connect' to teardown the connection. This is a
+ "link" TSM attribute, see
+ Documentation/ABI/testing/sysfs-class-tsm.
What: /sys/bus/pci/devices/.../tsm/dsm
Contact: linux-coco@lists.linux.dev
@@ -702,3 +705,30 @@ Description:
When present and the tsm/ attribute directory is present, the
authenticated attribute is an alias for the device 'connect'
state. See the 'tsm/connect' attribute for more details.
+ This is a "link" TSM attribute, see
+ Documentation/ABI/testing/sysfs-class-tsm.
+
+What: /sys/bus/pci/devices/.../tsm/lock
+Contact: linux-coco@lists.linux.dev
+Description:
+ (RW) Write the name of a TSM (TEE Security Manager) device
+ instance to this file to request that the platform transition
+ the PCIe device configuration to the TDISP LOCKED state. This
+ puts the device in a state where security sensitive
+ configuration setting can not be changed without transitioning
+ the device the PCIe TDISP ERROR state. Reads from this attribute
+ return the name of the TSM device instance that has arranged for
+ the device to be locked, or the empty string if not locked. A
+ TSM class device signals its readiness for lock requests via a
+ KOBJ_CHANGE event. Writes fail with EBUSY if this device is
+ bound to a driver. This is a "devsec" TSM attribute, see
+ Documentation/ABI/testing/sysfs-class-tsm. See
+ Documentation/driver-api/pci/tsm.rst for more details.
+
+What: /sys/bus/pci/devices/.../tsm/unlock
+Contact: linux-coco@lists.linux.dev
+Description:
+ (WO) Write the name of the TSM device that was specified to
+ 'lock' to teardown the connection. Writes fail with EBUSY if
+ this device is bound to a driver. This is a "devsec" TSM
+ attribute, see Documentation/ABI/testing/sysfs-class-tsm.
diff --git a/Documentation/ABI/testing/sysfs-class-tsm b/Documentation/ABI/testing/sysfs-class-tsm
index 2949468deaf7..44e8a0bf4dae 100644
--- a/Documentation/ABI/testing/sysfs-class-tsm
+++ b/Documentation/ABI/testing/sysfs-class-tsm
@@ -7,3 +7,22 @@ Description:
signals when the PCI layer is able to support establishment of
link encryption and other device-security features coordinated
through a platform tsm.
+
+What: /sys/class/tsm/tsmN/pci_mode
+Contact: linux-coco@lists.linux.dev
+Description:
+ (RO) A TSM with PCIe TDISP capability can be in one of two
+ modes.
+
+ "link": typically for a hypervisor (VMM) to authenticate,
+ establish a secure session, and setup link
+ encryption.
+
+ "devsec": typically for a confidential guest (TVM) to
+ transition assigned devices through the TDISP
+ state machine UNLOCKED->LOCKED->RUN.
+
+ See the "tsm/" entries in
+ Documentation/ABI/testing/sysfs-bus-pci for the available PCI
+ device attributes when a TSM with the given "pci_mode" is
+ registered.
diff --git a/Documentation/driver-api/pci/tsm.rst b/Documentation/driver-api/pci/tsm.rst
index 232b92bec93f..19df202107fa 100644
--- a/Documentation/driver-api/pci/tsm.rst
+++ b/Documentation/driver-api/pci/tsm.rst
@@ -5,6 +5,53 @@
PCI Trusted Execution Environment Security Manager (TSM)
========================================================
+Overview
+========
+
+A "TSM", as detailed by PCIe r7.0 section 11 "TEE Device Interface
+Security Protocol (TDISP)", is an entity within the platform's Trusted
+Computing Base (TCB) that enforces security policies on the host. It
+serves to mitigate a threat model where devices may be under the control
+of an adversary. The adversarial threats are:
+
+- Identity: Device may be mimicking a legitimate device identity / firmware
+- Physical: link may be under observation, or control (reorder / drop data)
+- Virtual: Device MMIO presented to a guest may not actually map the
+ device, device DMA may be redirected.
+
+In Linux a "tsm" is a broader concept. It is a class device interface to
+mitigate one or more of the above threats. A "tsm driver" registers a
+tsm device that publishes either the 'tsm/connect' or
+'tsm/{lock,accept}' set of attributes for the PCIe device. The typical
+expectation is that 'tsm/{lock,accept}' is published by a guest "tsm
+driver" to mitigate "Virtual" threats. The 'tsm/connect' interface is
+published by a host "tsm driver" to mitigate "Identity" and/or
+"Physical" threats.
+
+Device Interface LOCK
+=====================
+The lock operation facilitated by tsm/lock (see
+Documentation/ABI/testing/sysfs-bus-pci) places the device in a mode
+where any security sensitive changes to the device configuration results
+in the device transitioning to the ERROR state. The device presents
+signed evidence of its LOCK state to the kernel through the tsm driver.
+The relying party is responsible for verifying not only the evidence but
+that the device is trusted to maintain those attested values while
+locked. Accepting the locked configuration moves the device to the RUN
+state where it will accept private (T=1) traffic to its MMIO interface.
+
+Note that the RUN state is necessary, but not sufficient for the device
+to access private host memory (send T=1 DMA). That is only enabled after
+the device's trust level has been elevated to indicate it can operate
+within the TCB.
+
+A device in the RUN state is trusted to cease TCB interactions (send T=1
+DMA / accept T=1 MMIO TLPs) when it is next unlocked or encounters a
+security relevant error. The TSM is responsible for enforcing that the
+device is not unlocked within the interval between evidence collection
+and acceptance, by correlating the evidence from LOCK to the subsequent
+RUN request.
+
Subsystem Interfaces
====================
diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
index 8bc16029d31e..326471f1e01a 100644
--- a/include/linux/pci-tsm.h
+++ b/include/linux/pci-tsm.h
@@ -72,7 +72,8 @@ struct pci_tsm_ops {
* @unlock: destroy TSM context and return device to UNLOCKED state
*
* Context: @lock and @unlock run under pci_tsm_rwsem held for write to
- * sync with TSM unregistration and each other
+ * sync with TSM unregistration and each other. All operations run under
+ * the device lock for mutual exclusion with driver attach and detach.
*/
struct_group_tagged(pci_tsm_devsec_ops, devsec_ops,
struct pci_tsm *(*lock)(struct tsm_dev *tsm_dev,
@@ -115,6 +116,13 @@ struct pci_tdi {
* sub-function (SR-IOV virtual function, or non-function0
* multifunction-device), or a downstream endpoint (PCIe upstream switch-port as
* DSM).
+ *
+ * For devsec operations it serves to indicate that the function / TDI has been
+ * locked to a given TSM.
+ *
+ * The common expectation is that there is only ever one TSM, but this is not
+ * enforced. The implementation only enforces that a device can be "connected"
+ * to a TSM instance or "locked" to a different TSM.
*/
struct pci_tsm {
struct pci_dev *pdev;
@@ -136,6 +144,14 @@ struct pci_tsm_pf0 {
struct pci_doe_mb *doe_mb;
};
+/**
+ * struct pci_tsm_devsec - context for tracking private/accepted PCI resources
+ * @base_tsm: generic core "tsm" context
+ */
+struct pci_tsm_devsec {
+ struct pci_tsm base_tsm;
+};
+
/* physical function0 and capable of 'connect' */
static inline bool is_pci_tsm_pf0(struct pci_dev *pdev)
{
@@ -216,6 +232,8 @@ int pci_tsm_link_constructor(struct pci_dev *pdev, struct pci_tsm *tsm,
struct tsm_dev *tsm_dev);
int pci_tsm_pf0_constructor(struct pci_dev *pdev, struct pci_tsm_pf0 *tsm,
struct tsm_dev *tsm_dev);
+int pci_tsm_devsec_constructor(struct pci_dev *pdev, struct pci_tsm_devsec *tsm,
+ struct tsm_dev *tsm_dev);
void pci_tsm_pf0_destructor(struct pci_tsm_pf0 *tsm);
int pci_tsm_doe_transfer(struct pci_dev *pdev, u8 type, const void *req,
size_t req_sz, void *resp, size_t resp_sz);
@@ -230,6 +248,7 @@ static inline const struct pci_tsm_ops *to_pci_tsm_ops(struct pci_tsm *tsm)
{
return tsm->tsm_dev->pci_ops;
}
+struct pci_tsm_devsec *to_pci_tsm_devsec(struct pci_tsm *tsm);
#else
static inline int pci_tsm_register(struct tsm_dev *tsm_dev)
{
diff --git a/drivers/pci/tsm/core.c b/drivers/pci/tsm/core.c
index 220842df42bc..590e19f6c2b8 100644
--- a/drivers/pci/tsm/core.c
+++ b/drivers/pci/tsm/core.c
@@ -9,6 +9,7 @@
#define dev_fmt(fmt) "PCI/TSM: " fmt
#include <linux/bitfield.h>
+#include <linux/ioport.h>
#include <linux/pci.h>
#include <linux/pci-doe.h>
#include <linux/pci-tsm.h>
@@ -58,6 +59,26 @@ static struct pci_tsm_pf0 *to_pci_tsm_pf0(struct pci_tsm *tsm)
return container_of(pf0->tsm, struct pci_tsm_pf0, base_tsm);
}
+static inline bool is_devsec(struct pci_dev *pdev)
+{
+ return pdev->tsm && pdev->tsm->dsm_dev == NULL &&
+ pdev->tsm->tdi == NULL;
+}
+
+/* 'struct pci_tsm_devsec' wraps 'struct pci_tsm' when ->tdi == ->dsm == NULL */
+struct pci_tsm_devsec *to_pci_tsm_devsec(struct pci_tsm *tsm)
+{
+ struct pci_dev *pdev = tsm->pdev;
+
+ if (!is_devsec(pdev) || !has_tee(pdev)) {
+ pci_WARN_ONCE(pdev, 1, "invalid context object\n");
+ return NULL;
+ }
+
+ return container_of(tsm, struct pci_tsm_devsec, base_tsm);
+}
+EXPORT_SYMBOL_GPL(to_pci_tsm_devsec);
+
static void tsm_remove(struct pci_tsm *tsm)
{
struct pci_dev *pdev;
@@ -531,6 +552,125 @@ static ssize_t dsm_show(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR_RO(dsm);
+/**
+ * pci_tsm_unlock() - Transition TDI from LOCKED/RUN to UNLOCKED
+ * @pdev: TDI device to unlock
+ *
+ * Returns void, requires all callers to have satisfied dependencies like making
+ * sure the device is locked and detached from its driver.
+ */
+static void pci_tsm_unlock(struct pci_dev *pdev)
+{
+ lockdep_assert_held_write(&pci_tsm_rwsem);
+ device_lock_assert(&pdev->dev);
+
+ if (dev_WARN_ONCE(&pdev->dev, pdev->dev.driver,
+ "unlock attempted on driver attached device\n"))
+ return;
+
+ device_cc_reject(&pdev->dev);
+ to_pci_tsm_ops(pdev->tsm)->unlock(pdev->tsm);
+ pdev->tsm = NULL;
+}
+
+static int pci_tsm_lock(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
+{
+ const struct pci_tsm_ops *ops = tsm_dev->pci_ops;
+ struct pci_tsm *tsm;
+ int rc;
+
+ ACQUIRE(device_intr, lock)(&pdev->dev);
+ if ((rc = ACQUIRE_ERR(device_intr, &lock)))
+ return rc;
+
+ if (pdev->dev.driver)
+ return -EBUSY;
+
+ tsm = ops->lock(tsm_dev, pdev);
+ if (IS_ERR(tsm))
+ return PTR_ERR(tsm);
+
+ pdev->tsm = tsm;
+ return 0;
+}
+
+static ssize_t lock_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ int rc, id;
+
+ rc = sscanf(buf, "tsm%d\n", &id);
+ if (rc != 1)
+ return -EINVAL;
+
+ ACQUIRE(rwsem_write_kill, lock)(&pci_tsm_rwsem);
+ if ((rc = ACQUIRE_ERR(rwsem_write_kill, &lock)))
+ return rc;
+
+ if (pdev->tsm)
+ return -EBUSY;
+
+ struct tsm_dev *tsm_dev __free(put_tsm_dev) = find_tsm_dev(id);
+ if (!is_devsec_tsm(tsm_dev))
+ return -ENXIO;
+
+ rc = pci_tsm_lock(pdev, tsm_dev);
+ if (rc)
+ return rc;
+ return len;
+}
+
+static ssize_t lock_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct tsm_dev *tsm_dev;
+ int rc;
+
+ ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
+ if ((rc = ACQUIRE_ERR(rwsem_read_intr, &lock)))
+ return rc;
+
+ if (!pdev->tsm)
+ return sysfs_emit(buf, "\n");
+
+ tsm_dev = pdev->tsm->tsm_dev;
+ return sysfs_emit(buf, "%s\n", dev_name(&tsm_dev->dev));
+}
+static DEVICE_ATTR_RW(lock);
+
+static ssize_t unlock_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ struct tsm_dev *tsm_dev;
+ int rc;
+
+ ACQUIRE(rwsem_write_kill, lock)(&pci_tsm_rwsem);
+ if ((rc = ACQUIRE_ERR(rwsem_write_kill, &lock)))
+ return rc;
+
+ if (!pdev->tsm)
+ return -EINVAL;
+
+ tsm_dev = pdev->tsm->tsm_dev;
+ if (!sysfs_streq(buf, dev_name(&tsm_dev->dev)))
+ return -EINVAL;
+
+ ACQUIRE(device_intr, dev_lock)(&pdev->dev);
+ if ((rc = ACQUIRE_ERR(device_intr, &dev_lock)))
+ return rc;
+
+ if (pdev->dev.driver)
+ return -EBUSY;
+
+ pci_tsm_unlock(pdev);
+
+ return len;
+}
+static DEVICE_ATTR_WO(unlock);
+
/* The 'authenticated' attribute is exclusive to the presence of a 'link' TSM */
static bool pci_tsm_link_group_visible(struct kobject *kobj)
{
@@ -556,6 +696,13 @@ static bool pci_tsm_link_group_visible(struct kobject *kobj)
}
DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(pci_tsm_link);
+static bool pci_tsm_devsec_group_visible(struct kobject *kobj)
+{
+ struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
+
+ return pci_tsm_devsec_count && has_tee(pdev);
+}
+
/*
* 'link' and 'devsec' TSMs share the same 'tsm/' sysfs group, so the TSM type
* specific attributes need individual visibility checks.
@@ -587,12 +734,19 @@ static umode_t pci_tsm_attr_visible(struct kobject *kobj,
}
}
+ if (pci_tsm_devsec_group_visible(kobj)) {
+ if (attr == &dev_attr_lock.attr ||
+ attr == &dev_attr_unlock.attr)
+ return attr->mode;
+ }
+
return 0;
}
static bool pci_tsm_group_visible(struct kobject *kobj)
{
- return pci_tsm_link_group_visible(kobj);
+ return pci_tsm_link_group_visible(kobj) ||
+ pci_tsm_devsec_group_visible(kobj);
}
DEFINE_SYSFS_GROUP_VISIBLE(pci_tsm);
@@ -601,6 +755,8 @@ static struct attribute *pci_tsm_attrs[] = {
&dev_attr_disconnect.attr,
&dev_attr_bound.attr,
&dev_attr_dsm.attr,
+ &dev_attr_lock.attr,
+ &dev_attr_unlock.attr,
NULL
};
@@ -729,6 +885,29 @@ int pci_tsm_link_constructor(struct pci_dev *pdev, struct pci_tsm *tsm,
}
EXPORT_SYMBOL_GPL(pci_tsm_link_constructor);
+/**
+ * pci_tsm_devsec_constructor() - devsec TSM context initialization
+ * @pdev: The PCI device
+ * @tsm: context to initialize
+ * @tsm_dev: Platform TEE Security Manager, initiator of security operations
+ */
+int pci_tsm_devsec_constructor(struct pci_dev *pdev, struct pci_tsm_devsec *tsm,
+ struct tsm_dev *tsm_dev)
+{
+ struct pci_tsm *pci_tsm = &tsm->base_tsm;
+
+ if (!is_devsec_tsm(tsm_dev))
+ return -EINVAL;
+
+ pci_tsm->dsm_dev = NULL;
+ pci_tsm->tdi = NULL;
+ pci_tsm->pdev = pdev;
+ pci_tsm->tsm_dev = tsm_dev;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pci_tsm_devsec_constructor);
+
/**
* pci_tsm_pf0_constructor() - common 'struct pci_tsm_pf0' (DSM) initialization
* @pdev: Physical Function 0 PCI device (as indicated by is_pci_tsm_pf0())
@@ -756,6 +935,13 @@ void pci_tsm_pf0_destructor(struct pci_tsm_pf0 *pf0_tsm)
}
EXPORT_SYMBOL_GPL(pci_tsm_pf0_destructor);
+static void devsec_sysfs_enable(struct pci_dev *pdev)
+{
+ pci_dbg(pdev, "TEE I/O Device capability detected (TDISP)\n");
+
+ sysfs_update_group(&pdev->dev.kobj, &pci_tsm_attr_group);
+}
+
int pci_tsm_register(struct tsm_dev *tsm_dev)
{
struct pci_dev *pdev = NULL;
@@ -777,8 +963,10 @@ int pci_tsm_register(struct tsm_dev *tsm_dev)
for_each_pci_dev(pdev)
if (is_pci_tsm_pf0(pdev))
link_sysfs_enable(pdev);
- } else if (is_devsec_tsm(tsm_dev)) {
- pci_tsm_devsec_count++;
+ } else if (is_devsec_tsm(tsm_dev) && pci_tsm_devsec_count++ == 0) {
+ for_each_pci_dev(pdev)
+ if (has_tee(pdev))
+ devsec_sysfs_enable(pdev);
}
return 0;
@@ -813,6 +1001,9 @@ static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
if (is_link_tsm(tsm_dev) && is_pci_tsm_pf0(pdev) && !pci_tsm_link_count)
link_sysfs_disable(pdev);
+ if (is_devsec_tsm(tsm_dev) && !pci_tsm_devsec_count)
+ sysfs_update_group(&pdev->dev.kobj, &pci_tsm_attr_group);
+
/* Nothing else to do if this device never attached to the departing TSM */
if (!tsm)
return;
@@ -823,10 +1014,18 @@ static void __pci_tsm_destroy(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
else if (tsm_dev != tsm->tsm_dev)
return;
- if (is_link_tsm(tsm_dev) && is_pci_tsm_pf0(pdev))
- pci_tsm_disconnect(pdev);
- else
- pci_tsm_fn_exit(pdev);
+ /* Disconnect DSMs, unlock assigned TDIs, or cleanup DSM subfunctions */
+ if (is_link_tsm(tsm_dev)) {
+ if (is_pci_tsm_pf0(pdev))
+ pci_tsm_disconnect(pdev);
+ else
+ pci_tsm_fn_exit(pdev);
+ }
+
+ if (is_devsec_tsm(tsm_dev) && has_tee(pdev)) {
+ guard(device)(&pdev->dev);
+ pci_tsm_unlock(pdev);
+ }
}
void pci_tsm_destroy(struct pci_dev *pdev)
diff --git a/drivers/virt/coco/tsm-core.c b/drivers/virt/coco/tsm-core.c
index e784993353d8..8e298bda10fc 100644
--- a/drivers/virt/coco/tsm-core.c
+++ b/drivers/virt/coco/tsm-core.c
@@ -9,10 +9,50 @@
#include <linux/cleanup.h>
#include <linux/pci-tsm.h>
+static ssize_t pci_mode_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct tsm_dev *tsm_dev = container_of(dev, struct tsm_dev, dev);
+ const struct pci_tsm_ops *ops = tsm_dev->pci_ops;
+
+ if (ops->connect)
+ return sysfs_emit(buf, "link\n");
+ if (ops->lock)
+ return sysfs_emit(buf, "devsec\n");
+ return sysfs_emit(buf, "none\n");
+}
+static DEVICE_ATTR_RO(pci_mode);
+
+static umode_t tsm_pci_visible(struct kobject *kobj, struct attribute *attr, int n)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct tsm_dev *tsm_dev = container_of(dev, struct tsm_dev, dev);
+
+ if (tsm_dev->pci_ops)
+ return attr->mode;
+ return 0;
+}
+
+static struct attribute *tsm_pci_attrs[] = {
+ &dev_attr_pci_mode.attr,
+ NULL
+};
+
+static const struct attribute_group tsm_pci_group = {
+ .attrs = tsm_pci_attrs,
+ .is_visible = tsm_pci_visible,
+};
+
+static const struct attribute_group *tsm_pci_groups[] = {
+ &tsm_pci_group,
+ NULL
+};
+
static void tsm_release(struct device *);
static const struct class tsm_class = {
.name = "tsm",
- .dev_release = tsm_release
+ .dev_release = tsm_release,
+ .dev_groups = tsm_pci_groups,
};
static DEFINE_IDA(tsm_ida);
@@ -72,6 +112,7 @@ static struct tsm_dev *tsm_register_pci_or_reset(struct tsm_dev *tsm_dev,
device_unregister(&tsm_dev->dev);
return ERR_PTR(rc);
}
+ sysfs_update_group(&tsm_dev->dev.kobj, &tsm_pci_group);
/* Notify TSM userspace that PCI/TSM operations are now possible */
kobject_uevent(&tsm_dev->dev.kobj, KOBJ_CHANGE);
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 11/15] PCI/TSM: Add device interface security RUN support
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
` (9 preceding siblings ...)
2026-07-05 22:08 ` [PATCH 10/15] PCI/TSM: Add device interface security LOCKED support Dan Williams
@ 2026-07-05 22:08 ` Dan Williams
2026-07-05 22:08 ` [PATCH 12/15] PCI/TSM: Add device interface security DMA enable/disable Dan Williams
` (4 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-pci, driver-core, ankita, Bjorn Helgaas, Xu Yilun,
Aneesh Kumar K.V (Arm)
The final device-side operation of the PCIe Trusted Execution Environment
(TEE) Device Interface Security Protocol (TDISP) state machine is to ask
the TSM to flip the device from LOCKED to RUN.
The story so far in the security lifecycle of the device is that the VMM
setup an SPDM session and link encryption with the device's physical
function0. The VMM then assigned either that physical function or other
virtual function of that device to a VM. The VM asked the TSM to transition
the device from TDISP UNLOCKED->LOCKED. With the device LOCKED the VM is
enabled to validate signed fresh evidence of the device's configuration and
expected MMIO mappings.
The VM now accepts the device to transition it from LOCKED to RUN.
Implement a sysfs trigger to flip the device to private operation and plumb
that to a 'struct pci_tsm_ops::run()' operation.
Note that the RUN state only allows the device to be accessed via private
MMIO. DMA enable is a separate step triggered at pci_dma_configure() time.
Cc: Bjorn Helgaas <bhelgaas@google.com>
Co-developed-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Co-developed-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
Signed-off-by: Dan Williams <djbw@kernel.org>
---
Documentation/ABI/testing/sysfs-bus-pci | 13 ++++
include/linux/pci-tsm.h | 12 +++-
drivers/pci/tsm/core.c | 89 ++++++++++++++++++++++++-
3 files changed, 110 insertions(+), 4 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
index 1ed77b9402a6..c2a5c4fe9373 100644
--- a/Documentation/ABI/testing/sysfs-bus-pci
+++ b/Documentation/ABI/testing/sysfs-bus-pci
@@ -732,3 +732,16 @@ Description:
'lock' to teardown the connection. Writes fail with EBUSY if
this device is bound to a driver. This is a "devsec" TSM
attribute, see Documentation/ABI/testing/sysfs-class-tsm.
+
+What: /sys/bus/pci/devices/.../tsm/accept
+Contact: linux-coco@lists.linux.dev
+Description:
+ (RW) Write "1" (or any boolean "true" string) to this file to
+ request that TSM transition the device from the TDISP LOCKED
+ state to the RUN state and arrange the for the secure IOMMU to
+ accept requests with T=1 in the PCIe packet header (TLP)
+ targeting private memory. Per TDISP the only exits from the RUN
+ state are via an explicit unlock request or an event that
+ transitions the device to the ERROR state. Writes fail with
+ EBUSY if this device is bound to a driver. This is a "devsec"
+ TSM attribute, see Documentation/ABI/testing/sysfs-class-tsm.
diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
index 326471f1e01a..371a7e300a64 100644
--- a/include/linux/pci-tsm.h
+++ b/include/linux/pci-tsm.h
@@ -70,15 +70,18 @@ struct pci_tsm_ops {
* pci_tsm') for follow-on security state transitions from the
* LOCKED state
* @unlock: destroy TSM context and return device to UNLOCKED state
+ * @run: Move a device to the RUN state / prepare to enable DMA
*
* Context: @lock and @unlock run under pci_tsm_rwsem held for write to
- * sync with TSM unregistration and each other. All operations run under
- * the device lock for mutual exclusion with driver attach and detach.
+ * sync with TSM unregistration and each other. @run runs under
+ * pci_tsm_rwsem held for read. All operations run under the device lock
+ * for mutual exclusion with driver attach and detach.
*/
struct_group_tagged(pci_tsm_devsec_ops, devsec_ops,
struct pci_tsm *(*lock)(struct tsm_dev *tsm_dev,
struct pci_dev *pdev);
void (*unlock)(struct pci_tsm *tsm);
+ int (*run)(struct pci_dev *pdev);
);
int (*refresh_evidence)(struct pci_tsm *tsm, const void *nonce,
@@ -97,6 +100,9 @@ struct pci_tdi {
u32 tdi_id;
};
+/* Private operation acknowledged, future ioremap will use private alias */
+#define PCI_TSM_F_ACCEPT (1UL << 0)
+
/**
* struct pci_tsm - Core TSM context for a given PCIe endpoint
* @pdev: Back ref to device function, distinguishes type of pci_tsm context
@@ -104,6 +110,7 @@ struct pci_tdi {
* @tsm_dev: PCI TEE Security Manager device for Link Confidentiality or Device
* Function Security operations
* @tdi: TDI context established by the @bind link operation
+ * @flags: Convey TDISP availabilty and / or state
* @evidence: cached evidence from SPDM session establishment (connect), or
* TDISP bind (lock)
*
@@ -130,6 +137,7 @@ struct pci_tsm {
struct tsm_dev *tsm_dev;
struct pci_tdi *tdi;
struct device_evidence *evidence;
+ unsigned long flags;
};
/**
diff --git a/drivers/pci/tsm/core.c b/drivers/pci/tsm/core.c
index 590e19f6c2b8..1f09ab4e8d4c 100644
--- a/drivers/pci/tsm/core.c
+++ b/drivers/pci/tsm/core.c
@@ -552,6 +552,89 @@ static ssize_t dsm_show(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR_RO(dsm);
+/**
+ * pci_tsm_accept() - accept a device for private MMIO operation
+ * @pdev: PCI device to accept
+ *
+ * "Accept" transitions the device interface to the RUN state, it is
+ * only suitable to make that transition from a known idle (no active
+ * DMA / MMIO mappings) state. The "driver detached" state is a coarse
+ * way to assert that requirement.
+ */
+static int pci_tsm_accept(struct pci_dev *pdev)
+{
+ int rc;
+
+ ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
+ if ((rc = ACQUIRE_ERR(rwsem_read_intr, &lock)))
+ return rc;
+
+ if (!pdev->tsm)
+ return -EINVAL;
+
+ ACQUIRE(device_intr, dev_lock)(&pdev->dev);
+ if ((rc = ACQUIRE_ERR(device_intr, &dev_lock)))
+ return rc;
+
+ if (pdev->dev.driver)
+ return -EBUSY;
+
+ if (test_bit(PCI_TSM_F_ACCEPT, &pdev->tsm->flags))
+ return 0;
+
+ rc = to_pci_tsm_ops(pdev->tsm)->run(pdev);
+ if (rc)
+ return rc;
+
+ set_bit(PCI_TSM_F_ACCEPT, &pdev->tsm->flags);
+
+ return 0;
+}
+
+static ssize_t accept_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ bool accept;
+ int rc;
+
+ rc = kstrtobool(buf, &accept);
+ if (rc)
+ return rc;
+
+ /*
+ * TDISP can only go from RUN to UNLOCKED/ERROR, so there is no
+ * 'unaccept' verb.
+ */
+ if (!accept)
+ return -EINVAL;
+
+ rc = pci_tsm_accept(pdev);
+ if (rc)
+ return rc;
+
+ return len;
+}
+
+static ssize_t accept_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ bool accept = false;
+ int rc;
+
+ ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
+ if ((rc = ACQUIRE_ERR(rwsem_read_intr, &lock)))
+ return rc;
+
+ /* Unlocked == Unaccepted */
+ if (pdev->tsm)
+ accept = test_bit(PCI_TSM_F_ACCEPT, &pdev->tsm->flags);
+
+ return sysfs_emit(buf, "%d\n", accept);
+}
+static DEVICE_ATTR_RW(accept);
+
/**
* pci_tsm_unlock() - Transition TDI from LOCKED/RUN to UNLOCKED
* @pdev: TDI device to unlock
@@ -568,9 +651,9 @@ static void pci_tsm_unlock(struct pci_dev *pdev)
"unlock attempted on driver attached device\n"))
return;
- device_cc_reject(&pdev->dev);
to_pci_tsm_ops(pdev->tsm)->unlock(pdev->tsm);
pdev->tsm = NULL;
+ /* implicit clear of PCI_TSM_F_ACCEPT */
}
static int pci_tsm_lock(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
@@ -735,7 +818,8 @@ static umode_t pci_tsm_attr_visible(struct kobject *kobj,
}
if (pci_tsm_devsec_group_visible(kobj)) {
- if (attr == &dev_attr_lock.attr ||
+ if (attr == &dev_attr_accept.attr ||
+ attr == &dev_attr_lock.attr ||
attr == &dev_attr_unlock.attr)
return attr->mode;
}
@@ -755,6 +839,7 @@ static struct attribute *pci_tsm_attrs[] = {
&dev_attr_disconnect.attr,
&dev_attr_bound.attr,
&dev_attr_dsm.attr,
+ &dev_attr_accept.attr,
&dev_attr_lock.attr,
&dev_attr_unlock.attr,
NULL
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 12/15] PCI/TSM: Add device interface security DMA enable/disable
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
` (10 preceding siblings ...)
2026-07-05 22:08 ` [PATCH 11/15] PCI/TSM: Add device interface security RUN support Dan Williams
@ 2026-07-05 22:08 ` Dan Williams
2026-07-05 22:08 ` [PATCH 13/15] PCI, device core: Add private memory access for DEVICE_TRUST_TCB Dan Williams
` (3 subsequent siblings)
15 siblings, 0 replies; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-pci, driver-core, ankita, Bjorn Helgaas,
Alexey Kardashevskiy, Xu Yilun, Aneesh Kumar K.V
After the device interface has been switched to the RUN state, the TSM is
still responsible for enabling the device to access private memory. Placing
the DSM into RUN state causes the device to accept T=1 (within TCB) traffic
to its MMIO address space. The enable DMA step has the TSM configure the
IOMMU to accept T=1 traffic to the CC VM's private memory in addition to
all the memory previously accessible with T=0 DMA.
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Alexey Kardashevskiy <aik@amd.com>
Cc: Xu Yilun <yilun.xu@linux.intel.com>
Cc: "Aneesh Kumar K.V" <aneesh.kumar@kernel.org>
Signed-off-by: Dan Williams <djbw@kernel.org>
---
include/linux/pci-tsm.h | 13 +++++++++++++
drivers/pci/tsm/core.c | 42 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 55 insertions(+)
diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
index 371a7e300a64..397e5d8459cb 100644
--- a/include/linux/pci-tsm.h
+++ b/include/linux/pci-tsm.h
@@ -82,6 +82,8 @@ struct pci_tsm_ops {
struct pci_dev *pdev);
void (*unlock)(struct pci_tsm *tsm);
int (*run)(struct pci_dev *pdev);
+ int (*enable_dma)(struct pci_dev *pdev);
+ void (*disable_dma)(struct pci_dev *pdev);
);
int (*refresh_evidence)(struct pci_tsm *tsm, const void *nonce,
@@ -102,6 +104,8 @@ struct pci_tdi {
/* Private operation acknowledged, future ioremap will use private alias */
#define PCI_TSM_F_ACCEPT (1UL << 0)
+/* DMA access to private memory enabled */
+#define PCI_TSM_F_DMA (1UL << 1)
/**
* struct pci_tsm - Core TSM context for a given PCIe endpoint
@@ -247,6 +251,8 @@ int pci_tsm_doe_transfer(struct pci_dev *pdev, u8 type, const void *req,
size_t req_sz, void *resp, size_t resp_sz);
int pci_tsm_bind(struct pci_dev *pdev, struct kvm *kvm, u32 tdi_id);
void pci_tsm_unbind(struct pci_dev *pdev);
+int pci_tsm_enable_dma(struct pci_dev *pdev);
+void pci_tsm_disable_dma(struct pci_dev *pdev);
void pci_tsm_tdi_constructor(struct pci_dev *pdev, struct pci_tdi *tdi,
struct kvm *kvm, u32 tdi_id);
ssize_t pci_tsm_guest_req(struct pci_dev *pdev, enum pci_tsm_req_scope scope,
@@ -269,6 +275,13 @@ static inline int pci_tsm_bind(struct pci_dev *pdev, struct kvm *kvm, u64 tdi_id
{
return -ENXIO;
}
+static inline int pci_tsm_enable_dma(struct pci_dev *pdev)
+{
+ return 0;
+}
+static inline void pci_tsm_disable_dma(struct pci_dev *pdev)
+{
+}
static inline void pci_tsm_unbind(struct pci_dev *pdev)
{
}
diff --git a/drivers/pci/tsm/core.c b/drivers/pci/tsm/core.c
index 1f09ab4e8d4c..372363a39a44 100644
--- a/drivers/pci/tsm/core.c
+++ b/drivers/pci/tsm/core.c
@@ -754,6 +754,48 @@ static ssize_t unlock_store(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR_WO(unlock);
+/* pci_dma_configure() helper to finalize access to private memory. */
+int pci_tsm_enable_dma(struct pci_dev *pdev) __must_hold(&pdev->dev->mutex)
+{
+ struct device *dev = &pdev->dev;
+ const struct pci_tsm_ops *ops;
+ int rc;
+
+ if (dev_WARN_ONCE(dev, !dev->driver,
+ "DMA access only finalized at driver attach\n"))
+ return -ENXIO;
+
+ if (!pdev->tsm)
+ return 0;
+
+ ops = to_pci_tsm_ops(pdev->tsm);
+ if (!ops->enable_dma)
+ return 0;
+
+ rc = ops->enable_dma(pdev);
+ if (rc == 0)
+ set_bit(PCI_TSM_F_DMA, &pdev->tsm->flags);
+ return rc;
+}
+
+/*
+ * pci_dma_cleanup() helper to block private memory access as device is
+ * going idle
+ */
+void pci_tsm_disable_dma(struct pci_dev *pdev) __must_hold(&pdev->dev->mutex)
+{
+ const struct pci_tsm_ops *ops;
+
+ if (!pdev->tsm)
+ return;
+
+ if (!test_and_clear_bit(PCI_TSM_F_DMA, &pdev->tsm->flags))
+ return;
+
+ ops = to_pci_tsm_ops(pdev->tsm);
+ ops->disable_dma(pdev);
+}
+
/* The 'authenticated' attribute is exclusive to the presence of a 'link' TSM */
static bool pci_tsm_link_group_visible(struct kobject *kobj)
{
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 13/15] PCI, device core: Add private memory access for DEVICE_TRUST_TCB
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
` (11 preceding siblings ...)
2026-07-05 22:08 ` [PATCH 12/15] PCI/TSM: Add device interface security DMA enable/disable Dan Williams
@ 2026-07-05 22:08 ` Dan Williams
2026-07-06 12:42 ` Aneesh Kumar K.V
2026-07-05 22:08 ` [PATCH 14/15] PCI/TSM: Create MMIO descriptors via TDISP Report Dan Williams
` (2 subsequent siblings)
15 siblings, 1 reply; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-pci, driver-core, ankita, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Bjorn Helgaas, Dexuan Cui
A device that wants to access private memory needs to have its trust
elevated to DEVICE_TRUST_TCB. That trust is established either at compile
time (unlikely), the bus knows the device is within the TCB to start (some
paravisor setups), or the device is dynamically added to the TCB in
coordination with a TSM driver (primary TDISP use case) and the trust is
elevated by driver match.
When a PCI device is associated with a TSM for security services the low
level TSM driver in the CC VM has the opportunity validate DMA access.
That validation happens at ->dma_configure() time when the device attaches
to a driver. The TSM driver is responsible for proving to the platform TSM
that the VM is enabling DMA with respect to the most recently generated
evidence. If that fails, driver attach fails.
When a PCI device is not associated with a TSM provider for security
services, but the device is trusted there are 3 options.
1/ Arch requires all DMA enable events to be acked by TSM driver
2/ Arch does not require, but admin policy is responsible for knowing which
devices need TSM coordination to become active within the TCB.
3/ Device is approved by a paravisor to operate within the TCB, no TSM
coordination required.
In cases 2 and 3 if the device needed TSM driver coordination, but the TSM
driver or association to the device is missing, it triggers hardware
errors. Those errors are a configuration error that the kernel does not
actively prevent.
Architectures still need to fixup force_dma_unencrypted() to call
device_tcb_trusted() to tell the DMA layer that TCB access is granted.
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: "Rafael J. Wysocki" <rafael@kernel.org>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Dexuan Cui <decui@microsoft.com>
Signed-off-by: Dan Williams <djbw@kernel.org>
---
drivers/base/Kconfig | 19 ++++++++++++++++++-
include/linux/device/trust.h | 4 ++++
drivers/base/trust.c | 8 ++++++++
drivers/pci/pci-driver.c | 25 ++++++++++++++++---------
4 files changed, 46 insertions(+), 10 deletions(-)
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index a4233bdf9804..3597b39ee0e3 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -304,7 +304,17 @@ config DEVICE_TRUST_AUTO
help
Typical historical driver model, devices eagerly attempt to attach to
a driver and deploy all available mechanisms to allow performant
- direct memory access.
+ direct memory access. This trust level does not include TCB privileges.
+
+config DEVICE_TRUST_TCB
+ bool "TCB"
+ depends on EXPERT
+ help
+ Devices are assumed to be within the Trusted Compute Boundary (TCB)
+ with access to private TCB resources by default. This requires buses
+ to have trust awareness and opt devices out of private operation,
+ otherwise TCB integrity may fail the device's access or escalate to
+ terminating the execution context (kill the CC VM).
endchoice
@@ -330,6 +340,13 @@ config BUILTIN_DEVICE_TRUST_ADVERSARY
help
Deploy mitigations in the IOMMU layer and driver to limit access.
+config BUILTIN_DEVICE_TRUST_TCB
+ bool "TCB"
+ depends on EXPERT
+ help
+ Devices bound by built-in drivers are assumed to be within the
+ Trusted Compute Boundary with access to private/encrypted memory.
+
endchoice
endmenu
diff --git a/include/linux/device/trust.h b/include/linux/device/trust.h
index 283d3196e5e6..24951f5c56ba 100644
--- a/include/linux/device/trust.h
+++ b/include/linux/device/trust.h
@@ -13,6 +13,7 @@
* @DEVICE_TRUST_NONE: Blocked when idle, cannot bind
* @DEVICE_TRUST_ADVERSARY: Blocked when idle, constrained when active.
* @DEVICE_TRUST_AUTO: All typical privileges granted
+ * @DEVICE_TRUST_TCB: AUTO privileges + private/encrypted memory access
*
* Devices flagged as adversarial are the ones that can potentially
* execute DMA attacks and similar. They are typically connected through
@@ -25,11 +26,13 @@ enum device_trust {
DEVICE_TRUST_NONE,
DEVICE_TRUST_ADVERSARY,
DEVICE_TRUST_AUTO,
+ DEVICE_TRUST_TCB,
};
#define DEVICE_DEFAULT_TRUST \
(IS_ENABLED(CONFIG_DEVICE_TRUST_NONE) ? DEVICE_TRUST_NONE : \
IS_ENABLED(CONFIG_DEVICE_TRUST_ADVERSARY) ? DEVICE_TRUST_ADVERSARY : \
+ IS_ENABLED(CONFIG_DEVICE_TRUST_TCB) ? DEVICE_TRUST_TCB : \
DEVICE_TRUST_AUTO)
struct device;
@@ -37,6 +40,7 @@ struct device_driver;
#ifdef CONFIG_DEVICE_TRUST
bool device_untrusted(struct device *dev);
+bool device_tcb_trusted(struct device *dev);
void module_driver_trust(struct module *mod, const char *val);
void module_driver_trust_init(struct module *mod, bool require_trust);
#else
diff --git a/drivers/base/trust.c b/drivers/base/trust.c
index 8efbe5c51250..21d374affbba 100644
--- a/drivers/base/trust.c
+++ b/drivers/base/trust.c
@@ -23,11 +23,18 @@ bool device_untrusted(struct device *dev)
return dev->bus_trust && dev->bus_trust <= DEVICE_TRUST_ADVERSARY;
}
+bool device_tcb_trusted(struct device *dev)
+{
+ return dev->p->trust >= DEVICE_TRUST_TCB;
+}
+
/* Driver trust policy requires modules, builtin drivers always attach */
static enum device_trust builtin_driver_trust(void)
{
if (IS_ENABLED(CONFIG_BUILTIN_DEVICE_TRUST_ADVERSARY))
return DEVICE_TRUST_ADVERSARY;
+ else if (IS_ENABLED(CONFIG_BUILTIN_DEVICE_TRUST_TCB))
+ return DEVICE_TRUST_TCB;
return DEVICE_TRUST_AUTO;
}
@@ -61,6 +68,7 @@ static const char * const device_trust_names[] = {
[DEVICE_TRUST_NONE] = "none",
[DEVICE_TRUST_ADVERSARY] = "adversary",
[DEVICE_TRUST_AUTO] = "auto",
+ [DEVICE_TRUST_TCB] = "tcb",
};
static enum device_trust device_trust_parse(const char *name)
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index f36778e62ac1..e6a4bc3ca9b0 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -19,6 +19,7 @@
#include <linux/kexec.h>
#include <linux/of_device.h>
#include <linux/acpi.h>
+#include <linux/pci-tsm.h>
#include <linux/dma-map-ops.h>
#include <linux/iommu.h>
#include "pci.h"
@@ -1668,10 +1669,11 @@ static int pci_bus_num_vf(struct device *dev)
static int pci_dma_configure(struct device *dev)
{
const struct device_driver *drv = READ_ONCE(dev->driver);
- struct device *bridge;
+ struct pci_dev *pdev = to_pci_dev(dev);
int ret = 0;
- bridge = pci_get_host_bridge_device(to_pci_dev(dev));
+ struct device *bridge __free(put_device) =
+ pci_get_host_bridge_device(pdev);
if (IS_ENABLED(CONFIG_OF) && bridge->parent &&
bridge->parent->of_node) {
@@ -1688,16 +1690,21 @@ static int pci_dma_configure(struct device *dev)
* the standard ACS capability but still support ACS via those
* quirks.
*/
- pci_enable_acs(to_pci_dev(dev));
-
- pci_put_host_bridge_device(bridge);
+ pci_enable_acs(pdev);
/* @drv may not be valid when we're called from the IOMMU layer */
- if (!ret && drv && !to_pci_driver(drv)->driver_managed_dma) {
+ if (ret || !drv)
+ return ret;
+
+ if (device_tcb_trusted(dev))
+ ret = pci_tsm_enable_dma(pdev);
+
+ if (!ret && !to_pci_driver(drv)->driver_managed_dma)
ret = iommu_device_use_default_domain(dev);
- if (ret)
- arch_teardown_dma_ops(dev);
- }
+
+ /* undo {of,acpi}_dma_configure() */
+ if (ret)
+ arch_teardown_dma_ops(dev);
return ret;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 14/15] PCI/TSM: Create MMIO descriptors via TDISP Report
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
` (12 preceding siblings ...)
2026-07-05 22:08 ` [PATCH 13/15] PCI, device core: Add private memory access for DEVICE_TRUST_TCB Dan Williams
@ 2026-07-05 22:08 ` Dan Williams
2026-07-05 22:08 ` [PATCH 15/15] PCI/TSM: Add relative MMIO offset support? Dan Williams
2026-07-06 12:51 ` [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Jason Gunthorpe
15 siblings, 0 replies; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco; +Cc: linux-pci, driver-core, ankita, Arnd Bergmann, Xu Yilun
After pci_tsm_bind() and pci_tsm_lock() the low level TSM driver is
expected to populate PCI_TSM_EVIDENCE_TYPE_REPORT in its evidence store.
This report is defined by the TDISP GET_DEVICE_INTERFACE_REPORT response
payload.
Add a helper to create encrypted MMIO descriptors from that report
data. With those descriptors the TSM driver can use pci_tsm_mmio_setup() to
inform ioremap() how to map the device per the device's expectations. The
VM is expected to validate the interface with the relying party before
accepting the device for operation.
The helper also provides the obfuscated starting address for each encrypted
MMIO range as the VM is never disclosed on the hpa that correlates to the
gpa of the device's mmio. The obfuscated address is BAR aligned.
Based on an original patch by Aneesh [1]
Cc: Arnd Bergmann <arnd@arndb.de>
Link: https://lore.kernel.org/linux-coco/20251117140007.122062-8-aneesh.kumar@kernel.org/ [1]
Co-developed-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Dan Williams <djbw@kernel.org>
---
include/linux/ioport.h | 2 +
include/linux/pci-tsm.h | 35 +++++++
drivers/pci/tsm/core.c | 224 ++++++++++++++++++++++++++++++++++++++++
kernel/resource.c | 8 ++
4 files changed, 269 insertions(+)
diff --git a/include/linux/ioport.h b/include/linux/ioport.h
index f7930b3dfd0a..122f1eefb4b9 100644
--- a/include/linux/ioport.h
+++ b/include/linux/ioport.h
@@ -144,6 +144,7 @@ enum {
IORES_DESC_RESERVED = 7,
IORES_DESC_SOFT_RESERVED = 8,
IORES_DESC_CXL = 9,
+ IORES_DESC_ENCRYPTED = 10,
};
/*
@@ -240,6 +241,7 @@ struct resource_constraint {
extern struct resource ioport_resource;
extern struct resource iomem_resource;
extern struct resource soft_reserve_resource;
+extern struct resource encrypted_iomem_resource;
extern struct resource *request_resource_conflict(struct resource *root, struct resource *new);
extern int request_resource(struct resource *root, struct resource *new);
diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
index 397e5d8459cb..6d5fadd79360 100644
--- a/include/linux/pci-tsm.h
+++ b/include/linux/pci-tsm.h
@@ -156,12 +156,42 @@ struct pci_tsm_pf0 {
struct pci_doe_mb *doe_mb;
};
+/**
+ * struct pci_tsm_mmio_entry - an encrypted MMIO range
+ * @res: MMIO address range (typically Guest Physical Address, GPA)
+ * @tsm_offset: Host Physical Address, HPA obfuscation offset added by the TSM.
+ * Translates report addresses to GPA.
+ */
+struct pci_tsm_mmio_entry {
+ struct resource res;
+ u64 tsm_offset;
+};
+
+struct pci_tsm_mmio {
+ int nr;
+ struct pci_tsm_mmio_entry mmio[];
+};
+
+static inline struct pci_tsm_mmio_entry *
+pci_tsm_mmio_entry(struct pci_tsm_mmio *mmio, int idx)
+{
+ return &mmio->mmio[idx];
+}
+
+static inline struct resource *pci_tsm_mmio_resource(struct pci_tsm_mmio *mmio,
+ int idx)
+{
+ return &mmio->mmio[idx].res;
+}
+
/**
* struct pci_tsm_devsec - context for tracking private/accepted PCI resources
* @base_tsm: generic core "tsm" context
+ * @mmio: encrypted MMIO resources for this assigned device
*/
struct pci_tsm_devsec {
struct pci_tsm base_tsm;
+ struct pci_tsm_mmio *mmio;
};
/* physical function0 and capable of 'connect' */
@@ -263,6 +293,11 @@ static inline const struct pci_tsm_ops *to_pci_tsm_ops(struct pci_tsm *tsm)
return tsm->tsm_dev->pci_ops;
}
struct pci_tsm_devsec *to_pci_tsm_devsec(struct pci_tsm *tsm);
+int pci_tsm_mmio_setup(struct pci_dev *pdev, struct pci_tsm_mmio *mmio);
+void pci_tsm_mmio_teardown(struct pci_tsm_mmio *mmio);
+
+struct pci_tsm_mmio *pci_tsm_mmio_alloc(struct pci_dev *pdev);
+int pci_tsm_mmio_free(struct pci_dev *pdev, struct pci_tsm_mmio *mmio);
#else
static inline int pci_tsm_register(struct tsm_dev *tsm_dev)
{
diff --git a/drivers/pci/tsm/core.c b/drivers/pci/tsm/core.c
index 372363a39a44..9ac216ad896d 100644
--- a/drivers/pci/tsm/core.c
+++ b/drivers/pci/tsm/core.c
@@ -15,6 +15,7 @@
#include <linux/pci-tsm.h>
#include <linux/sysfs.h>
#include <linux/tsm.h>
+#include <linux/unaligned.h>
#include <linux/xarray.h>
#include "../pci.h"
@@ -552,6 +553,229 @@ static ssize_t dsm_show(struct device *dev, struct device_attribute *attr,
}
static DEVICE_ATTR_RO(dsm);
+static void mmio_teardown(struct pci_tsm_mmio *mmio, int nr)
+{
+ while (nr--)
+ remove_resource(pci_tsm_mmio_resource(mmio, nr));
+}
+
+/**
+ * pci_tsm_mmio_setup() - mark device MMIO as encrypted in iomem
+ * @pdev: device owner of MMIO resources
+ * @mmio: container of an array of resources to mark encrypted
+ */
+int pci_tsm_mmio_setup(struct pci_dev *pdev, struct pci_tsm_mmio *mmio)
+{
+ int i;
+
+ device_lock_assert(&pdev->dev);
+ if (pdev->dev.driver)
+ return -EBUSY;
+
+ for (i = 0; i < mmio->nr; i++) {
+ struct resource *res = pci_tsm_mmio_resource(mmio, i);
+ int j;
+
+ if (resource_size(res) == 0 || !res->end)
+ break;
+
+ /* Only require the caller to set the range, init remainder */
+ *res = DEFINE_RES_NAMED_DESC(res->start, resource_size(res),
+ pci_name(pdev), IORESOURCE_MEM,
+ IORES_DESC_ENCRYPTED);
+
+ for (j = 0; j < PCI_NUM_RESOURCES; j++)
+ if (resource_contains(pci_resource_n(pdev, j), res))
+ break;
+
+ /* Request is outside of device MMIO */
+ if (j >= PCI_NUM_RESOURCES)
+ break;
+
+ if (insert_resource(&encrypted_iomem_resource, res) != 0)
+ break;
+ }
+
+ if (i >= mmio->nr)
+ return 0;
+
+ mmio_teardown(mmio, i);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(pci_tsm_mmio_setup);
+
+void pci_tsm_mmio_teardown(struct pci_tsm_mmio *mmio)
+{
+ mmio_teardown(mmio, mmio->nr);
+}
+EXPORT_SYMBOL_GPL(pci_tsm_mmio_teardown);
+
+/*
+ * PCIe ECN TEE Device Interface Security Protocol (TDISP)
+ *
+ * Device Interface Report data object layout as defined by PCIe r7.0 section
+ * 11.3.11
+ */
+#define PCI_TSM_DEVIF_REPORT_MMIO_ATTR_MSIX_TABLE BIT(0)
+#define PCI_TSM_DEVIF_REPORT_MMIO_ATTR_MSIX_PBA BIT(1)
+#define PCI_TSM_DEVIF_REPORT_MMIO_ATTR_IS_NON_TEE BIT(2)
+#define PCI_TSM_DEVIF_REPORT_MMIO_ATTR_IS_UPDATABLE BIT(3)
+#define PCI_TSM_DEVIF_REPORT_MMIO_ATTR_RANGE_ID GENMASK(31, 16)
+
+/* An interface report 'pfn' is 4K in size */
+struct pci_tsm_devif_mmio {
+ __le64 phys;
+ __le32 nr_pfns;
+ __le32 attributes;
+};
+
+struct pci_tsm_devif_report {
+ __le16 interface_info;
+ __le16 reserved;
+ __le16 msi_x_message_control;
+ __le16 lnr_control;
+ __le32 tph_control;
+ __le32 mmio_range_count;
+ struct pci_tsm_devif_mmio mmio[];
+};
+
+/**
+ * pci_tsm_mmio_alloc() - allocate encrypted MMIO range descriptor
+ * @pdev: device owner of MMIO ranges
+ *
+ * Return: the encrypted MMIO range descriptor on success, NULL on failure
+ *
+ * Assumes that this is called within the live lifetime of a PCI device's
+ * association with a low level TSM.
+ */
+struct pci_tsm_mmio *pci_tsm_mmio_alloc(struct pci_dev *pdev)
+{
+ struct device_evidence *evidence = pdev->tsm->evidence;
+ u64 reporting_bar_base, last_reporting_end;
+ struct device_evidence_object *report_obj;
+ const struct pci_tsm_devif_report *report;
+ u32 mmio_range_count;
+ int last_bar = -1;
+ int i;
+
+ if (!evidence)
+ return NULL;
+
+ report_obj = &evidence->obj[DEVICE_EVIDENCE_TYPE_REPORT];
+
+ guard(rwsem_read)(&evidence->lock);
+ if (report_obj->len < sizeof(struct pci_tsm_devif_report))
+ return NULL;
+
+ report = report_obj->data;
+ mmio_range_count = __le32_to_cpu(report->mmio_range_count);
+
+ /* check that the report object is self-consistent on mmio entries */
+ if (report_obj->len < struct_size(report, mmio, mmio_range_count))
+ return NULL;
+
+ /* create pci_tsm_mmio descriptors from the report data */
+ struct pci_tsm_mmio *mmio __free(kfree) =
+ kzalloc_flex(*mmio, mmio, mmio_range_count);
+ if (!mmio)
+ return NULL;
+
+ for (i = 0; i < mmio_range_count; i++) {
+ u64 range_off;
+ struct range range;
+ const struct pci_tsm_devif_mmio *mmio_data = &report->mmio[i];
+ struct pci_tsm_mmio_entry *entry =
+ pci_tsm_mmio_entry(mmio, mmio->nr);
+ u64 tsm_offset = __le64_to_cpu(mmio_data->phys);
+ u64 size = __le32_to_cpu(mmio_data->nr_pfns) * SZ_4K;
+ u32 attr = __le32_to_cpu(mmio_data->attributes);
+ int bar = FIELD_GET(PCI_TSM_DEVIF_REPORT_MMIO_ATTR_RANGE_ID,
+ attr);
+
+ if (bar >= PCI_STD_NUM_BARS ||
+ !(pci_resource_flags(pdev, bar) & IORESOURCE_MEM) ||
+ (pci_resource_flags(pdev, bar) & IORESOURCE_UNSET)) {
+ pci_dbg(pdev, "Invalid reporting bar ID %d\n", bar);
+ return NULL;
+ }
+
+ if (last_bar > bar) {
+ pci_dbg(pdev, "Reporting bar ID not in ascending order\n");
+ return NULL;
+ }
+
+ if (last_bar < bar) {
+ unsigned long mask = pci_resource_len(pdev, bar) - 1;
+
+ /* Transition to a new bar */
+ last_bar = bar;
+
+ /*
+ * Determine the obfuscated base of the BAR. BAR
+ * offsets are never obfuscated.
+ */
+ reporting_bar_base = tsm_offset & ~mask;
+ } else if (tsm_offset < last_reporting_end) {
+ pci_dbg(pdev, "Reporting ranges within BAR not in ascending order\n");
+ return NULL;
+ }
+
+ /* Per spec the tsm_offset never results in overflow / underflow */
+ last_reporting_end = tsm_offset + size;
+ if (last_reporting_end < tsm_offset) {
+ pci_dbg(pdev, "Reporting range overflow\n");
+ return NULL;
+ }
+
+ range_off = tsm_offset - reporting_bar_base;
+ if (pci_resource_len(pdev, bar) < range_off + size) {
+ pci_dbg(pdev, "Reporting range larger than BAR size\n");
+ return NULL;
+ }
+
+ range.start = pci_resource_start(pdev, bar) + range_off;
+ range.end = range.start + size - 1;
+
+ /* Only record the TEE ranges for later consideration by ioremap() */
+ if (FIELD_GET(PCI_TSM_DEVIF_REPORT_MMIO_ATTR_IS_NON_TEE,
+ attr)) {
+ pci_dbg(pdev, "Skipping non-TEE range, BAR%d %pra\n",
+ bar, &range);
+ continue;
+ }
+
+ entry->res.start = range.start;
+ entry->res.end = range.end;
+ entry->tsm_offset = tsm_offset;
+ mmio->nr++;
+ }
+
+ return_ptr(mmio);
+}
+EXPORT_SYMBOL_GPL(pci_tsm_mmio_alloc);
+
+/**
+ * pci_tsm_mmio_free() - free a pci_tsm_mmio instance
+ * @pdev: device owner of MMIO ranges
+ * @mmio: instance to free
+ *
+ * Returns 0 if @mmio was idle on entry, -EBUSY otherwise
+ */
+int pci_tsm_mmio_free(struct pci_dev *pdev, struct pci_tsm_mmio *mmio)
+{
+ for (int i = 0; i < mmio->nr; i++) {
+ struct resource *res = pci_tsm_mmio_resource(mmio, i);
+
+ if (dev_WARN_ONCE(&pdev->dev, resource_assigned(res),
+ "MMIO resource still assigned %pr\n", res))
+ return -EBUSY;
+ }
+ kfree(mmio);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pci_tsm_mmio_free);
+
/**
* pci_tsm_accept() - accept a device for private MMIO operation
* @pdev: PCI device to accept
diff --git a/kernel/resource.c b/kernel/resource.c
index 3d17e3196a3e..ba91ce43ff7a 100644
--- a/kernel/resource.c
+++ b/kernel/resource.c
@@ -56,6 +56,14 @@ struct resource soft_reserve_resource = {
.flags = IORESOURCE_MEM,
};
+struct resource encrypted_iomem_resource = {
+ .name = "Encrypted MMIO",
+ .start = 0,
+ .end = -1,
+ .desc = IORES_DESC_ENCRYPTED,
+ .flags = IORESOURCE_MEM,
+};
+
static DEFINE_RWLOCK(resource_lock);
/*
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 15/15] PCI/TSM: Add relative MMIO offset support?
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
` (13 preceding siblings ...)
2026-07-05 22:08 ` [PATCH 14/15] PCI/TSM: Create MMIO descriptors via TDISP Report Dan Williams
@ 2026-07-05 22:08 ` Dan Williams
2026-07-06 12:51 ` [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Jason Gunthorpe
15 siblings, 0 replies; 18+ messages in thread
From: Dan Williams @ 2026-07-05 22:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-pci, driver-core, ankita, Alexey Kardashevskiy, Xu Yilun,
Aneesh Kumar K.V
The RMM specification, DEN0137-2.0-bet2 section A9.6.2 "Realm validation of
device memory mappings" documents the expectation that the
MMIO_REPORTING_OFFSET chosen for TDISP Interface Reports is always BAR
aligned.
Ideally this change is not needed and all implementations share the same
expectation.
If this semantic is already shipping in production and/or the PCI-SIG
clarifies that an implementation can hold this assumption then Linux will
need to ask the TSM drivers for this hint.
Cc: Alexey Kardashevskiy <aik@amd.com>
Cc: Xu Yilun <yilun.xu@linux.intel.com>
Cc: "Aneesh Kumar K.V" <aneesh.kumar@kernel.org>
Signed-off-by: Dan Williams <djbw@kernel.org>
---
include/linux/pci-tsm.h | 15 ++++++++++++++-
drivers/pci/tsm/core.c | 14 ++++++++++----
2 files changed, 24 insertions(+), 5 deletions(-)
diff --git a/include/linux/pci-tsm.h b/include/linux/pci-tsm.h
index 6d5fadd79360..be9f78ca2c1a 100644
--- a/include/linux/pci-tsm.h
+++ b/include/linux/pci-tsm.h
@@ -296,7 +296,20 @@ struct pci_tsm_devsec *to_pci_tsm_devsec(struct pci_tsm *tsm);
int pci_tsm_mmio_setup(struct pci_dev *pdev, struct pci_tsm_mmio *mmio);
void pci_tsm_mmio_teardown(struct pci_tsm_mmio *mmio);
-struct pci_tsm_mmio *pci_tsm_mmio_alloc(struct pci_dev *pdev);
+/**
+ * enum tdisp_offset_scheme - MMIO_REPORTING_OFFSET assumptions
+ * @TDISP_OFFSET_BAR_ALIGN: mask by bar size to recover offset
+ * @TDISP_OFFSET_RELATIVE: first mmio report per bar is bar-offset-0
+ *
+ * A TSM driver may know that the default TDISP_OFFSET_BAR_ALIGN
+ * assumption is being violated.
+ */
+enum tdisp_offset_scheme {
+ TDISP_OFFSET_BAR_ALIGN,
+ TDISP_OFFSET_RELATIVE,
+};
+struct pci_tsm_mmio *pci_tsm_mmio_alloc(struct pci_dev *pdev,
+ enum tdisp_offset_scheme scheme);
int pci_tsm_mmio_free(struct pci_dev *pdev, struct pci_tsm_mmio *mmio);
#else
static inline int pci_tsm_register(struct tsm_dev *tsm_dev)
diff --git a/drivers/pci/tsm/core.c b/drivers/pci/tsm/core.c
index 9ac216ad896d..19ad35f2da4a 100644
--- a/drivers/pci/tsm/core.c
+++ b/drivers/pci/tsm/core.c
@@ -643,13 +643,15 @@ struct pci_tsm_devif_report {
/**
* pci_tsm_mmio_alloc() - allocate encrypted MMIO range descriptor
* @pdev: device owner of MMIO ranges
+ * @scheme: allow the low level TSM driver to hint the offset calc scheme
*
* Return: the encrypted MMIO range descriptor on success, NULL on failure
*
* Assumes that this is called within the live lifetime of a PCI device's
* association with a low level TSM.
*/
-struct pci_tsm_mmio *pci_tsm_mmio_alloc(struct pci_dev *pdev)
+struct pci_tsm_mmio *pci_tsm_mmio_alloc(struct pci_dev *pdev,
+ enum tdisp_offset_scheme scheme)
{
struct device_evidence *evidence = pdev->tsm->evidence;
u64 reporting_bar_base, last_reporting_end;
@@ -712,10 +714,14 @@ struct pci_tsm_mmio *pci_tsm_mmio_alloc(struct pci_dev *pdev)
last_bar = bar;
/*
- * Determine the obfuscated base of the BAR. BAR
- * offsets are never obfuscated.
+ * Either the first range per bar always maps
+ * the start of the BAR, or the reporting_offset
+ * is BAR size aligned.
*/
- reporting_bar_base = tsm_offset & ~mask;
+ if (scheme == TDISP_OFFSET_RELATIVE)
+ reporting_bar_base = tsm_offset;
+ else
+ reporting_bar_base = tsm_offset & ~mask;
} else if (tsm_offset < last_reporting_end) {
pci_dbg(pdev, "Reporting ranges within BAR not in ascending order\n");
return NULL;
--
2.54.0
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH 13/15] PCI, device core: Add private memory access for DEVICE_TRUST_TCB
2026-07-05 22:08 ` [PATCH 13/15] PCI, device core: Add private memory access for DEVICE_TRUST_TCB Dan Williams
@ 2026-07-06 12:42 ` Aneesh Kumar K.V
0 siblings, 0 replies; 18+ messages in thread
From: Aneesh Kumar K.V @ 2026-07-06 12:42 UTC (permalink / raw)
To: Dan Williams, linux-coco
Cc: linux-pci, driver-core, ankita, Greg Kroah-Hartman,
Rafael J. Wysocki, Danilo Krummrich, Bjorn Helgaas, Dexuan Cui
Dan Williams <djbw@kernel.org> writes:
> A device that wants to access private memory needs to have its trust
> elevated to DEVICE_TRUST_TCB. That trust is established either at compile
> time (unlikely), the bus knows the device is within the TCB to start (some
> paravisor setups), or the device is dynamically added to the TCB in
> coordination with a TSM driver (primary TDISP use case) and the trust is
> elevated by driver match.
>
Do we have the last case, where the device is dynamically added to the
TCB in coordination with a TSM, implemented in this series? Do we expect
the CCA driver to set that up?
>
> When a PCI device is associated with a TSM for security services the low
> level TSM driver in the CC VM has the opportunity validate DMA access.
> That validation happens at ->dma_configure() time when the device attaches
> to a driver. The TSM driver is responsible for proving to the platform TSM
> that the VM is enabling DMA with respect to the most recently generated
> evidence. If that fails, driver attach fails.
>
> When a PCI device is not associated with a TSM provider for security
> services, but the device is trusted there are 3 options.
>
> 1/ Arch requires all DMA enable events to be acked by TSM driver
>
> 2/ Arch does not require, but admin policy is responsible for knowing which
> devices need TSM coordination to become active within the TCB.
>
> 3/ Device is approved by a paravisor to operate within the TCB, no TSM
> coordination required.
>
> In cases 2 and 3 if the device needed TSM driver coordination, but the TSM
> driver or association to the device is missing, it triggers hardware
> errors. Those errors are a configuration error that the kernel does not
> actively prevent.
>
> Architectures still need to fixup force_dma_unencrypted() to call
> device_tcb_trusted() to tell the DMA layer that TCB access is granted.
>
-aneesh
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP)
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
` (14 preceding siblings ...)
2026-07-05 22:08 ` [PATCH 15/15] PCI/TSM: Add relative MMIO offset support? Dan Williams
@ 2026-07-06 12:51 ` Jason Gunthorpe
15 siblings, 0 replies; 18+ messages in thread
From: Jason Gunthorpe @ 2026-07-06 12:51 UTC (permalink / raw)
To: Dan Williams
Cc: linux-coco, linux-pci, driver-core, ankita, Aaron Tomlin,
Alexey Kardashevskiy, Alistair Francis, Aneesh Kumar K.V,
Arnd Bergmann, Bjorn Helgaas, Daniel Gomez, Danilo Krummrich,
Dexuan Cui, Donald Hunter, Greg Kroah-Hartman, Jakub Kicinski,
Luis Chamberlain, Lukas Wunner, Petr Pavlu, Rafael J. Wysocki,
Robin Murphy, Sami Tolvanen, Samuel Ortiz, Saravana Kannan,
Will Deacon, Xu Yilun
On Sun, Jul 05, 2026 at 03:08:04PM -0700, Dan Williams wrote:
> * NONE: no usage of the device unless the trust is explicitly overridden
> by user policy specified via a driver flag, module flag, or uapi (TBD).
>
> * ADVERSARY: needs acknowledgement from the bus and IOMMU / DMA layers
> that the device is limited to strict IOMMU translation behavior. Drivers
> can use this as a signal to limit functionality. This designation
> implies follow-on IOMMU and bus enabling work for features like
> arranging for the device to attach to a blocked IOMMU domain when
> detached from a driver.
>
> * AUTO: typical / historical Linux driver model.
>
> * TCB: a trust level that only exists in Confidential Computing
> environments. When acked by the IOMMU / DMA layer it enables the device
> to issue direct-DMA to private/encrypted addresses or otherwise attach to
> a secure vIOMMU within the TCB.
I'm not sure I entirely like this one, certainly it needs to be
possible to have both T=1 and ADVERSARY together.
Arguably the T=0/1 decision is much more like link encryption, it
effects the transport of the DMA into the CPU. That it also impacts
how the VIOMMU works is the only thing that make it sort of
trust-like in this model.
I'd also argue this list is missing "FULL" trust, which is the
historical Linux behavior for a normal device. AUTO should be
selecting between FULL/ADVERSARY based on things like the ACPI/etc as
it does today.
In a CC VM if we have a T=0 device we probably want to operate it with
ADVERSARY (there is no T=0 VIOMMU so this is equivilent to FULL)
For a T=1 device we need to have the choice of FULL or ADVERSARY. When
a VIOMMU is present ADVERSARY will further restrict the T=1 traffic as
a defense in depth.
So, it is really necessary to have "TCB" here?
If the trust level is reduced to just be a command to the kernel how
it should operate the device then it would be up to userspace to
confirm things like T=1 before setting the trust. The kernel would
have to set the trust to NONE when security sensitive changes are
detected.
If we still want a kernel-side policy gate, eg kernel will not
progress unless T=1 is met, then I think that would be better as an
independent pair'd policy field, ie
trust=FULL when policy=T=1, LINK_ENCRYPTION=1, etc
As bundling the two into "TCB" is probably going to turn problematic
as we have more and more combinations of policy conditions.
Jason
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-07-06 12:51 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-05 22:08 [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Dan Williams
2026-07-05 22:08 ` [PATCH 01/15] netlink: specs: Introduce multi-message blobs for SPDM Dan Williams
2026-07-05 22:08 ` [PATCH 02/15] tools: ynl: Teach pyynl to handle blobs Dan Williams
2026-07-05 22:08 ` [PATCH 03/15] tools: ynl: Teach ynl_gen_c to validate and dump 'blob' attributes Dan Williams
2026-07-05 22:08 ` [PATCH 04/15] device core: Introduce "device evidence" over netlink Dan Williams
2026-07-05 22:08 ` [PATCH 05/15] device core: Add "device evidence" 'validate' command Dan Williams
2026-07-05 22:08 ` [PATCH 06/15] PCI/TSM: Add device evidence support Dan Williams
2026-07-05 22:08 ` [PATCH 07/15] modules: Document the global async_probe parameter Dan Williams
2026-07-05 22:08 ` [PATCH 08/15] device core: Initial device trust infrastructure Dan Williams
2026-07-05 22:08 ` [PATCH 09/15] PCI, device core: Move "untrusted" concept to DEVICE_TRUST_ADVERSARY Dan Williams
2026-07-05 22:08 ` [PATCH 10/15] PCI/TSM: Add device interface security LOCKED support Dan Williams
2026-07-05 22:08 ` [PATCH 11/15] PCI/TSM: Add device interface security RUN support Dan Williams
2026-07-05 22:08 ` [PATCH 12/15] PCI/TSM: Add device interface security DMA enable/disable Dan Williams
2026-07-05 22:08 ` [PATCH 13/15] PCI, device core: Add private memory access for DEVICE_TRUST_TCB Dan Williams
2026-07-06 12:42 ` Aneesh Kumar K.V
2026-07-05 22:08 ` [PATCH 14/15] PCI/TSM: Create MMIO descriptors via TDISP Report Dan Williams
2026-07-05 22:08 ` [PATCH 15/15] PCI/TSM: Add relative MMIO offset support? Dan Williams
2026-07-06 12:51 ` [PATCH 00/15] Device Evidence and Trust for PCI Security Protocol (TDISP) Jason Gunthorpe
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox