device core: Introduce confidential device acceptance
Of the many problems to solve with PCIe Trusted Execution Environment
Device Interface Security Protocol (TDISP) support, this is among the most
fraught. New device core infrastructure demands a high degree of scrutiny
especially when touching the long standing kernel policy that the kernel
trusts devices by default. Previous adjacent proposals in this space (e.g.
device filter, no bounce buffer flag...) have not moved forward.
So, what is this new 'struct device_private' mechanism, how is it different
from previous attempts, and why not a bus-device-type specific mechanism
(e.g. pci_dev::untrusted, usb_device::authorized, tb_switch::authorized,
etc...)?
TEE acceptance is not a state that random modules should be allowed to
change in the common case. A device entering the accepted state is a
violent operation. Pre-existing MMIO and DMA mappings can not survive this
event. The device_cc_accept() and device_cc_reject() helpers (where "cc" ==
"confidential computing") coordinate with driver attachment and are only
meant for core-kernel bus drivers like the PCI core.
For drivers the default expectation is unenlightened. I.e. drivers that are
identical to the non-TDISP case. There are however cases where the device
needs a driver to perform configuration prior to userspace performing the
lock+accept operations to shift the device into operation within the TCB.
For that case an enlightened driver needs the ability to determine if the
device is already "lock+accept" ready. The device_cc_probe() interface
exists for that check.
When the device enters the TEE, other subsystems need to behave
differently. For example, the IOMMU/DMA mapping subsystem needs to switch
DMA mapping requests from SWIOTLB bounce buffering to direct-DMA to private
memory. That device state is communicated via device_cc_accepted() in a
common way.
The observation is that PCI is not the only bus that has designs on
interacting with a TEE acceptance state. The "adjacent proposals" mentioned
before include platform firmware and embedded buses that want to accept
devices into the TEE. A bus-type-specific flag would be an ongoing
maintenance burden for each new bus that adds TEE acceptance support.
Cc: Christoph Hellwig <hch@lst.de>
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Cc: Marek Szyprowski <m.szyprowski@samsung.com>
Cc: Robin Murphy <robin.murphy@arm.com>
Cc: Roman Kisel <romank@linux.microsoft.com>
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: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: "Rafael J. Wysocki" <rafael@kernel.org>
Cc: Danilo Krummrich <dakr@kernel.org>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index 1786d87..d4743bf 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -249,4 +249,8 @@
command line option on every system/board your kernel is expected to
work on.
+config CONFIDENTIAL_DEVICES
+ depends on ARCH_HAS_CC_PLATFORM
+ bool
+
endmenu
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index 8074a10..e11052c 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -27,6 +27,7 @@
obj-$(CONFIG_GENERIC_ARCH_TOPOLOGY) += arch_topology.o
obj-$(CONFIG_GENERIC_ARCH_NUMA) += arch_numa.o
obj-$(CONFIG_ACPI) += physical_location.o
+obj-$(CONFIG_CONFIDENTIAL_DEVICES) += coco.o
obj-y += test/
diff --git a/drivers/base/base.h b/drivers/base/base.h
index cd5f2cd..d9d5b8e 100644
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -106,8 +106,13 @@ struct driver_private {
* @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.
+ * @cc_accepted: track the TEE acceptance state of the device for deferred
+ * probing, MMIO mapping type, and SWIOTLB bypass for private memory DMA.
*
* Nothing outside of the driver core should ever touch these fields.
+ *
+ * All bitfield flags are manipulated under device_lock() to avoid
+ * read-modify-write collisions.
*/
struct device_private {
struct klist klist_children;
@@ -120,6 +125,10 @@ struct device_private {
char *deferred_probe_reason;
struct device *device;
u8 dead:1;
+#ifdef CONFIG_CONFIDENTIAL_DEVICES
+ u8 cc_accepted:1;
+#endif
+
};
#define to_device_private_parent(obj) \
container_of(obj, struct device_private, knode_parent)
diff --git a/drivers/base/coco.c b/drivers/base/coco.c
new file mode 100644
index 0000000..d7cd0a2
--- /dev/null
+++ b/drivers/base/coco.c
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2025 Intel Corporation. All rights reserved. */
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/lockdep.h>
+#include "base.h"
+
+/*
+ * Confidential devices implement encrypted + integrity protected MMIO and have
+ * the ability to issue DMA to encrypted + integrity protected System RAM. The
+ * device_cc_*() helpers aid buses in setting the acceptance state, drivers in
+ * preparing and probing the acceptance state, and other kernel subsystem in
+ * augmenting behavior in the presence of accepted devices (e.g.
+ * ioremap_encrypted()).
+ */
+
+/**
+ * device_cc_accept(): Mark a device as accepted for TEE operation
+ * @dev: device to accept
+ *
+ * Confidential bus drivers use this helper to accept devices at initial
+ * enumeration, or dynamically one attestation has been performed.
+ *
+ * Given that moving a device into confidential / private operation implicates
+ * any of MMIO mapping attributes, physical address, and IOMMU mappings this
+ * transition must be done while the device is idle (driver detached).
+ *
+ * This is an internal helper for buses not device drivers.
+ */
+int device_cc_accept(struct device *dev)
+{
+ lockdep_assert_held(&dev->mutex);
+
+ if (dev->driver)
+ return -EBUSY;
+ dev->p->cc_accepted = 1;
+
+ return 0;
+}
+
+int device_cc_reject(struct device *dev)
+{
+ lockdep_assert_held(&dev->mutex);
+
+ if (dev->driver)
+ return -EBUSY;
+ dev->p->cc_accepted = 0;
+
+ return 0;
+}
+
+/**
+ * device_cc_accepted(): Get the TEE operational state of a device
+ * @dev: device to check
+ *
+ * Various subsystems, mm/ioremap, drivers/iommu, drivers/vfio, kernel/dma...
+ * need to augment their behavior in the presence of confidential devices. This
+ * simple, deliberately not exported, helper is for those built-in consumers.
+ *
+ * This is an internal helper for subsystems not device drivers. See
+ * device_cc_probe() for an interface for device drivers to probe acceptance
+ * state.
+ */
+bool device_cc_accepted(struct device *dev)
+{
+ return dev->p->cc_accepted;
+}
+
+/**
+ * device_cc_probe(): Probe for acceptance
+ * @dev: device to probe
+ *
+ * For the atypical case of an enlightened confidential device driver, provide a
+ * mechanism to check if the device is already accepted by userspace.
+ *
+ * This is an exported helper for device drivers that need to coordinate device
+ * configuration state and acceptance.
+ *
+ * Return: true if the device is already accepted, false otherwise (including
+ * cases where calling context expectations (outside of ->probe()) are
+ * violated).
+ */
+bool device_cc_probe(struct device *dev)
+{
+ /*
+ * See work_on_cpu() in local_pci_probe() for one reason why
+ * lockdep_assert_held() can not be used here.
+ */
+ if (WARN_ON_ONCE(!mutex_is_locked(&dev->mutex)))
+ return false;
+
+ if (!dev->driver)
+ return false;
+
+ return dev->p->cc_accepted;
+}
+EXPORT_SYMBOL_GPL(device_cc_probe);
diff --git a/include/linux/device.h b/include/linux/device.h
index b031ff7..260b3ae 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -1210,6 +1210,33 @@ static inline bool device_link_test(const struct device_link *link, u32 flags)
return !!(link->flags & flags);
}
+/* Confidential Device state helpers */
+#ifdef CONFIG_CONFIDENTIAL_DEVICES
+int device_cc_accept(struct device *dev);
+int device_cc_reject(struct device *dev);
+bool device_cc_accepted(struct device *dev);
+bool device_cc_probe(struct device *dev);
+#else
+static inline int device_cc_accept(struct device *dev)
+{
+ lockdep_assert_held(&dev->mutex);
+ return 0;
+}
+static inline int device_cc_reject(struct device *dev)
+{
+ lockdep_assert_held(&dev->mutex);
+ return 0;
+}
+static inline bool device_cc_accepted(struct device *dev)
+{
+ return false;
+}
+static inline bool device_cc_probe(struct device *dev)
+{
+ return false;
+}
+#endif /* CONFIG_CONFIDENTIAL_DEVICES */
+
/* Create alias, so I can be autoloaded. */
#define MODULE_ALIAS_CHARDEV(major,minor) \
MODULE_ALIAS("char-major-" __stringify(major) "-" __stringify(minor))