diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
index ecf47559f495..e1b8f15e4a3a 100644
--- a/Documentation/ABI/testing/sysfs-bus-pci
+++ b/Documentation/ABI/testing/sysfs-bus-pci
@@ -500,3 +500,31 @@ Description:
 		console drivers from the device.  Raw users of pci-sysfs
 		resourceN attributes must be terminated prior to resizing.
 		Success of the resizing operation is not guaranteed.
+
+What:		/sys/bus/pci/devices/.../doe_features
+Date:		July 2024
+Contact:	Linux PCI developers <linux-pci@vger.kernel.org>
+Description:
+		This directory contains a list of the supported
+		Data Object Exchange (DOE) features. The feature values are
+		the file name. The contents of each file is the raw vendor id and
+		data object feature values, the same as the name.
+
+		The value comes from the device and specifies the vendor and
+		data object type supported. The lower (RHS of the colon) is
+		the data object type in hex. The upper (LHS of the colon)
+		is the vendor ID.
+
+		As all DOE devices must support the DOE discovery protocol, if
+		DOE is supported you will at least see this file, with
+		this contents
+
+		# cat doe_features/0001:00
+		0001:00
+
+		If the device supports other protocols you will see other files
+		as well. For example is CMA/SPDM and secure CMA/SPDM are supported
+		the doe_features directory will look like this
+
+		# ls doe_features
+		0001:00        0001:01        0001:02
diff --git a/drivers/pci/doe.c b/drivers/pci/doe.c
index defc4be81bd4..e7b702afce88 100644
--- a/drivers/pci/doe.c
+++ b/drivers/pci/doe.c
@@ -14,6 +14,7 @@
 
 #include <linux/bitfield.h>
 #include <linux/delay.h>
+#include <linux/device.h>
 #include <linux/jiffies.h>
 #include <linux/mutex.h>
 #include <linux/pci.h>
@@ -47,6 +48,8 @@
  * @wq: Wait queue for work item
  * @work_queue: Queue of pci_doe_work items
  * @flags: Bit array of PCI_DOE_FLAG_* flags
+ * @device_attrs: Array of device attributes, used in cleanup
+ * @sysfs_attrs: Array of sysfs attributes, used in cleanup
  */
 struct pci_doe_mb {
 	struct pci_dev *pdev;
@@ -56,6 +59,11 @@ struct pci_doe_mb {
 	wait_queue_head_t wq;
 	struct workqueue_struct *work_queue;
 	unsigned long flags;
+
+#ifdef CONFIG_SYSFS
+	struct device_attribute *device_attrs;
+	struct attribute **sysfs_attrs;
+#endif
 };
 
 struct pci_doe_feature {
@@ -92,6 +100,58 @@ struct pci_doe_task {
 	struct pci_doe_mb *doe_mb;
 };
 
+#ifdef CONFIG_SYSFS
+static bool pci_doe_features_sysfs_group_visible(struct kobject *kobj)
+{
+	struct pci_dev *pdev = to_pci_dev(kobj_to_dev(kobj));
+	struct pci_doe_mb *doe_mb;
+	unsigned long index;
+
+	xa_for_each(&pdev->doe_mbs, index, doe_mb) {
+		if (!xa_empty(&doe_mb->feats))
+			return true;
+	}
+
+	return false;
+}
+DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(pci_doe_features_sysfs)
+
+struct attribute_group pci_doe_sysfs_group = {
+	.name	    = "doe_features",
+	.is_visible = SYSFS_GROUP_VISIBLE(pci_doe_features_sysfs),
+};
+
+static ssize_t pci_doe_sysfs_feature_show(struct device *dev,
+					  struct device_attribute *attr,
+					  char *buf)
+{
+	return sysfs_emit(buf, "%s\n", attr->attr.name);
+}
+
+static void pci_doe_sysfs_feature_remove(struct pci_dev *pdev,
+					 struct pci_doe_mb *doe_mb)
+{
+	struct device_attribute *attrs = doe_mb->device_attrs;
+	struct attribute **sysfs_attrs = doe_mb->sysfs_attrs;
+	unsigned long i;
+	void *entry;
+
+	if (!attrs)
+		return;
+
+	doe_mb->device_attrs = NULL;
+	doe_mb->sysfs_attrs = NULL;
+
+	xa_for_each(&doe_mb->feats, i, entry) {
+		if (attrs[i].attr.name)
+			kfree(attrs[i].attr.name);
+	}
+
+	kfree(attrs);
+	kfree(sysfs_attrs);
+}
+#endif
+
 static int pci_doe_wait(struct pci_doe_mb *doe_mb, unsigned long timeout)
 {
 	if (wait_event_timeout(doe_mb->wq,
@@ -687,6 +747,12 @@ void pci_doe_init(struct pci_dev *pdev)
 {
 	struct pci_doe_mb *doe_mb;
 	u16 offset = 0;
+	struct attribute **sysfs_attrs;
+	struct device_attribute *attrs;
+	unsigned long num_features = 0;
+	unsigned long i;
+	unsigned long vid, type;
+	void *entry;
 	int rc;
 
 	xa_init(&pdev->doe_mbs);
@@ -707,6 +773,45 @@ void pci_doe_init(struct pci_dev *pdev)
 			pci_doe_destroy_mb(doe_mb);
 		}
 	}
+
+	if (doe_mb) {
+		xa_for_each(&doe_mb->feats, i, entry)
+			num_features++;
+
+		sysfs_attrs = kcalloc(num_features + 1, sizeof(*sysfs_attrs), GFP_KERNEL);
+		if (!sysfs_attrs)
+			return;
+
+		attrs = kcalloc(num_features, sizeof(*attrs), GFP_KERNEL);
+		if (!attrs) {
+			kfree(sysfs_attrs);
+			return;
+		}
+
+		doe_mb->device_attrs = attrs;
+		doe_mb->sysfs_attrs = sysfs_attrs;
+
+		xa_for_each(&doe_mb->feats, i, entry) {
+			sysfs_attr_init(&attrs[i].attr);
+
+			vid = xa_to_value(entry) >> 8;
+			type = xa_to_value(entry) & 0xFF;
+
+			attrs[i].attr.name = kasprintf(GFP_KERNEL, "%04lx:%02lx", vid, type);
+			if (!attrs[i].attr.name) {
+				pci_doe_sysfs_feature_remove(pdev, doe_mb);
+				return;
+			}
+			attrs[i].attr.mode = 0444;
+			attrs[i].show = pci_doe_sysfs_feature_show;
+
+			sysfs_attrs[i] = &attrs[i].attr;
+		}
+
+		sysfs_attrs[num_features] = NULL;
+
+		pci_doe_sysfs_group.attrs = sysfs_attrs;
+	}
 }
 
 void pci_doe_destroy(struct pci_dev *pdev)
@@ -714,8 +819,10 @@ void pci_doe_destroy(struct pci_dev *pdev)
 	struct pci_doe_mb *doe_mb;
 	unsigned long index;
 
-	xa_for_each(&pdev->doe_mbs, index, doe_mb)
+	xa_for_each(&pdev->doe_mbs, index, doe_mb) {
+		pci_doe_sysfs_feature_remove(pdev, doe_mb);
 		pci_doe_destroy_mb(doe_mb);
+	}
 
 	xa_destroy(&pdev->doe_mbs);
 }
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index 40cfa716392f..cd838b85d6ab 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -16,6 +16,7 @@
 #include <linux/kernel.h>
 #include <linux/sched.h>
 #include <linux/pci.h>
+#include <linux/pci-doe.h>
 #include <linux/stat.h>
 #include <linux/export.h>
 #include <linux/topology.h>
@@ -1661,6 +1662,9 @@ const struct attribute_group *pci_dev_attr_groups[] = {
 #endif
 #ifdef CONFIG_PCIEASPM
 	&aspm_ctrl_attr_group,
+#endif
+#ifdef CONFIG_PCI_DOE
+	&pci_doe_sysfs_group,
 #endif
 	NULL,
 };
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index fd44565c4756..a26c586cdeb4 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -189,6 +189,7 @@ extern const struct attribute_group *pci_dev_groups[];
 extern const struct attribute_group *pci_dev_attr_groups[];
 extern const struct attribute_group *pcibus_groups[];
 extern const struct attribute_group *pci_bus_groups[];
+extern struct attribute_group pci_doe_sysfs_group;
 #else
 static inline int pci_create_sysfs_dev_files(struct pci_dev *pdev) { return 0; }
 static inline void pci_remove_sysfs_dev_files(struct pci_dev *pdev) { }
@@ -196,6 +197,7 @@ static inline void pci_remove_sysfs_dev_files(struct pci_dev *pdev) { }
 #define pci_dev_attr_groups NULL
 #define pcibus_groups NULL
 #define pci_bus_groups NULL
+#define pci_doe_sysfs_group NULL
 #endif
 
 extern unsigned long pci_hotplug_io_size;
