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. Driver interactions with the "accepted" state are similar to driver interactions with the driver-core probe deferral mechanism (also managed in 'struct device_private'). TEE I/O aware drivers are responsible for preparing the device for acceptance and then waiting for the accept event. That maps cleanly to the probe deferral mechanism and device_cc_probe() helps coordinates that handoff. 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@xxxxxx> Cc: Jason Gunthorpe <jgg@xxxxxxxx> Cc: Marek Szyprowski <m.szyprowski@xxxxxxxxxxx> Cc: Robin Murphy <robin.murphy@xxxxxxx> Cc: Roman Kisel <romank@xxxxxxxxxxxxxxxxxxx> Cc: Bjorn Helgaas <bhelgaas@xxxxxxxxxx> Cc: Samuel Ortiz <sameo@xxxxxxxxxxxx> Cc: Alexey Kardashevskiy <aik@xxxxxxx> Cc: Xu Yilun <yilun.xu@xxxxxxxxxxxxxxx> Cc: "Aneesh Kumar K.V" <aneesh.kumar@xxxxxxxxxx> Cc: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx> Cc: "Rafael J. Wysocki" <rafael@xxxxxxxxxx> Cc: Danilo Krummrich <dakr@xxxxxxxxxx> Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx> --- drivers/base/Kconfig | 4 ++ drivers/base/Makefile | 1 + drivers/base/base.h | 5 +++ drivers/base/coco.c | 96 ++++++++++++++++++++++++++++++++++++++++++ include/linux/device.h | 28 ++++++++++++ 5 files changed, 134 insertions(+) create mode 100644 drivers/base/coco.c diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index 064eb52ff7e2..311e5377bd70 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -243,4 +243,8 @@ config FW_DEVLINK_SYNC_STATE_TIMEOUT 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 8074a10183dc..e11052cd5253 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -27,6 +27,7 @@ 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_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 123031a757d9..e4eec07675aa 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -98,6 +98,8 @@ struct driver_private { * the device; typically because it depends on another driver getting * probed first. * @async_driver - pointer to device driver awaiting probe via async_probe + * @cc_accepted - track the TEE acceptance state of the device for deferred + * probing, MMIO mapping type, and SWIOTLB bypass for private memory DMA. * @device - pointer back to the struct device that this structure is * associated with. * @dead - This device is currently either in the process of or has been @@ -115,6 +117,9 @@ struct device_private { struct list_head deferred_probe; const struct device_driver *async_driver; char *deferred_probe_reason; +#ifdef CONFIG_CONFIDENTIAL_DEVICES + bool cc_accepted; +#endif struct device *device; u8 dead:1; }; diff --git a/drivers/base/coco.c b/drivers/base/coco.c new file mode 100644 index 000000000000..97c22d0e9247 --- /dev/null +++ b/drivers/base/coco.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0 +#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 = true; + + return 0; +} + +int device_cc_reject(struct device *dev) +{ + lockdep_assert_held(&dev->mutex); + + if (dev->driver) + return -EBUSY; + dev->p->cc_accepted = false; + + 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. + */ +bool device_cc_accepted(struct device *dev) +{ + return dev->p->cc_accepted; +} + +/** + * device_cc_probe(): Coordinate dynamic acceptance with a device driver + * @dev: device to defer probing while acceptance pending + * + * Dynamically accepted devices may need a driver to perform initial + * configuration to get the device into a state where it can be accepted. Use + * this helper to exit driver probe at that partial device-init point and log + * this TEE acceptance specific deferral reason. + * + * This is an exported helper for device drivers that need to coordinate device + * configuration state and acceptance. + */ +int 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. + */ + WARN_ON_ONCE(!mutex_is_locked(&dev->mutex)); + + if (!dev->driver) + return -EINVAL; + + if (dev->p->cc_accepted) + return 0; + + dev_err_probe(dev, -EPROBE_DEFER, "TEE acceptance pending\n"); + + return -EPROBE_DEFER; +} +EXPORT_SYMBOL_GPL(device_cc_probe); diff --git a/include/linux/device.h b/include/linux/device.h index 0470d19da7f2..43d072866949 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -1207,6 +1207,34 @@ 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); +int device_cc_probe(struct device *dev); +bool device_cc_accepted(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 int device_cc_probe(struct device *dev) +{ + lockdep_assert_held(&dev->mutex); + return 0; +} +static inline bool device_cc_accepted(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)) -- 2.50.1