Establish just enough emulated PCI infrastructure to register a sample
TSM (platform security manager) driver and have it discover an IDE + TEE
(link encryption + device-interface security protocol (TDISP)) capable
device.
Use the existing a CONFIG_PCI_BRIDGE_EMUL to emulate an IDE capable root
port, and open code the emulation of an endpoint device via simulated
configuration cycle responses.
The devsec_tsm driver responds to the PCI core TSM operations as if it
successfully exercised the given interface security protocol message.
The devsec_bus and devsec_tsm drivers can be loaded in either order to
reflect cases like SEV-TIO where the TSM is PCI-device firmware, and
cases like TDX Connect where the TSM is a software agent running on the
host CPU.
Follow-on patches add common code for TSM managed IDE establishment. For
now, just successfully complete setup and teardown of the DSM (device
security manager) context as a building block for management of TDI
(trusted device interface) instances.
# modprobe devsec_bus
devsec_bus devsec_bus: PCI host bridge to bus 10000:00
pci_bus 10000:00: root bus resource [bus 00-01]
pci_bus 10000:00: root bus resource [mem 0xf000000000-0xffffffffff 64bit]
pci 10000:00:00.0: [8086:7075] type 01 class 0x060400 PCIe Root Port
pci 10000:00:00.0: PCI bridge to [bus 00]
pci 10000:00:00.0: bridge window [io 0x0000-0x0fff]
pci 10000:00:00.0: bridge window [mem 0x00000000-0x000fffff]
pci 10000:00:00.0: bridge window [mem 0x00000000-0x000fffff 64bit pref]
pci 10000:00:00.0: bridge configuration invalid ([bus 00-00]), reconfiguring
pci 10000:01:00.0: [8086:ffff] type 00 class 0x000000 PCIe Endpoint
pci 10000:01:00.0: BAR 0 [mem 0xf000000000-0xf0001fffff 64bit pref]
pci_doe_abort: pci 10000:01:00.0: DOE: [100] Issuing Abort
pci_doe_cache_protocols: pci 10000:01:00.0: DOE: [100] Found protocol 0 vid: 1 prot: 1
pci 10000:01:00.0: disabling ASPM on pre-1.1 PCIe device. You can enable it with 'pcie_aspm=force'
pci 10000:00:00.0: PCI bridge to [bus 01]
pci_bus 10000:01: busn_res: [bus 01] end is updated to 01
# modprobe devsec_tsm
devsec_tsm_pci_probe: pci 10000:01:00.0: devsec: tsm enabled
__pci_tsm_init: pci 10000:01:00.0: TSM: Device security capabilities detected ( ide tee ), TSM attach
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Lukas Wunner <lukas@wunner.de>
Cc: Samuel Ortiz <sameo@rivosinc.com>
Cc: Alexey Kardashevskiy <aik@amd.com>
Cc: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
---
MAINTAINERS | 1 +
samples/Kconfig | 16 +
samples/Makefile | 1 +
samples/devsec/Makefile | 10 +
samples/devsec/bus.c | 708 ++++++++++++++++++++++++++++++++++++++++
samples/devsec/common.c | 26 ++
samples/devsec/devsec.h | 40 +++
samples/devsec/tsm.c | 173 ++++++++++
8 files changed, 975 insertions(+)
create mode 100644 samples/devsec/Makefile
create mode 100644 samples/devsec/bus.c
create mode 100644 samples/devsec/common.c
create mode 100644 samples/devsec/devsec.h
create mode 100644 samples/devsec/tsm.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 8cb7ee9270d2..97494511da0c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25251,6 +25251,7 @@ F: Documentation/driver-api/pci/tsm.rst
F: drivers/pci/tsm.c
F: drivers/virt/coco/guest/
F: include/linux/*tsm*.h
+F: samples/devsec/
F: samples/tsm-mr/
TRUSTED SERVICES TEE DRIVER
diff --git a/samples/Kconfig b/samples/Kconfig
index ffef99950206..8441593fb654 100644
--- a/samples/Kconfig
+++ b/samples/Kconfig
@@ -325,6 +325,22 @@ source "samples/rust/Kconfig"
source "samples/damon/Kconfig"
+config SAMPLE_DEVSEC
+ tristate "Build a sample TEE Security Manager with an emulated PCI endpoint"
+ depends on PCI
+ depends on VIRT_DRIVERS
+ depends on PCI_DOMAINS_GENERIC || X86
+ select PCI_BRIDGE_EMUL
+ select PCI_TSM
+ select TSM
+ help
+ Build a sample platform TEE Security Manager (TSM) driver with a
+ corresponding emulated PCIe topology. The resulting sample modules,
+ devsec_bus and devsec_tsm, exercise device-security enumeration, PCI
+ subsystem use ABIs, device security flows. For example, exercise IDE
+ (link encryption) establishment and TDISP state transitions via a
+ Device Security Manager (DSM).
+
endif # SAMPLES
config HAVE_SAMPLE_FTRACE_DIRECT
diff --git a/samples/Makefile b/samples/Makefile
index 07641e177bd8..59b510ace9b2 100644
--- a/samples/Makefile
+++ b/samples/Makefile
@@ -45,3 +45,4 @@ obj-$(CONFIG_SAMPLE_DAMON_PRCL) += damon/
obj-$(CONFIG_SAMPLE_DAMON_MTIER) += damon/
obj-$(CONFIG_SAMPLE_HUNG_TASK) += hung_task/
obj-$(CONFIG_SAMPLE_TSM_MR) += tsm-mr/
+obj-y += devsec/
diff --git a/samples/devsec/Makefile b/samples/devsec/Makefile
new file mode 100644
index 000000000000..c8cb5c0cceb8
--- /dev/null
+++ b/samples/devsec/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_SAMPLE_DEVSEC) += devsec_common.o
+devsec_common-y := common.o
+
+obj-$(CONFIG_SAMPLE_DEVSEC) += devsec_bus.o
+devsec_bus-y := bus.o
+
+obj-$(CONFIG_SAMPLE_DEVSEC) += devsec_tsm.o
+devsec_tsm-y := tsm.o
diff --git a/samples/devsec/bus.c b/samples/devsec/bus.c
new file mode 100644
index 000000000000..675e185fcf79
--- /dev/null
+++ b/samples/devsec/bus.c
@@ -0,0 +1,708 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2024 - 2025 Intel Corporation. All rights reserved.
+
+#include <linux/platform_device.h>
+#include <linux/genalloc.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/module.h>
+#include <linux/range.h>
+#include <uapi/linux/pci_regs.h>
+#include <linux/pci.h>
+
+#include "../../drivers/pci/pci-bridge-emul.h"
+#include "devsec.h"
+
+#define NR_DEVSEC_BUSES 1
+#define NR_DEVSEC_ROOT_PORTS 1
+#define NR_PORT_STREAMS 1
+#define NR_ADDR_ASSOC 1
+#define NR_DEVSEC_DEVS 1
+
+struct devsec {
+ struct pci_host_bridge hb;
+ struct devsec_sysdata sysdata;
+ struct gen_pool *iomem_pool;
+ struct resource resource[2];
+ struct pci_bus *bus;
+ struct device *dev;
+ struct devsec_port {
+ union {
+ struct devsec_ide {
+ u32 cap;
+ u32 ctl;
+ struct devsec_stream {
+ u32 cap;
+ u32 ctl;
+ u32 status;
+ u32 rid1;
+ u32 rid2;
+ struct devsec_addr_assoc {
+ u32 assoc1;
+ u32 assoc2;
+ u32 assoc3;
+ } assoc[NR_ADDR_ASSOC];
+ } stream[NR_PORT_STREAMS];
+ } ide __packed;
+ char ide_regs[sizeof(struct devsec_ide)];
+ };
+ struct pci_bridge_emul bridge;
+ } *devsec_ports[NR_DEVSEC_ROOT_PORTS];
+ struct devsec_dev {
+ struct devsec *devsec;
+ struct range mmio_range;
+ u8 __cfg[SZ_4K];
+ struct devsec_dev_doe {
+ int cap;
+ u32 req[SZ_4K / sizeof(u32)];
+ u32 rsp[SZ_4K / sizeof(u32)];
+ int write, read, read_ttl;
+ } doe;
+ u16 ide_pos;
+ union {
+ struct devsec_ide ide __packed;
+ char ide_regs[sizeof(struct devsec_ide)];
+ };
+ } *devsec_devs[NR_DEVSEC_DEVS];
+};
+
+#define devsec_base(x) ((void __force __iomem *) &(x)->__cfg[0])
+
+static struct devsec *bus_to_devsec(struct pci_bus *bus)
+{
+ return container_of(bus->sysdata, struct devsec, sysdata);
+}
+
+static int devsec_dev_config_read(struct devsec *devsec, struct pci_bus *bus,
+ unsigned int devfn, int pos, int size,
+ u32 *val)
+{
+ struct devsec_dev *devsec_dev;
+ struct devsec_dev_doe *doe;
+ void __iomem *base;
+
+ if (PCI_FUNC(devfn) != 0 ||
+ PCI_SLOT(devfn) >= ARRAY_SIZE(devsec->devsec_devs))
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ devsec_dev = devsec->devsec_devs[PCI_SLOT(devfn)];
+ base = devsec_base(devsec_dev);
+ doe = &devsec_dev->doe;
+
+ if (pos == doe->cap + PCI_DOE_READ) {
+ if (doe->read_ttl > 0) {
+ *val = doe->rsp[doe->read];
+ dev_dbg(&bus->dev, "devfn: %#x doe read[%d]\n", devfn,
+ doe->read);
+ } else {
+ *val = 0;
+ dev_dbg(&bus->dev, "devfn: %#x doe no data\n", devfn);
+ }
+ return PCIBIOS_SUCCESSFUL;
+ } else if (pos == doe->cap + PCI_DOE_STATUS) {
+ if (doe->read_ttl > 0) {
+ *val = PCI_DOE_STATUS_DATA_OBJECT_READY;
+ dev_dbg(&bus->dev, "devfn: %#x object ready\n", devfn);
+ } else if (doe->read_ttl < 0) {
+ *val = PCI_DOE_STATUS_ERROR;
+ dev_dbg(&bus->dev, "devfn: %#x error\n", devfn);
+ } else {
+ *val = 0;
+ dev_dbg(&bus->dev, "devfn: %#x idle\n", devfn);
+ }
+ return PCIBIOS_SUCCESSFUL;
+ } else if (pos >= devsec_dev->ide_pos &&
+ pos < devsec_dev->ide_pos + sizeof(struct devsec_ide)) {
+ *val = *(u32 *) &devsec_dev->ide_regs[pos - devsec_dev->ide_pos];
+ return PCIBIOS_SUCCESSFUL;
+ }
+
+ switch (size) {
+ case 1:
+ *val = readb(base + pos);
+ break;
+ case 2:
+ *val = readw(base + pos);
+ break;
+ case 4:
+ *val = readl(base + pos);
+ break;
+ default:
+ PCI_SET_ERROR_RESPONSE(val);
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ }
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static int devsec_port_config_read(struct devsec *devsec, unsigned int devfn,
+ int pos, int size, u32 *val)
+{
+ struct devsec_port *devsec_port;
+
+ if (PCI_FUNC(devfn) != 0 ||
+ PCI_SLOT(devfn) >= ARRAY_SIZE(devsec->devsec_ports))
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ devsec_port = devsec->devsec_ports[PCI_SLOT(devfn)];
+ return pci_bridge_emul_conf_read(&devsec_port->bridge, pos, size, val);
+}
+
+static int devsec_pci_read(struct pci_bus *bus, unsigned int devfn, int pos,
+ int size, u32 *val)
+{
+ struct devsec *devsec = bus_to_devsec(bus);
+
+ dev_vdbg(&bus->dev, "devfn: %#x pos: %#x size: %d\n", devfn, pos, size);
+
+ if (bus == devsec->hb.bus)
+ return devsec_port_config_read(devsec, devfn, pos, size, val);
+ else if (bus->parent == devsec->hb.bus)
+ return devsec_dev_config_read(devsec, bus, devfn, pos, size,
+ val);
+
+ return PCIBIOS_DEVICE_NOT_FOUND;
+}
+
+#ifndef PCI_DOE_PROTOCOL_DISCOVERY
+#define PCI_DOE_PROTOCOL_DISCOVERY 0
+#define PCI_DOE_FEATURE_CMA 1
+#endif
+
+/* just indicate support for CMA */
+static void doe_process(struct devsec_dev_doe *doe)
+{
+ u8 type;
+ u16 vid;
+
+ vid = FIELD_GET(PCI_DOE_DATA_OBJECT_HEADER_1_VID, doe->req[0]);
+ type = FIELD_GET(PCI_DOE_DATA_OBJECT_HEADER_1_TYPE, doe->req[0]);
+
+ if (vid != PCI_VENDOR_ID_PCI_SIG) {
+ doe->read_ttl = -1;
+ return;
+ }
+
+ if (type != PCI_DOE_PROTOCOL_DISCOVERY) {
+ doe->read_ttl = -1;
+ return;
+ }
+
+ doe->rsp[0] = doe->req[0];
+ doe->rsp[1] = FIELD_PREP(PCI_DOE_DATA_OBJECT_HEADER_2_LENGTH, 3);
+ doe->read_ttl = 3;
+ doe->rsp[2] = FIELD_PREP(PCI_DOE_DATA_OBJECT_DISC_RSP_3_VID,
+ PCI_VENDOR_ID_PCI_SIG) |
+ FIELD_PREP(PCI_DOE_DATA_OBJECT_DISC_RSP_3_PROTOCOL,
+ PCI_DOE_FEATURE_CMA) |
+ FIELD_PREP(PCI_DOE_DATA_OBJECT_DISC_RSP_3_NEXT_INDEX, 0);
+}
+
+static int devsec_dev_config_write(struct devsec *devsec, struct pci_bus *bus,
+ unsigned int devfn, int pos, int size,
+ u32 val)
+{
+ struct devsec_dev *devsec_dev;
+ struct devsec_dev_doe *doe;
+ struct devsec_ide *ide;
+ void __iomem *base;
+
+ dev_vdbg(&bus->dev, "devfn: %#x pos: %#x size: %d\n", devfn, pos, size);
+
+ if (PCI_FUNC(devfn) != 0 ||
+ PCI_SLOT(devfn) >= ARRAY_SIZE(devsec->devsec_devs))
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ devsec_dev = devsec->devsec_devs[PCI_SLOT(devfn)];
+ base = devsec_base(devsec_dev);
+ doe = &devsec_dev->doe;
+ ide = &devsec_dev->ide;
+
+ if (pos >= PCI_BASE_ADDRESS_0 && pos <= PCI_BASE_ADDRESS_5) {
+ if (size != 4)
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ /* only one 64-bit mmio bar emulated for now */
+ if (pos == PCI_BASE_ADDRESS_0)
+ val &= ~lower_32_bits(range_len(&devsec_dev->mmio_range) - 1);
+ else if (pos == PCI_BASE_ADDRESS_1)
+ val &= ~upper_32_bits(range_len(&devsec_dev->mmio_range) - 1);
+ else
+ val = 0;
+ } else if (pos == PCI_ROM_ADDRESS) {
+ val = 0;
+ } else if (pos == doe->cap + PCI_DOE_CTRL) {
+ if (val & PCI_DOE_CTRL_GO) {
+ dev_dbg(&bus->dev, "devfn: %#x doe go\n", devfn);
+ doe_process(doe);
+ }
+ if (val & PCI_DOE_CTRL_ABORT) {
+ dev_dbg(&bus->dev, "devfn: %#x doe abort\n", devfn);
+ doe->write = 0;
+ doe->read = 0;
+ doe->read_ttl = 0;
+ }
+ return PCIBIOS_SUCCESSFUL;
+ } else if (pos == doe->cap + PCI_DOE_WRITE) {
+ if (doe->write < ARRAY_SIZE(doe->req))
+ doe->req[doe->write++] = val;
+ dev_dbg(&bus->dev, "devfn: %#x doe write[%d]\n", devfn,
+ doe->write - 1);
+ return PCIBIOS_SUCCESSFUL;
+ } else if (pos == doe->cap + PCI_DOE_READ) {
+ if (doe->read_ttl > 0) {
+ doe->read_ttl--;
+ doe->read++;
+ dev_dbg(&bus->dev, "devfn: %#x doe ack[%d]\n", devfn,
+ doe->read - 1);
+ }
+ return PCIBIOS_SUCCESSFUL;
+ } else if (pos >= devsec_dev->ide_pos &&
+ pos < devsec_dev->ide_pos + sizeof(struct devsec_ide)) {
+ u16 ide_off = pos - devsec_dev->ide_pos;
+
+ for (int i = 0; i < NR_PORT_STREAMS; i++) {
+ struct devsec_stream *stream = &ide->stream[i];
+
+ if (ide_off != offsetof(typeof(*ide), stream[i].ctl))
+ continue;
+
+ stream->ctl = val;
+ stream->status &= ~PCI_IDE_SEL_STS_STATE_MASK;
+ if (val & PCI_IDE_SEL_CTL_EN)
+ stream->status |= FIELD_PREP(
+ PCI_IDE_SEL_STS_STATE_MASK,
+ PCI_IDE_SEL_STS_STATE_SECURE);
+ else
+ stream->status |= FIELD_PREP(
+ PCI_IDE_SEL_STS_STATE_MASK,
+ PCI_IDE_SEL_STS_STATE_INSECURE);
+ return PCIBIOS_SUCCESSFUL;
+ }
+ }
+
+ switch (size) {
+ case 1:
+ writeb(val, base + pos);
+ break;
+ case 2:
+ writew(val, base + pos);
+ break;
+ case 4:
+ writel(val, base + pos);
+ break;
+ default:
+ return PCIBIOS_BAD_REGISTER_NUMBER;
+ }
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static int devsec_port_config_write(struct devsec *devsec, struct pci_bus *bus,
+ unsigned int devfn, int pos, int size,
+ u32 val)
+{
+ struct devsec_port *devsec_port;
+
+ dev_vdbg(&bus->dev, "devfn: %#x pos: %#x size: %d\n", devfn, pos, size);
+
+ if (PCI_FUNC(devfn) != 0 ||
+ PCI_SLOT(devfn) >= ARRAY_SIZE(devsec->devsec_ports))
+ return PCIBIOS_DEVICE_NOT_FOUND;
+
+ devsec_port = devsec->devsec_ports[PCI_SLOT(devfn)];
+ return pci_bridge_emul_conf_write(&devsec_port->bridge, pos, size, val);
+}
+
+static int devsec_pci_write(struct pci_bus *bus, unsigned int devfn, int pos,
+ int size, u32 val)
+{
+ struct devsec *devsec = bus_to_devsec(bus);
+
+ dev_vdbg(&bus->dev, "devfn: %#x pos: %#x size: %d\n", devfn, pos, size);
+
+ if (bus == devsec->hb.bus)
+ return devsec_port_config_write(devsec, bus, devfn, pos, size,
+ val);
+ else if (bus->parent == devsec->hb.bus)
+ return devsec_dev_config_write(devsec, bus, devfn, pos, size,
+ val);
+ return PCIBIOS_DEVICE_NOT_FOUND;
+}
+
+static struct pci_ops devsec_ops = {
+ .read = devsec_pci_read,
+ .write = devsec_pci_write,
+};
+
+static void destroy_bus(void *data)
+{
+ struct pci_host_bridge *hb = data;
+
+ pci_stop_root_bus(hb->bus);
+ pci_remove_root_bus(hb->bus);
+}
+
+static u32 build_ext_cap_header(u32 id, u32 ver, u32 next)
+{
+ return FIELD_PREP(GENMASK(15, 0), id) |
+ FIELD_PREP(GENMASK(19, 16), ver) |
+ FIELD_PREP(GENMASK(31, 20), next);
+}
+
+static void init_ide(struct devsec_ide *ide)
+{
+ ide->cap = PCI_IDE_CAP_SELECTIVE | PCI_IDE_CAP_IDE_KM |
+ PCI_IDE_CAP_TEE_LIMITED |
+ FIELD_PREP(PCI_IDE_CAP_SEL_NUM_MASK, NR_PORT_STREAMS - 1);
+
+ for (int i = 0; i < NR_PORT_STREAMS; i++)
+ ide->stream[i].cap =
+ FIELD_PREP(PCI_IDE_SEL_CAP_ASSOC_NUM_MASK, NR_ADDR_ASSOC);
+}
+
+static void init_dev_cfg(struct devsec_dev *devsec_dev)
+{
+ void __iomem *base = devsec_base(devsec_dev), *cap_base;
+ int pos, next;
+
+ /* BAR space */
+ writew(0x8086, base + PCI_VENDOR_ID);
+ writew(0xffff, base + PCI_DEVICE_ID);
+ writel(lower_32_bits(devsec_dev->mmio_range.start) |
+ PCI_BASE_ADDRESS_MEM_TYPE_64 |
+ PCI_BASE_ADDRESS_MEM_PREFETCH,
+ base + PCI_BASE_ADDRESS_0);
+ writel(upper_32_bits(devsec_dev->mmio_range.start),
+ base + PCI_BASE_ADDRESS_1);
+
+ /* Capability init */
+ writeb(PCI_HEADER_TYPE_NORMAL, base + PCI_HEADER_TYPE);
+ writew(PCI_STATUS_CAP_LIST, base + PCI_STATUS);
+ pos = 0x40;
+ writew(pos, base + PCI_CAPABILITY_LIST);
+
+ /* PCI-E Capability */
+ cap_base = base + pos;
+ writeb(PCI_CAP_ID_EXP, cap_base);
+ writew(PCI_EXP_TYPE_ENDPOINT, cap_base + PCI_EXP_FLAGS);
+ writew(PCI_EXP_LNKSTA_CLS_2_5GB | PCI_EXP_LNKSTA_NLW_X1, cap_base + PCI_EXP_LNKSTA);
+ writel(PCI_EXP_DEVCAP_FLR | PCI_EXP_DEVCAP_TEE, cap_base + PCI_EXP_DEVCAP);
+
+ /* DOE Extended Capability */
+ pos = PCI_CFG_SPACE_SIZE;
+ next = pos + PCI_DOE_CAP_SIZEOF;
+ cap_base = base + pos;
+ devsec_dev->doe.cap = pos;
+ writel(build_ext_cap_header(PCI_EXT_CAP_ID_DOE, 2, next), cap_base);
+
+ /* IDE Extended Capability */
+ pos = next;
+ cap_base = base + pos;
+ writel(build_ext_cap_header(PCI_EXT_CAP_ID_IDE, 1, 0), cap_base);
+ devsec_dev->ide_pos = pos + 4;
+ init_ide(&devsec_dev->ide);
+}
+
+#define MMIO_SIZE SZ_2M
+
+static void destroy_devsec_dev(void *data)
+{
+ struct devsec_dev *devsec_dev = data;
+ struct devsec *devsec = devsec_dev->devsec;
+
+ gen_pool_free(devsec->iomem_pool, devsec_dev->mmio_range.start,
+ range_len(&devsec_dev->mmio_range));
+ kfree(devsec_dev);
+}
+
+static struct devsec_dev *devsec_dev_alloc(struct devsec *devsec)
+{
+ struct devsec_dev *devsec_dev __free(kfree) =
+ kzalloc(sizeof(*devsec_dev), GFP_KERNEL);
+ struct genpool_data_align data = {
+ .align = MMIO_SIZE,
+ };
+ u64 phys;
+
+ if (!devsec_dev)
+ return ERR_PTR(-ENOMEM);
+
+ phys = gen_pool_alloc_algo(devsec->iomem_pool, MMIO_SIZE,
+ gen_pool_first_fit_align, &data);
+ if (!phys)
+ return ERR_PTR(-ENOMEM);
+
+ *devsec_dev = (struct devsec_dev) {
+ .mmio_range = {
+ .start = phys,
+ .end = phys + MMIO_SIZE - 1,
+ },
+ .devsec = devsec,
+ };
+ init_dev_cfg(devsec_dev);
+
+ return_ptr(devsec_dev);
+}
+
+static int alloc_devs(struct devsec *devsec)
+{
+ struct device *dev = devsec->dev;
+
+ for (int i = 0; i < ARRAY_SIZE(devsec->devsec_devs); i++) {
+ struct devsec_dev *devsec_dev = devsec_dev_alloc(devsec);
+ int rc;
+
+ if (IS_ERR(devsec_dev))
+ return PTR_ERR(devsec_dev);
+ rc = devm_add_action_or_reset(dev, destroy_devsec_dev,
+ devsec_dev);
+ if (rc)
+ return rc;
+ devsec->devsec_devs[i] = devsec_dev;
+ }
+
+ return 0;
+}
+
+static pci_bridge_emul_read_status_t
+devsec_bridge_read_base(struct pci_bridge_emul *bridge, int pos, u32 *val)
+{
+ return PCI_BRIDGE_EMUL_NOT_HANDLED;
+}
+
+static pci_bridge_emul_read_status_t
+devsec_bridge_read_pcie(struct pci_bridge_emul *bridge, int pos, u32 *val)
+{
+ return PCI_BRIDGE_EMUL_NOT_HANDLED;
+}
+
+static pci_bridge_emul_read_status_t
+devsec_bridge_read_ext(struct pci_bridge_emul *bridge, int pos, u32 *val)
+{
+ struct devsec_port *devsec_port = bridge->data;
+
+ /* only one extended capability, IDE... */
+ if (pos == 0) {
+ *val = build_ext_cap_header(PCI_EXT_CAP_ID_IDE, 1, 0);
+ return PCI_BRIDGE_EMUL_HANDLED;
+ }
+
+ if (pos < 4)
+ return PCI_BRIDGE_EMUL_NOT_HANDLED;
+
+ pos -= 4;
+ if (pos < sizeof(struct devsec_ide)) {
+ *val = *(u32 *)(&devsec_port->ide_regs[pos]);
+ return PCI_BRIDGE_EMUL_HANDLED;
+ }
+
+ return PCI_BRIDGE_EMUL_NOT_HANDLED;
+}
+
+static void devsec_bridge_write_base(struct pci_bridge_emul *bridge, int pos,
+ u32 old, u32 new, u32 mask)
+{
+}
+
+static void devsec_bridge_write_pcie(struct pci_bridge_emul *bridge, int pos,
+ u32 old, u32 new, u32 mask)
+{
+}
+
+static void devsec_bridge_write_ext(struct pci_bridge_emul *bridge, int pos,
+ u32 old, u32 new, u32 mask)
+{
+ struct devsec_port *devsec_port = bridge->data;
+
+ if (pos < sizeof(struct devsec_ide))
+ *(u32 *)(&devsec_port->ide_regs[pos]) = new;
+}
+
+static const struct pci_bridge_emul_ops devsec_bridge_ops = {
+ .read_base = devsec_bridge_read_base,
+ .write_base = devsec_bridge_write_base,
+ .read_pcie = devsec_bridge_read_pcie,
+ .write_pcie = devsec_bridge_write_pcie,
+ .read_ext = devsec_bridge_read_ext,
+ .write_ext = devsec_bridge_write_ext,
+};
+
+static int init_port(struct devsec_port *devsec_port)
+{
+ struct pci_bridge_emul *bridge = &devsec_port->bridge;
+
+ *bridge = (struct pci_bridge_emul) {
+ .conf = {
+ .vendor = cpu_to_le16(0x8086),
+ .device = cpu_to_le16(0x7075),
+ .class_revision = cpu_to_le32(0x1),
+ .pref_mem_base = cpu_to_le16(PCI_PREF_RANGE_TYPE_64),
+ .pref_mem_limit = cpu_to_le16(PCI_PREF_RANGE_TYPE_64),
+ },
+ .pcie_conf = {
+ .devcap = cpu_to_le16(PCI_EXP_DEVCAP_FLR),
+ .lnksta = cpu_to_le16(PCI_EXP_LNKSTA_CLS_2_5GB),
+ },
+ .subsystem_vendor_id = cpu_to_le16(0x8086),
+ .has_pcie = true,
+ .data = devsec_port,
+ .ops = &devsec_bridge_ops,
+ };
+
+ init_ide(&devsec_port->ide);
+
+ return pci_bridge_emul_init(bridge, 0);
+}
+
+static void destroy_port(void *data)
+{
+ struct devsec_port *devsec_port = data;
+
+ pci_bridge_emul_cleanup(&devsec_port->bridge);
+ kfree(devsec_port);
+}
+
+static struct devsec_port *devsec_port_alloc(void)
+{
+ int rc;
+
+ struct devsec_port *devsec_port __free(kfree) =
+ kzalloc(sizeof(*devsec_port), GFP_KERNEL);
+
+ if (!devsec_port)
+ return ERR_PTR(-ENOMEM);
+
+ rc = init_port(devsec_port);
+ if (rc)
+ return ERR_PTR(rc);
+
+ return_ptr(devsec_port);
+}
+
+static int alloc_ports(struct devsec *devsec)
+{
+ struct device *dev = devsec->dev;
+
+ for (int i = 0; i < ARRAY_SIZE(devsec->devsec_ports); i++) {
+ struct devsec_port *devsec_port = devsec_port_alloc();
+ int rc;
+
+ if (IS_ERR(devsec_port))
+ return PTR_ERR(devsec_port);
+ rc = devm_add_action_or_reset(dev, destroy_port, devsec_port);
+ if (rc)
+ return rc;
+ devsec->devsec_ports[i] = devsec_port;
+ }
+
+ return 0;
+}
+
+static int __init devsec_bus_probe(struct platform_device *pdev)
+{
+ int rc;
+ struct devsec *devsec;
+ u64 mmio_size = SZ_64G;
+ struct devsec_sysdata *sd;
+ struct pci_host_bridge *hb;
+ struct device *dev = &pdev->dev;
+ u64 mmio_start = iomem_resource.end + 1 - SZ_64G;
+
+ hb = devm_pci_alloc_host_bridge(
+ dev, sizeof(*devsec) - sizeof(struct pci_host_bridge));
+ if (!hb)
+ return -ENOMEM;
+
+ devsec = container_of(hb, struct devsec, hb);
+ devsec->dev = dev;
+ devsec->iomem_pool = devm_gen_pool_create(dev, ilog2(SZ_2M),
+ NUMA_NO_NODE, "devsec iomem");
+ if (!devsec->iomem_pool)
+ return -ENOMEM;
+
+ rc = gen_pool_add(devsec->iomem_pool, mmio_start, mmio_size,
+ NUMA_NO_NODE);
+ if (rc)
+ return rc;
+
+ rc = alloc_ports(devsec);
+ if (rc)
+ return rc;
+
+ rc = alloc_devs(devsec);
+ if (rc)
+ return rc;
+
+ devsec->resource[0] = (struct resource) {
+ .name = "DEVSEC BUSES",
+ .start = 0,
+ .end = NR_DEVSEC_BUSES + NR_DEVSEC_ROOT_PORTS - 1,
+ .flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED,
+ };
+ pci_add_resource(&hb->windows, &devsec->resource[0]);
+
+ devsec->resource[1] = (struct resource) {
+ .name = "DEVSEC MMIO",
+ .start = mmio_start,
+ .end = mmio_start + mmio_size - 1,
+ .flags = IORESOURCE_MEM | IORESOURCE_MEM_64,
+ };
+ pci_add_resource(&hb->windows, &devsec->resource[1]);
+
+ sd = &devsec->sysdata;
+ devsec_sysdata = sd;
+ hb->domain_nr = pci_bus_find_emul_domain_nr(PCI_DOMAIN_NR_NOT_SET);
+ if (hb->domain_nr < 0)
+ return hb->domain_nr;
+
+ /*
+ * Note, domain_nr is set in devsec_sysdata for
+ * !CONFIG_PCI_DOMAINS_GENERIC platforms
+ */
+ devsec_set_domain_nr(sd, hb->domain_nr);
+
+ hb->dev.parent = dev;
+ hb->sysdata = sd;
+ hb->ops = &devsec_ops;
+
+ rc = pci_scan_root_bus_bridge(hb);
+ if (rc)
+ return rc;
+
+ return devm_add_action_or_reset(dev, destroy_bus, no_free_ptr(hb));
+}
+
+static struct platform_driver devsec_bus_driver = {
+ .driver = {
+ .name = "devsec_bus",
+ },
+};
+
+static struct platform_device *devsec_bus;
+
+static int __init devsec_bus_init(void)
+{
+ struct platform_device_info devsec_bus_info = {
+ .name = "devsec_bus",
+ .id = -1,
+ };
+ int rc;
+
+ devsec_bus = platform_device_register_full(&devsec_bus_info);
+ if (IS_ERR(devsec_bus))
+ return PTR_ERR(devsec_bus);
+
+ rc = platform_driver_probe(&devsec_bus_driver, devsec_bus_probe);
+ if (rc)
+ platform_device_unregister(devsec_bus);
+ return 0;
+}
+module_init(devsec_bus_init);
+
+static void __exit devsec_bus_exit(void)
+{
+ platform_driver_unregister(&devsec_bus_driver);
+ platform_device_unregister(devsec_bus);
+}
+module_exit(devsec_bus_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Device Security Sample Infrastructure: TDISP Device Emulation");
diff --git a/samples/devsec/common.c b/samples/devsec/common.c
new file mode 100644
index 000000000000..de0078e4d614
--- /dev/null
+++ b/samples/devsec/common.c
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2024 - 2025 Intel Corporation. All rights reserved.
+
+#include <linux/pci.h>
+#include <linux/export.h>
+
+/*
+ * devsec_bus and devsec_tsm need a common location for this data to
+ * avoid depending on each other. Enables load order testing
+ */
+struct pci_sysdata *devsec_sysdata;
+EXPORT_SYMBOL_GPL(devsec_sysdata);
+
+static int __init common_init(void)
+{
+ return 0;
+}
+module_init(common_init);
+
+static void __exit common_exit(void)
+{
+}
+module_exit(common_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Device Security Sample Infrastructure: Shared data");
diff --git a/samples/devsec/devsec.h b/samples/devsec/devsec.h
new file mode 100644
index 000000000000..ae4274c86244
--- /dev/null
+++ b/samples/devsec/devsec.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+// Copyright(c) 2024 - 2025 Intel Corporation. All rights reserved.
+
+#ifndef __DEVSEC_H__
+#define __DEVSEC_H__
+struct devsec_sysdata {
+#ifdef CONFIG_X86
+ /*
+ * Must be first member to that x86::pci_domain_nr() can type
+ * pun devsec_sysdata and pci_sysdata.
+ */
+ struct pci_sysdata sd;
+#else
+ int domain_nr;
+#endif
+};
+
+#ifdef CONFIG_X86
+static inline void devsec_set_domain_nr(struct devsec_sysdata *sd,
+ int domain_nr)
+{
+ sd->sd.domain = domain_nr;
+}
+static inline int devsec_get_domain_nr(struct devsec_sysdata *sd)
+{
+ return sd->sd.domain;
+}
+#else
+static inline void devsec_set_domain_nr(struct devsec_sysdata *sd,
+ int domain_nr)
+{
+ sd->domain_nr = domain_nr;
+}
+static inline int devsec_get_domain_nr(struct devsec_sysdata *sd)
+{
+ return sd->domain_nr;
+}
+#endif
+extern struct devsec_sysdata *devsec_sysdata;
+#endif /* __DEVSEC_H__ */
diff --git a/samples/devsec/tsm.c b/samples/devsec/tsm.c
new file mode 100644
index 000000000000..a4705212a7e4
--- /dev/null
+++ b/samples/devsec/tsm.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright(c) 2024 - 2025 Intel Corporation. All rights reserved. */
+
+#define dev_fmt(fmt) "devsec: " fmt
+#include <linux/device/faux.h>
+#include <linux/pci-tsm.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/tsm.h>
+#include "devsec.h"
+
+struct devsec_tsm_pf0 {
+ struct pci_tsm_pf0 pci;
+#define NR_TSM_STREAMS 4
+};
+
+struct devsec_tsm_fn {
+ struct pci_tsm pci;
+};
+
+static struct devsec_tsm_pf0 *to_devsec_tsm_pf0(struct pci_tsm *tsm)
+{
+ return container_of(tsm, struct devsec_tsm_pf0, pci.tsm);
+}
+
+static struct devsec_tsm_fn *to_devsec_tsm_fn(struct pci_tsm *tsm)
+{
+ return container_of(tsm, struct devsec_tsm_fn, pci);
+}
+
+static const struct pci_tsm_ops *__devsec_pci_ops;
+
+static struct pci_tsm *devsec_tsm_pf0_probe(struct pci_dev *pdev)
+{
+ int rc;
+
+ struct devsec_tsm_pf0 *devsec_tsm __free(kfree) =
+ kzalloc(sizeof(*devsec_tsm), GFP_KERNEL);
+ if (!devsec_tsm)
+ return NULL;
+
+ rc = pci_tsm_pf0_constructor(pdev, &devsec_tsm->pci, __devsec_pci_ops);
+ if (rc)
+ return NULL;
+
+ pci_dbg(pdev, "tsm enabled\n");
+ return &no_free_ptr(devsec_tsm)->pci.tsm;
+}
+
+static struct pci_tsm *devsec_tsm_fn_probe(struct pci_dev *pdev)
+{
+ int rc;
+
+ struct devsec_tsm_fn *devsec_tsm __free(kfree) =
+ kzalloc(sizeof(*devsec_tsm), GFP_KERNEL);
+ if (!devsec_tsm)
+ return NULL;
+
+ rc = pci_tsm_constructor(pdev, &devsec_tsm->pci, __devsec_pci_ops);
+ if (rc)
+ return NULL;
+
+ pci_dbg(pdev, "tsm (sub-function) enabled\n");
+ return &no_free_ptr(devsec_tsm)->pci;
+}
+
+static struct pci_tsm *devsec_tsm_pci_probe(struct pci_dev *pdev)
+{
+ if (pdev->sysdata != devsec_sysdata)
+ return NULL;
+
+ if (is_pci_tsm_pf0(pdev))
+ return devsec_tsm_pf0_probe(pdev);
+ return devsec_tsm_fn_probe(pdev);
+}
+
+static void devsec_tsm_pci_remove(struct pci_tsm *tsm)
+{
+ struct pci_dev *pdev = tsm->pdev;
+
+ pci_dbg(pdev, "tsm disabled\n");
+
+ if (is_pci_tsm_pf0(pdev)) {
+ struct devsec_tsm_pf0 *devsec_tsm = to_devsec_tsm_pf0(tsm);
+
+ pci_tsm_pf0_destructor(&devsec_tsm->pci);
+ kfree(devsec_tsm);
+ } else {
+ struct devsec_tsm_fn *devsec_tsm = to_devsec_tsm_fn(tsm);
+
+ kfree(devsec_tsm);
+ }
+}
+
+/*
+ * Reference consumer for a TSM driver "connect" operation callback. The
+ * low-level TSM driver understands details about the platform the PCI
+ * core does not, like number of available streams that can be
+ * established per host bridge. The expected flow is:
+ *
+ * 1/ Allocate platform specific Stream resource (TSM specific)
+ * 2/ Allocate Stream Ids in the endpoint and Root Port (PCI TSM helper)
+ * 3/ Register Stream Ids for the consumed resources from the last 2
+ * steps to be accountable (via sysfs) to the admin (PCI TSM helper)
+ * 4/ Register the Stream with the TSM core so that either PCI sysfs or
+ * TSM core sysfs can list the in-use resources (TSM core helper)
+ * 5/ Configure IDE settings in the endpoint and Root Port (PCI TSM helper)
+ * 6/ RPC call to TSM to perform IDE_KM and optionally enable the stream
+ * (TSM Specific)
+ * 7/ Enable the stream in the endpoint, and root port if TSM call did
+ * not already handle that (PCI TSM helper)
+ *
+ * The expectation is the helpers referenceed are convenience "library"
+ * APIs for common operations, not a "midlayer" that enforces a specific
+ * or use model sequencing.
+ */
+static int devsec_tsm_connect(struct pci_dev *pdev)
+{
+ return -ENXIO;
+}
+
+static void devsec_tsm_disconnect(struct pci_dev *pdev)
+{
+}
+
+static struct pci_tsm_ops devsec_pci_ops = {
+ .probe = devsec_tsm_pci_probe,
+ .remove = devsec_tsm_pci_remove,
+ .connect = devsec_tsm_connect,
+ .disconnect = devsec_tsm_disconnect,
+};
+
+static void devsec_tsm_remove(void *tsm_dev)
+{
+ tsm_unregister(tsm_dev);
+}
+
+static int devsec_tsm_probe(struct faux_device *fdev)
+{
+ struct tsm_dev *tsm_dev;
+
+ tsm_dev = tsm_register(&fdev->dev, NULL, &devsec_pci_ops);
+ if (IS_ERR(tsm_dev))
+ return PTR_ERR(tsm_dev);
+
+ return devm_add_action_or_reset(&fdev->dev, devsec_tsm_remove,
+ tsm_dev);
+}
+
+static struct faux_device *devsec_tsm;
+
+static const struct faux_device_ops devsec_device_ops = {
+ .probe = devsec_tsm_probe,
+};
+
+static int __init devsec_tsm_init(void)
+{
+ __devsec_pci_ops = &devsec_pci_ops;
+ devsec_tsm = faux_device_create("devsec_tsm", NULL, &devsec_device_ops);
+ if (!devsec_tsm)
+ return -ENOMEM;
+ return 0;
+}
+module_init(devsec_tsm_init);
+
+static void __exit devsec_tsm_exit(void)
+{
+ faux_device_destroy(devsec_tsm);
+}
+module_exit(devsec_tsm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Device Security Sample Infrastructure: Platform TSM Driver");
--
2.50.1
On Thu, Jul 17, 2025 at 11:33:53AM -0700, Dan Williams wrote: > Establish just enough emulated PCI infrastructure to register a sample > TSM (platform security manager) driver and have it discover an IDE + TEE > (link encryption + device-interface security protocol (TDISP)) capable > device. > > Use the existing a CONFIG_PCI_BRIDGE_EMUL to emulate an IDE capable root > port, and open code the emulation of an endpoint device via simulated > configuration cycle responses. s/existing a/existing/ > The devsec_tsm driver responds to the PCI core TSM operations as if it > successfully exercised the given interface security protocol message. > > The devsec_bus and devsec_tsm drivers can be loaded in either order to > reflect cases like SEV-TIO where the TSM is PCI-device firmware, and > cases like TDX Connect where the TSM is a software agent running on the > host CPU. > > Follow-on patches add common code for TSM managed IDE establishment. For > now, just successfully complete setup and teardown of the DSM (device > security manager) context as a building block for management of TDI > (trusted device interface) instances. > > # modprobe devsec_bus > devsec_bus devsec_bus: PCI host bridge to bus 10000:00 > pci_bus 10000:00: root bus resource [bus 00-01] > pci_bus 10000:00: root bus resource [mem 0xf000000000-0xffffffffff 64bit] > pci 10000:00:00.0: [8086:7075] type 01 class 0x060400 PCIe Root Port > pci 10000:00:00.0: PCI bridge to [bus 00] > pci 10000:00:00.0: bridge window [io 0x0000-0x0fff] > pci 10000:00:00.0: bridge window [mem 0x00000000-0x000fffff] > pci 10000:00:00.0: bridge window [mem 0x00000000-0x000fffff 64bit pref] > pci 10000:00:00.0: bridge configuration invalid ([bus 00-00]), reconfiguring > pci 10000:01:00.0: [8086:ffff] type 00 class 0x000000 PCIe Endpoint > pci 10000:01:00.0: BAR 0 [mem 0xf000000000-0xf0001fffff 64bit pref] > pci_doe_abort: pci 10000:01:00.0: DOE: [100] Issuing Abort > pci_doe_cache_protocols: pci 10000:01:00.0: DOE: [100] Found protocol 0 vid: 1 prot: 1 > pci 10000:01:00.0: disabling ASPM on pre-1.1 PCIe device. You can enable it with 'pcie_aspm=force' > pci 10000:00:00.0: PCI bridge to [bus 01] > pci_bus 10000:01: busn_res: [bus 01] end is updated to 01 Most of these messages don't seem relevant to DSM/TDISP/etc. It *would* be useful to have a hint about what specifically makes this an IDE + TEE device. Capability visible via lspci? Are devices at both ends required, e.g., a Root Port and an Endpoint? Oooh, I see (finally). This hierarchy is all totally fabricated, no actual hardware involved at all. You did say that above; it just took a while to sink in. > # modprobe devsec_tsm > devsec_tsm_pci_probe: pci 10000:01:00.0: devsec: tsm enabled > __pci_tsm_init: pci 10000:01:00.0: TSM: Device security capabilities detected ( ide tee ), TSM attach s/tsm/TSM/ in the message s/ide/IDE/ s/tee/TEE/ Looks like spurious spaces inside parens? > + * The expectation is the helpers referenceed are convenience "library" s/referenceed/referenced/
Bjorn Helgaas wrote: > On Thu, Jul 17, 2025 at 11:33:53AM -0700, Dan Williams wrote: > > Establish just enough emulated PCI infrastructure to register a sample > > TSM (platform security manager) driver and have it discover an IDE + TEE > > (link encryption + device-interface security protocol (TDISP)) capable > > device. > > > > Use the existing a CONFIG_PCI_BRIDGE_EMUL to emulate an IDE capable root > > port, and open code the emulation of an endpoint device via simulated > > configuration cycle responses. > > s/existing a/existing/ Fixed. > > > The devsec_tsm driver responds to the PCI core TSM operations as if it > > successfully exercised the given interface security protocol message. > > > > The devsec_bus and devsec_tsm drivers can be loaded in either order to > > reflect cases like SEV-TIO where the TSM is PCI-device firmware, and > > cases like TDX Connect where the TSM is a software agent running on the > > host CPU. > > > > Follow-on patches add common code for TSM managed IDE establishment. For > > now, just successfully complete setup and teardown of the DSM (device > > security manager) context as a building block for management of TDI > > (trusted device interface) instances. > > > > # modprobe devsec_bus > > devsec_bus devsec_bus: PCI host bridge to bus 10000:00 > > pci_bus 10000:00: root bus resource [bus 00-01] > > pci_bus 10000:00: root bus resource [mem 0xf000000000-0xffffffffff 64bit] > > pci 10000:00:00.0: [8086:7075] type 01 class 0x060400 PCIe Root Port > > pci 10000:00:00.0: PCI bridge to [bus 00] > > pci 10000:00:00.0: bridge window [io 0x0000-0x0fff] > > pci 10000:00:00.0: bridge window [mem 0x00000000-0x000fffff] > > pci 10000:00:00.0: bridge window [mem 0x00000000-0x000fffff 64bit pref] > > pci 10000:00:00.0: bridge configuration invalid ([bus 00-00]), reconfiguring > > pci 10000:01:00.0: [8086:ffff] type 00 class 0x000000 PCIe Endpoint > > pci 10000:01:00.0: BAR 0 [mem 0xf000000000-0xf0001fffff 64bit pref] > > pci_doe_abort: pci 10000:01:00.0: DOE: [100] Issuing Abort > > pci_doe_cache_protocols: pci 10000:01:00.0: DOE: [100] Found protocol 0 vid: 1 prot: 1 > > pci 10000:01:00.0: disabling ASPM on pre-1.1 PCIe device. You can enable it with 'pcie_aspm=force' > > pci 10000:00:00.0: PCI bridge to [bus 01] > > pci_bus 10000:01: busn_res: [bus 01] end is updated to 01 > > Most of these messages don't seem relevant to DSM/TDISP/etc. It > *would* be useful to have a hint about what specifically makes this an > IDE + TEE device. Capability visible via lspci? Are devices at both > ends required, e.g., a Root Port and an Endpoint? > > Oooh, I see (finally). This hierarchy is all totally fabricated, no > actual hardware involved at all. You did say that above; it just took > a while to sink in. Yeah, there are so many moving parts to this that I do not feel comfortable leaving 100% of the testing to hardware. This is faster to prototype than teaching QEMU to emulate all the pieces here. > > > # modprobe devsec_tsm > > devsec_tsm_pci_probe: pci 10000:01:00.0: devsec: tsm enabled > > __pci_tsm_init: pci 10000:01:00.0: TSM: Device security capabilities detected ( ide tee ), TSM attach > > s/tsm/TSM/ in the message > s/ide/IDE/ > s/tee/TEE/ Fixed. > > Looks like spurious spaces inside parens? Yeah just to avoid extra code to elide the separator, but easy enough to fixup. > > + * The expectation is the helpers referenceed are convenience "library" > > s/referenceed/referenced/ Got it, checkpatch misses this one.
On Thu, 17 Jul 2025 11:33:53 -0700 Dan Williams <dan.j.williams@intel.com> wrote: > Establish just enough emulated PCI infrastructure to register a sample > TSM (platform security manager) driver and have it discover an IDE + TEE > (link encryption + device-interface security protocol (TDISP)) capable > device. > > Use the existing a CONFIG_PCI_BRIDGE_EMUL to emulate an IDE capable root > port, and open code the emulation of an endpoint device via simulated > configuration cycle responses. > > The devsec_tsm driver responds to the PCI core TSM operations as if it > successfully exercised the given interface security protocol message. > > The devsec_bus and devsec_tsm drivers can be loaded in either order to > reflect cases like SEV-TIO where the TSM is PCI-device firmware, and > cases like TDX Connect where the TSM is a software agent running on the > host CPU. > > Follow-on patches add common code for TSM managed IDE establishment. For > now, just successfully complete setup and teardown of the DSM (device > security manager) context as a building block for management of TDI > (trusted device interface) instances. > > # modprobe devsec_bus > devsec_bus devsec_bus: PCI host bridge to bus 10000:00 > pci_bus 10000:00: root bus resource [bus 00-01] > pci_bus 10000:00: root bus resource [mem 0xf000000000-0xffffffffff 64bit] > pci 10000:00:00.0: [8086:7075] type 01 class 0x060400 PCIe Root Port > pci 10000:00:00.0: PCI bridge to [bus 00] > pci 10000:00:00.0: bridge window [io 0x0000-0x0fff] > pci 10000:00:00.0: bridge window [mem 0x00000000-0x000fffff] > pci 10000:00:00.0: bridge window [mem 0x00000000-0x000fffff 64bit pref] > pci 10000:00:00.0: bridge configuration invalid ([bus 00-00]), reconfiguring > pci 10000:01:00.0: [8086:ffff] type 00 class 0x000000 PCIe Endpoint > pci 10000:01:00.0: BAR 0 [mem 0xf000000000-0xf0001fffff 64bit pref] > pci_doe_abort: pci 10000:01:00.0: DOE: [100] Issuing Abort > pci_doe_cache_protocols: pci 10000:01:00.0: DOE: [100] Found protocol 0 vid: 1 prot: 1 > pci 10000:01:00.0: disabling ASPM on pre-1.1 PCIe device. You can enable it with 'pcie_aspm=force' > pci 10000:00:00.0: PCI bridge to [bus 01] > pci_bus 10000:01: busn_res: [bus 01] end is updated to 01 > # modprobe devsec_tsm > devsec_tsm_pci_probe: pci 10000:01:00.0: devsec: tsm enabled > __pci_tsm_init: pci 10000:01:00.0: TSM: Device security capabilities detected ( ide tee ), TSM attach > > Cc: Bjorn Helgaas <bhelgaas@google.com> > Cc: Lukas Wunner <lukas@wunner.de> > Cc: Samuel Ortiz <sameo@rivosinc.com> > Cc: Alexey Kardashevskiy <aik@amd.com> > Cc: Xu Yilun <yilun.xu@linux.intel.com> > Signed-off-by: Dan Williams <dan.j.williams@intel.com> A fairly superficial review. Too much staring at code today to check the emulation was right and have any chance of spotting bugs! > diff --git a/samples/devsec/bus.c b/samples/devsec/bus.c > new file mode 100644 > index 000000000000..675e185fcf79 > --- /dev/null > +++ b/samples/devsec/bus.c > @@ -0,0 +1,708 @@ > +static int alloc_devs(struct devsec *devsec) > +{ > + struct device *dev = devsec->dev; Similar to below. Maybe use it inline. > + > + for (int i = 0; i < ARRAY_SIZE(devsec->devsec_devs); i++) { > + struct devsec_dev *devsec_dev = devsec_dev_alloc(devsec); > + int rc; > + > + if (IS_ERR(devsec_dev)) > + return PTR_ERR(devsec_dev); > + rc = devm_add_action_or_reset(dev, destroy_devsec_dev, > + devsec_dev); > + if (rc) > + return rc; > + devsec->devsec_devs[i] = devsec_dev; > + } > + > + return 0; > +} > +static int init_port(struct devsec_port *devsec_port) > +{ > + struct pci_bridge_emul *bridge = &devsec_port->bridge; > + > + *bridge = (struct pci_bridge_emul) { > + .conf = { > + .vendor = cpu_to_le16(0x8086), > + .device = cpu_to_le16(0x7075), Emulating something real? If not maybe we should get an ID from another space (or reserve this one ;) > + .class_revision = cpu_to_le32(0x1), > + .pref_mem_base = cpu_to_le16(PCI_PREF_RANGE_TYPE_64), > + .pref_mem_limit = cpu_to_le16(PCI_PREF_RANGE_TYPE_64), > + }, > +{ > + struct device *dev = devsec->dev; Only used once. I'd move it down there. > + > + for (int i = 0; i < ARRAY_SIZE(devsec->devsec_ports); i++) { > + struct devsec_port *devsec_port = devsec_port_alloc(); > + int rc; > + > + if (IS_ERR(devsec_port)) > + return PTR_ERR(devsec_port); > + rc = devm_add_action_or_reset(dev, destroy_port, devsec_port); > + if (rc) > + return rc; > + devsec->devsec_ports[i] = devsec_port; > + } > + > + return 0; > +} > + > +static int __init devsec_bus_probe(struct platform_device *pdev) > +{ > + int rc; > + struct devsec *devsec; > + u64 mmio_size = SZ_64G; > + struct devsec_sysdata *sd; > + struct pci_host_bridge *hb; > + struct device *dev = &pdev->dev; > + u64 mmio_start = iomem_resource.end + 1 - SZ_64G; > + > + hb = devm_pci_alloc_host_bridge( > + dev, sizeof(*devsec) - sizeof(struct pci_host_bridge)); I'd move dev up a line. > + if (!hb) > + return -ENOMEM; > diff --git a/samples/devsec/tsm.c b/samples/devsec/tsm.c > new file mode 100644 > index 000000000000..a4705212a7e4 > --- /dev/null > +++ b/samples/devsec/tsm.c > @@ -0,0 +1,173 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* Copyright(c) 2024 - 2025 Intel Corporation. All rights reserved. */ > + > +static const struct pci_tsm_ops *__devsec_pci_ops; > + > +static struct pci_tsm *devsec_tsm_pf0_probe(struct pci_dev *pdev) > +{ > + int rc; > + > + struct devsec_tsm_pf0 *devsec_tsm __free(kfree) = > + kzalloc(sizeof(*devsec_tsm), GFP_KERNEL); > + if (!devsec_tsm) > + return NULL; > + > + rc = pci_tsm_pf0_constructor(pdev, &devsec_tsm->pci, __devsec_pci_ops); As below. I'm not seeing why we can't use &devsec_pci_ops directly here. > + if (rc) > + return NULL; > + > + pci_dbg(pdev, "tsm enabled\n"); > + return &no_free_ptr(devsec_tsm)->pci.tsm; > +} > + > +static struct pci_tsm *devsec_tsm_fn_probe(struct pci_dev *pdev) > +{ > + int rc; > + > + struct devsec_tsm_fn *devsec_tsm __free(kfree) = > + kzalloc(sizeof(*devsec_tsm), GFP_KERNEL); > + if (!devsec_tsm) > + return NULL; > + > + rc = pci_tsm_constructor(pdev, &devsec_tsm->pci, __devsec_pci_ops); here as well. > + if (rc) > + return NULL; > + > + pci_dbg(pdev, "tsm (sub-function) enabled\n"); > + return &no_free_ptr(devsec_tsm)->pci; > +} > +static struct pci_tsm_ops devsec_pci_ops = { > + .probe = devsec_tsm_pci_probe, > + .remove = devsec_tsm_pci_remove, > + .connect = devsec_tsm_connect, > + .disconnect = devsec_tsm_disconnect, > +}; > + > +static void devsec_tsm_remove(void *tsm_dev) > +{ > + tsm_unregister(tsm_dev); > +} > + > +static int devsec_tsm_probe(struct faux_device *fdev) > +{ > + struct tsm_dev *tsm_dev; > + > + tsm_dev = tsm_register(&fdev->dev, NULL, &devsec_pci_ops); > + if (IS_ERR(tsm_dev)) > + return PTR_ERR(tsm_dev); > + > + return devm_add_action_or_reset(&fdev->dev, devsec_tsm_remove, > + tsm_dev); > +} > + > +static struct faux_device *devsec_tsm; > + > +static const struct faux_device_ops devsec_device_ops = { > + .probe = devsec_tsm_probe, > +}; > + > +static int __init devsec_tsm_init(void) > +{ > + __devsec_pci_ops = &devsec_pci_ops; I'm not immediately grasping why this global is needed. You never check if it's set, so why not just move definition of devsec_pci_ops early enough that can be directly used everywhere. > + devsec_tsm = faux_device_create("devsec_tsm", NULL, &devsec_device_ops); > + if (!devsec_tsm) > + return -ENOMEM; > + return 0; > +} > +module_init(devsec_tsm_init); > + > +static void __exit devsec_tsm_exit(void) > +{ > + faux_device_destroy(devsec_tsm); > +} > +module_exit(devsec_tsm_exit); > + > +MODULE_LICENSE("GPL"); > +MODULE_DESCRIPTION("Device Security Sample Infrastructure: Platform TSM Driver");
Jonathan Cameron wrote: > On Thu, 17 Jul 2025 11:33:53 -0700 > Dan Williams <dan.j.williams@intel.com> wrote: > > > Establish just enough emulated PCI infrastructure to register a sample > > TSM (platform security manager) driver and have it discover an IDE + TEE > > (link encryption + device-interface security protocol (TDISP)) capable > > device. > > > > Use the existing a CONFIG_PCI_BRIDGE_EMUL to emulate an IDE capable root > > port, and open code the emulation of an endpoint device via simulated > > configuration cycle responses. > > > > The devsec_tsm driver responds to the PCI core TSM operations as if it > > successfully exercised the given interface security protocol message. > > > > The devsec_bus and devsec_tsm drivers can be loaded in either order to > > reflect cases like SEV-TIO where the TSM is PCI-device firmware, and > > cases like TDX Connect where the TSM is a software agent running on the > > host CPU. > > > > Follow-on patches add common code for TSM managed IDE establishment. For > > now, just successfully complete setup and teardown of the DSM (device > > security manager) context as a building block for management of TDI > > (trusted device interface) instances. > > > > # modprobe devsec_bus > > devsec_bus devsec_bus: PCI host bridge to bus 10000:00 > > pci_bus 10000:00: root bus resource [bus 00-01] > > pci_bus 10000:00: root bus resource [mem 0xf000000000-0xffffffffff 64bit] > > pci 10000:00:00.0: [8086:7075] type 01 class 0x060400 PCIe Root Port > > pci 10000:00:00.0: PCI bridge to [bus 00] > > pci 10000:00:00.0: bridge window [io 0x0000-0x0fff] > > pci 10000:00:00.0: bridge window [mem 0x00000000-0x000fffff] > > pci 10000:00:00.0: bridge window [mem 0x00000000-0x000fffff 64bit pref] > > pci 10000:00:00.0: bridge configuration invalid ([bus 00-00]), reconfiguring > > pci 10000:01:00.0: [8086:ffff] type 00 class 0x000000 PCIe Endpoint > > pci 10000:01:00.0: BAR 0 [mem 0xf000000000-0xf0001fffff 64bit pref] > > pci_doe_abort: pci 10000:01:00.0: DOE: [100] Issuing Abort > > pci_doe_cache_protocols: pci 10000:01:00.0: DOE: [100] Found protocol 0 vid: 1 prot: 1 > > pci 10000:01:00.0: disabling ASPM on pre-1.1 PCIe device. You can enable it with 'pcie_aspm=force' > > pci 10000:00:00.0: PCI bridge to [bus 01] > > pci_bus 10000:01: busn_res: [bus 01] end is updated to 01 > > # modprobe devsec_tsm > > devsec_tsm_pci_probe: pci 10000:01:00.0: devsec: tsm enabled > > __pci_tsm_init: pci 10000:01:00.0: TSM: Device security capabilities detected ( ide tee ), TSM attach > > > > Cc: Bjorn Helgaas <bhelgaas@google.com> > > Cc: Lukas Wunner <lukas@wunner.de> > > Cc: Samuel Ortiz <sameo@rivosinc.com> > > Cc: Alexey Kardashevskiy <aik@amd.com> > > Cc: Xu Yilun <yilun.xu@linux.intel.com> > > Signed-off-by: Dan Williams <dan.j.williams@intel.com> > > A fairly superficial review. Too much staring at code today > to check the emulation was right and have any chance of spotting bugs! > > > diff --git a/samples/devsec/bus.c b/samples/devsec/bus.c > > new file mode 100644 > > index 000000000000..675e185fcf79 > > --- /dev/null > > +++ b/samples/devsec/bus.c > > @@ -0,0 +1,708 @@ > > > +static int alloc_devs(struct devsec *devsec) > > +{ > > + struct device *dev = devsec->dev; > > Similar to below. Maybe use it inline. ok. > > + for (int i = 0; i < ARRAY_SIZE(devsec->devsec_devs); i++) { > > + struct devsec_dev *devsec_dev = devsec_dev_alloc(devsec); > > + int rc; > > + > > + if (IS_ERR(devsec_dev)) > > + return PTR_ERR(devsec_dev); > > + rc = devm_add_action_or_reset(dev, destroy_devsec_dev, > > + devsec_dev); > > + if (rc) > > + return rc; > > + devsec->devsec_devs[i] = devsec_dev; > > + } > > + > > + return 0; > > +} > > > > +static int init_port(struct devsec_port *devsec_port) > > +{ > > + struct pci_bridge_emul *bridge = &devsec_port->bridge; > > + > > + *bridge = (struct pci_bridge_emul) { > > + .conf = { > > + .vendor = cpu_to_le16(0x8086), > > + .device = cpu_to_le16(0x7075), > > Emulating something real? If not maybe we should get an ID from another space > (or reserve this one ;) I am happy to switch to something else, but no, I do not have time to chase this through PCI SIG. I do not expect this id to cause conflicts, but no guarantees. > > + .class_revision = cpu_to_le32(0x1), > > + .pref_mem_base = cpu_to_le16(PCI_PREF_RANGE_TYPE_64), > > + .pref_mem_limit = cpu_to_le16(PCI_PREF_RANGE_TYPE_64), > > + }, > > > > +{ > > + struct device *dev = devsec->dev; > > Only used once. I'd move it down there. ok. > > > + > > + for (int i = 0; i < ARRAY_SIZE(devsec->devsec_ports); i++) { > > + struct devsec_port *devsec_port = devsec_port_alloc(); > > + int rc; > > + > > + if (IS_ERR(devsec_port)) > > + return PTR_ERR(devsec_port); > > + rc = devm_add_action_or_reset(dev, destroy_port, devsec_port); > > + if (rc) > > + return rc; > > + devsec->devsec_ports[i] = devsec_port; > > + } > > + > > + return 0; > > +} > > + > > +static int __init devsec_bus_probe(struct platform_device *pdev) > > +{ > > + int rc; > > + struct devsec *devsec; > > + u64 mmio_size = SZ_64G; > > + struct devsec_sysdata *sd; > > + struct pci_host_bridge *hb; > > + struct device *dev = &pdev->dev; > > + u64 mmio_start = iomem_resource.end + 1 - SZ_64G; > > + > > + hb = devm_pci_alloc_host_bridge( > > + dev, sizeof(*devsec) - sizeof(struct pci_host_bridge)); > > I'd move dev up a line. clang-format disagrees and I prefer just letting a tool do my formatting. [..] > > +static int __init devsec_tsm_init(void) > > +{ > > + __devsec_pci_ops = &devsec_pci_ops; > > I'm not immediately grasping why this global is needed. > You never check if it's set, so why not just move definition of devsec_pci_ops > early enough that can be directly used everywhere. Because devsec_pci_ops is used inside the ops it declares. So either forward declare all those ops, or do this pointer assigment dance. I opted for the latter as it is smaller.
+CC Gerd, of off chance we can use a Redhat PCI device ID for kernel emulation similar to those they let Qemu use. > > > > + for (int i = 0; i < ARRAY_SIZE(devsec->devsec_devs); i++) { > > > + struct devsec_dev *devsec_dev = devsec_dev_alloc(devsec); > > > + int rc; > > > + > > > + if (IS_ERR(devsec_dev)) > > > + return PTR_ERR(devsec_dev); > > > + rc = devm_add_action_or_reset(dev, destroy_devsec_dev, > > > + devsec_dev); > > > + if (rc) > > > + return rc; > > > + devsec->devsec_devs[i] = devsec_dev; > > > + } > > > + > > > + return 0; > > > +} > > > > > > > +static int init_port(struct devsec_port *devsec_port) > > > +{ > > > + struct pci_bridge_emul *bridge = &devsec_port->bridge; > > > + > > > + *bridge = (struct pci_bridge_emul) { > > > + .conf = { > > > + .vendor = cpu_to_le16(0x8086), > > > + .device = cpu_to_le16(0x7075), > > > > Emulating something real? If not maybe we should get an ID from another space > > (or reserve this one ;) > > I am happy to switch to something else, but no, I do not have time to > chase this through PCI SIG. I do not expect this id to cause conflicts, > but no guarantees. Nothing to do with the SIG - you definitely don't want to try talking them into giving a Vendor ID for the kernel. That's an Intel ID so you need to find the owner of whatever tracker Intel uses for these. Or maybe we can ask for one of the Redhat ones (maintained by Gerd). > > > > + .class_revision = cpu_to_le32(0x1), > > > + .pref_mem_base = cpu_to_le16(PCI_PREF_RANGE_TYPE_64), > > > + .pref_mem_limit = cpu_to_le16(PCI_PREF_RANGE_TYPE_64), > > > + }, > > > > ... > > > +static int __init devsec_tsm_init(void) > > > +{ > > > + __devsec_pci_ops = &devsec_pci_ops; > > > > I'm not immediately grasping why this global is needed. > > You never check if it's set, so why not just move definition of devsec_pci_ops > > early enough that can be directly used everywhere. > > Because devsec_pci_ops is used inside the ops it declares. So either > forward declare all those ops, or do this pointer assigment dance. I > opted for the latter as it is smaller. Ok. I guess in emulation that's a reasonable compromise. Maybe leave a comment somewhere to avoid repeat of this feedback. Jonathan >
Jonathan Cameron wrote: > > +CC Gerd, of off chance we can use a Redhat PCI device ID for kernel > emulation similar to those they let Qemu use. > [..] > > > Emulating something real? If not maybe we should get an ID from another space > > > (or reserve this one ;) > > > > I am happy to switch to something else, but no, I do not have time to > > chase this through PCI SIG. I do not expect this id to cause conflicts, > > but no guarantees. > > Nothing to do with the SIG - you definitely don't want to try talking them > into giving a Vendor ID for the kernel. That's an Intel ID so you need to find > the owner of whatever tracker Intel uses for these. About the same level of difficulty... > Or maybe we can ask for one of the Redhat ones (maintained by Gerd). In the meantime I added this to the Kconfig because I also received a report of an AMD platform being confused about this extra PCI domain. This protects against allyesconfig builds autoloading this thing which should only be used with unit tests. diff --git a/samples/Kconfig b/samples/Kconfig index 8441593fb654..9ad822d4e808 100644 --- a/samples/Kconfig +++ b/samples/Kconfig @@ -327,6 +327,7 @@ source "samples/damon/Kconfig" config SAMPLE_DEVSEC tristate "Build a sample TEE Security Manager with an emulated PCI endpoint" + depends on m depends on PCI depends on VIRT_DRIVERS depends on PCI_DOMAINS_GENERIC || X86 @@ -339,7 +340,11 @@ config SAMPLE_DEVSEC devsec_bus and devsec_tsm, exercise device-security enumeration, PCI subsystem use ABIs, device security flows. For example, exercise IDE (link encryption) establishment and TDISP state transitions via a - Device Security Manager (DSM). + Device Security Manager (DSM). Note the emulation uses a device-id + (vendor: 0x8086 device: 0x7075) that is *assumed* unused and some + architectures may be confused by this emulated PCI domain. + + If unsure, say N endif # SAMPLES > > > > > > > + .class_revision = cpu_to_le32(0x1), > > > > + .pref_mem_base = cpu_to_le16(PCI_PREF_RANGE_TYPE_64), > > > > + .pref_mem_limit = cpu_to_le16(PCI_PREF_RANGE_TYPE_64), > > > > + }, > > > > > > > > ... > > > > +static int __init devsec_tsm_init(void) > > > > +{ > > > > + __devsec_pci_ops = &devsec_pci_ops; > > > > > > I'm not immediately grasping why this global is needed. > > > You never check if it's set, so why not just move definition of devsec_pci_ops > > > early enough that can be directly used everywhere. > > > > Because devsec_pci_ops is used inside the ops it declares. So either > > forward declare all those ops, or do this pointer assigment dance. I > > opted for the latter as it is smaller. > Ok. I guess in emulation that's a reasonable compromise. Maybe leave > a comment somewhere to avoid repeat of this feedback. I expect all the low-level tsm drivers will struggle with this, so expect to see this pattern repeat outside of emulation.
On Wed, Aug 06, 2025 at 11:33:18AM -0700, dan.j.williams@intel.com wrote: > Jonathan Cameron wrote: > > > > +CC Gerd, of off chance we can use a Redhat PCI device ID for kernel > > emulation similar to those they let Qemu use. > > > [..] > > > > Emulating something real? If not maybe we should get an ID from another space > > > > (or reserve this one ;) > > > > > > I am happy to switch to something else, but no, I do not have time to > > > chase this through PCI SIG. I do not expect this id to cause conflicts, > > > but no guarantees. > > > > Nothing to do with the SIG - you definitely don't want to try talking them > > into giving a Vendor ID for the kernel. That's an Intel ID so you need to find > > the owner of whatever tracker Intel uses for these. > > About the same level of difficulty... > > > Or maybe we can ask for one of the Redhat ones (maintained by Gerd). Well, they are meant for virtual devices emulated by qemu (and the registry is docs/specs/pci-ids.rst in the qemu repo). We made exceptions to that rule before (linux/samples/vfio-mdev/mdpy.c got one for example). So feel free to try sending a patch with an update to qemu-devel. There should be a /good/ explanation why you want go that route, and "I'm to lazy to get one from my employer" is not what I'd consider "good". Also it's qemu release freeze and vacation season right now, so don't expect this process to be fast. take care, Gerd
Gerd Hoffmann wrote: > On Wed, Aug 06, 2025 at 11:33:18AM -0700, dan.j.williams@intel.com wrote: > > Jonathan Cameron wrote: > > > > > > +CC Gerd, of off chance we can use a Redhat PCI device ID for kernel > > > emulation similar to those they let Qemu use. > > > > > [..] > > > > > Emulating something real? If not maybe we should get an ID from another space > > > > > (or reserve this one ;) > > > > > > > > I am happy to switch to something else, but no, I do not have time to > > > > chase this through PCI SIG. I do not expect this id to cause conflicts, > > > > but no guarantees. > > > > > > Nothing to do with the SIG - you definitely don't want to try talking them > > > into giving a Vendor ID for the kernel. That's an Intel ID so you need to find > > > the owner of whatever tracker Intel uses for these. > > > > About the same level of difficulty... > > > > > Or maybe we can ask for one of the Redhat ones (maintained by Gerd). > > Well, they are meant for virtual devices emulated by qemu (and the > registry is docs/specs/pci-ids.rst in the qemu repo). > > We made exceptions to that rule before (linux/samples/vfio-mdev/mdpy.c > got one for example). So feel free to try sending a patch with an > update to qemu-devel. There should be a /good/ explanation why you want > go that route, and "I'm to lazy to get one from my employer" is not what > I'd consider "good". Also it's qemu release freeze and vacation season > right now, so don't expect this process to be fast. Thanks for the details Gerd. IIUC that samples/vfio-mdev/ example is for a functional use case. samples/devsec/ is not a functional use case. It does not need an exclusive id to enable some limited unit testing. If samples/devsec/ ever causes real world conflict the resolution is turn it off / refrain from loading it.
© 2016 - 2025 Red Hat, Inc.