diff --git a/hw/pci/meson.build b/hw/pci/meson.build
index 5c4bbac817..7336620ee3 100644
--- a/hw/pci/meson.build
+++ b/hw/pci/meson.build
@@ -11,7 +11,7 @@ pci_ss.add(files(
 # The functions in these modules can be used by devices too.  Since we
 # allow plugging PCIe devices into PCI buses, include them even if
 # CONFIG_PCI_EXPRESS=n.
-pci_ss.add(files('pcie.c', 'pcie_aer.c'))
+pci_ss.add(files('pcie.c', 'pcie_aer.c',  'pcie_doe.c'))
 softmmu_ss.add(when: 'CONFIG_PCI_EXPRESS', if_true: files('pcie_port.c', 'pcie_host.c'))
 softmmu_ss.add_all(when: 'CONFIG_PCI', if_true: pci_ss)
 
diff --git a/hw/pci/pcie_doe.c b/hw/pci/pcie_doe.c
new file mode 100644
index 0000000000..8739c41280
--- /dev/null
+++ b/hw/pci/pcie_doe.c
@@ -0,0 +1,257 @@
+/*
+ * pcie_doe.c
+ * utility functions for pci express data object exchange introduced
+ * in PCI 5.0 Data Object Exchange (DOE) ECN
+ *
+ * Copyright (c) 2021 Jonathan Cameron <Jonathan.Cameron@huawei.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qemu/error-report.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/doe.h"
+#include "hw/qdev-properties.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/range.h"
+#include "qemu/rcu.h"
+#include "sysemu/hostmem.h"
+
+struct doe_handler {
+    uint16_t vendor_id;
+    uint8_t object_type;
+    doe_msg_handler_t handler;
+    void *priv;
+};
+
+static void doe_set_ctl(PCIEDOE *doe, uint32_t val)
+{
+    /* Abort */
+    if (val & PCI_DOE_CTRL_DOE_ABORT) {
+        doe->req_index = 0;
+        doe->rsp_index = 0;
+        doe->req_length = 0;
+        doe->error = false;
+        doe->data_object_ready = false;
+    }
+
+    if (val & PCI_DOE_CTRL_DOE_GO) {
+        GList *l;
+        uint16_t vendor_id = doe->store[0] & PCI_DATA_OBJ_DW0_VID;
+        uint8_t object_type = (doe->store[0] & PCI_DATA_OBJ_DW0_TYPE) >>
+            ctz32(PCI_DATA_OBJ_DW0_TYPE);
+        if ((doe->req_index != 3) || (doe->req_length != 3)) {
+            /*
+             * Not entirely clear what should happen if req_length is correct
+             * buf insufficient data has been received.
+             */
+            doe->error = true;
+            return;
+        }
+        /* Discovery protocol - DOE ECN */
+        if (vendor_id == PCI_VENDOR_ID_PCI_SIG &&
+            object_type == PCI_DOE_DIS_OBJ_TYPE) {
+            uint8_t index = doe->store[2] & PCI_DOE_DIS_REQ_D0_DW0_INDEX;
+            doe->store[1] = 3;
+            if (index == 0) {
+                /* First entry is this one, the discovery protocol itself */
+                uint8_t next;
+
+                if (doe->cb_list) {
+                    next = index + 1;
+                } else {
+                    next = 0;
+                }
+                doe->store[2] =
+                    (next << ctz32(PCI_DOE_DIS_RSP_D0_DW0_NEXT_INDEX)) |
+                    (0 << ctz32(PCI_DOE_DIS_RSP_D0_DW0_PROT)) |
+                    0x0001;
+            } else {
+                /* Other entries based on register callbacks */
+                uint8_t next;
+                struct doe_handler *h;
+
+                h = g_list_nth_data(doe->cb_list, index - 1);
+                /*
+                 * Off end of list, Table 7-x4 in DOE ECN -
+                 * Vendor ID 0xFFFF if no more indices
+                 */
+                if (h == NULL) {
+                    doe->store[2] = 0xFFFF;
+                } else {
+                    if (g_list_nth(doe->cb_list, index)) {
+                        next = index + 1;
+                    } else {
+                        next = 0;
+                    }
+                    doe->store[2] =
+                        (next << ctz32(PCI_DOE_DIS_RSP_D0_DW0_NEXT_INDEX)) |
+                        (h->object_type << ctz32(PCI_DOE_DIS_RSP_D0_DW0_PROT)) |
+                        h->vendor_id;
+                }
+            }
+            doe->data_object_ready = true;
+            doe->rsp_index = 0;
+        } else {
+            for (l = doe->cb_list; l != NULL; l = l->next) {
+                struct doe_handler *h = l->data;
+                if (h->vendor_id == vendor_id &&
+                    h->object_type == object_type) {
+                    int ret = h->handler(doe, h->vendor_id, h->object_type,
+                                         h->priv);
+                    if (ret) {
+                        /*
+                         * No response so as per 6.xx.1 in DOE ECN
+                         * "... within 1 second after the DOE Go bit was Set
+                         *  in the DOE Control register, otherwise the DOE
+                         *  instance must Set the DOE Error bit in the DOE
+                         *  Status register.."
+                         */
+                         doe->error = true;
+                        break;
+                    }
+                    doe->data_object_ready = true;
+                    doe->rsp_index = 0;
+                    break;
+                }
+            }
+            /* Comamnd not handled */
+            if (l == NULL) {
+                doe->error = true;
+            }
+        }
+        /* Reset input index to allow for a new message */
+        doe->req_index = 0;
+    }
+}
+
+static void doe_set_write_mailbox(PCIEDOE *doe, uint32_t val)
+{
+    if (doe->req_index == 1) {
+        if (val & 0x3FFFF) {
+            doe->req_length = val & PCI_DATA_OBJ_DW1_LEN;
+        } else {
+            doe->req_length = 1 << 18;
+        }
+    }
+    if (doe->req_length && doe->req_index == doe->req_length) {
+        /*
+         * 6.xx.1 Data Objects
+         * If the DW transferred does not match the indicated Length
+         * for a data object, then the data object must be
+         * silently discarded
+         */
+        return;
+    }
+    doe->store[doe->req_index] = val;
+    doe->req_index++;
+}
+
+static uint32_t doe_get_read_mailbox(PCIEDOE *doe)
+{
+    uint32_t val;
+
+    if (doe->rsp_index == 0) {
+        doe->rsp_length = doe->store[1] & PCI_DATA_OBJ_DW1_LEN;
+    }
+    if (!doe->data_object_ready) {
+        /* Underflow of the Read Data Mailbox Mechanism */
+        doe->error = true;
+        return 0;
+    }
+
+    val = doe->store[doe->rsp_index];
+    doe->rsp_index++;
+    if (doe->rsp_index == doe->rsp_length) {
+        doe->rsp_index = -1;
+        doe->data_object_ready = false;
+    }
+
+    return val;
+}
+
+static uint32_t doe_get_status(PCIEDOE *doe)
+{
+    uint32_t val = 0;
+
+    if (doe->busy) {
+        val |= PCI_DOE_STATUS_DOE_BUSY;
+    }
+    /* bit 1: interrupt not yet supported */
+    if (doe->error) {
+        val |= PCI_DOE_STATUS_DOE_ERROR;
+    }
+    if (doe->data_object_ready) {
+        val |= PCI_DOE_STATUS_DATA_OBJECT_READY;
+    }
+
+    return val;
+}
+
+void doe_add_message_handler(PCIEDOE *doe, uint16_t vendor_id,
+                             uint8_t object_type,
+                             const doe_msg_handler_t handler, void *priv)
+{
+    struct doe_handler *h = g_malloc0(sizeof(*handler));
+
+    h->vendor_id = vendor_id;
+    h->object_type = object_type;
+    h->handler = handler;
+    h->priv = priv;
+    doe->cb_list = g_list_append(doe->cb_list, h);
+}
+
+uint32_t pcie_doe_ecap(PCIEDOE *doe, PCIDevice *d, uint16_t offset)
+{
+    doe->doe_base = offset;
+    /* Length field is 18 bits and is in dwords */
+    doe->store = g_malloc0((1 << 18) * sizeof(uint32_t));
+
+    pcie_add_capability(d, PCI_EXT_CAP_ID_DOE, 1, offset, 0x18);
+    offset += 0x18;
+
+    return offset;
+}
+
+void pcie_doe_write(PCIEDOE *doe, uint32_t addr, uint32_t val, int len)
+{
+    if (len != 4) {
+        return;
+    }
+
+    switch (addr - doe->doe_base) {
+    case PCI_DOE_CTRL:
+        doe_set_ctl(doe, val);
+        break;
+    case PCI_DOE_WRITE_MAILBOX:
+        doe_set_write_mailbox(doe, val);
+        break;
+    }
+}
+
+uint32_t pcie_doe_read(PCIEDOE *doe, uint32_t addr, int len, int *found)
+{
+    if (len != 4) {
+        *found = 0;
+        return 0;
+    }
+
+    *found = 1;
+    switch (addr - doe->doe_base) {
+    case PCI_DOE_CAP:
+        return 0; /* No interrupt support */
+    case PCI_DOE_STATUS:
+        return doe_get_status(doe);
+    case PCI_DOE_READ_MAILBOX:
+        return doe_get_read_mailbox(doe);
+    default:
+        *found = 0;
+        return 0;
+    }
+}
+
diff --git a/include/hw/pci/doe.h b/include/hw/pci/doe.h
new file mode 100644
index 0000000000..364c866c53
--- /dev/null
+++ b/include/hw/pci/doe.h
@@ -0,0 +1,40 @@
+/*
+ * PCIE DOE emulation.
+ *
+ * Copyright (c) 2021 Jonathan Cameron <Jonathan.Cameron@huawei.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef QEMU_PCIE_DOE_H_
+#define QEMU_PCIE_DOE_H_
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "hw/qdev-properties.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+
+typedef struct pcie_doe {
+    uint32_t doe_base;
+    GList *cb_list;
+    int req_index;
+    int req_length;
+    int rsp_index;
+    int rsp_length;
+    bool data_object_ready;
+    bool error;
+    bool busy;
+    uint32_t *store;
+} PCIEDOE;
+
+typedef int (*doe_msg_handler_t)(PCIEDOE *doe, uint16_t vendor_id,
+                                 uint8_t object_type, void *priv);
+
+uint32_t pcie_doe_ecap(PCIEDOE *doe, PCIDevice *d, uint16_t offset);
+void doe_add_message_handler(PCIEDOE *doe, uint16_t vendor_id,
+                             uint8_t object_type,
+                             const doe_msg_handler_t handler, void *priv);
+uint32_t pcie_doe_read(PCIEDOE *doe, uint32_t addr, int len, int *found);
+void pcie_doe_write(PCIEDOE *doe, uint32_t addr, uint32_t val, int len);
+#endif
diff --git a/include/hw/pci/pci_ids.h b/include/hw/pci/pci_ids.h
index 76bf3ed590..636b2e8017 100644
--- a/include/hw/pci/pci_ids.h
+++ b/include/hw/pci/pci_ids.h
@@ -157,6 +157,8 @@
 
 /* Vendors and devices.  Sort key: vendor first, device next. */
 
+#define PCI_VENDOR_ID_PCI_SIG            0x0001
+
 #define PCI_VENDOR_ID_LSI_LOGIC          0x1000
 #define PCI_DEVICE_ID_LSI_53C810         0x0001
 #define PCI_DEVICE_ID_LSI_53C895A        0x0012
