diff --git a/hw/cxl/cxl-cdat.c b/hw/cxl/cxl-cdat.c
new file mode 100644
index 0000000000..6ed4c15cc0
--- /dev/null
+++ b/hw/cxl/cxl-cdat.c
@@ -0,0 +1,252 @@
+/*
+ * Support for CDAT entires as defined in
+ * Coherent Device Attribute Table (CDAT) Specification rev 1.02
+ * Available from uefi.org.
+ *
+ * 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/mem/memory-device.h"
+#include "hw/mem/pc-dimm.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"
+#include "sysemu/numa.h"
+#include "hw/cxl/cxl.h"
+
+void cdat_add_dsmas(CXLCDAT *cdat, uint8_t dsmad_handle, uint8_t flags,
+                    uint64_t dpa_base, uint64_t dpa_length)
+{
+    struct cxl_cdat_dsmas *dsmas = g_malloc0(sizeof(*dsmas));
+    dsmas->dsmad_handle = dsmad_handle;
+    dsmas->flags = flags;
+    dsmas->dpa_base = dpa_base;
+    dsmas->dpa_length = dpa_length;
+    cdat->dsmas_list = g_list_append(cdat->dsmas_list, dsmas);
+}
+
+void cdat_add_dslbis(CXLCDAT *cdat, uint8_t handle, uint8_t flags,
+                     uint8_t data_type, uint64_t base_unit,
+                     uint16_t entry0, uint16_t entry1, uint16_t entry2)
+{
+    struct cxl_cdat_dslbis *dslbis = g_malloc0(sizeof(*dslbis));
+    dslbis->handle = handle;
+    dslbis->flags = flags;
+    dslbis->data_type = data_type;
+    dslbis->entry_base_unit = base_unit;
+    dslbis->entries[0] = entry0;
+    dslbis->entries[1] = entry1;
+    dslbis->entries[2] = entry2;
+    cdat->dslbis_list = g_list_append(cdat->dslbis_list, dslbis);
+}
+
+void cdat_add_dsmscis(CXLCDAT *cdat, uint8_t dsmas_handle,
+                      uint64_t memory_sc_size, uint64_t cache_attrs)
+{
+    struct cxl_cdat_dsmscis *dsmscis = g_malloc(sizeof(*dsmscis));
+    dsmscis->dsmas_handle = dsmas_handle;
+    dsmscis->memory_side_cache_size = memory_sc_size;
+    dsmscis->cache_attributes = cache_attrs;
+    cdat->dsmscis_list = g_list_append(cdat->dsmscis_list, dsmscis);
+}
+
+void cdat_add_dsis(CXLCDAT *cdat, uint8_t flags, uint8_t handle)
+{
+    struct cxl_cdat_dsis *dsis = g_malloc(sizeof(*dsis));
+    dsis->flags = flags;
+    dsis->handle = handle;
+    cdat->dsis_list = g_list_append(cdat->dsis_list, dsis);
+}
+
+void cdat_add_dsemts(CXLCDAT *cdat, uint8_t dsmas_handle,
+                     uint8_t efi_mem_type_attr, uint64_t dpa_offset,
+                     uint64_t dpa_length)
+{
+    struct cxl_cdat_dsemts *dsemts = g_malloc(sizeof(*dsemts));
+    dsemts->dsmas_handle = dsmas_handle;
+    dsemts->efi_mem_type_attr = efi_mem_type_attr;
+    dsemts->dpa_offset = dpa_offset;
+    dsemts->dpa_length = dpa_length;
+    cdat->dsemts_list = g_list_append(cdat->dsemts_list, dsemts);
+}
+
+struct cxl_cdat_sslbis *cdat_add_sslbis(CXLCDAT *cdat, uint8_t num_entries,
+                                        uint8_t data_type, uint64_t base_unit)
+{
+    struct cxl_cdat_sslbis *sslbis =
+        g_malloc(sizeof(*sslbis) + num_entries * sizeof(sslbis->entries[0]));
+    sslbis->num_entries = num_entries;
+    sslbis->data_type = data_type;
+    sslbis->base_unit = base_unit;
+    cdat->sslbis_list = g_list_append(cdat->sslbis_list, sslbis);
+    return sslbis;
+}
+
+int cdata_sslbis_set_entry(struct cxl_cdat_sslbis *sslbis, uint8_t index,
+                           uint16_t portx, uint16_t porty, uint16_t val)
+{
+    struct cxl_cdat_sslbis_entry *entry;
+    if (index >= sslbis->num_entries) {
+        return -1;
+    }
+    entry = &sslbis->entries[index];
+    entry->port_x_id = portx;
+    entry->port_y_id = porty;
+    entry->val = val;
+    return 0;
+}
+
+int cxl_table_access(PCIEDOE *doe, uint16_t vendor_id, uint8_t object_type,
+                     void *priv)
+{
+    uint8_t table_type;
+    int total_entries;
+    uint16_t entry_handle;
+    CXLCDAT *cdat = priv;
+    uint16_t next_entry;
+
+    if (doe->req_length != 3) {
+        /* optional error for unexpected command length */
+        return -1;
+    }
+
+    if ((doe->store[2] & CXL_DOE_TABLE_ACCESS_DW2_RCODE) != 0) {
+        /*
+         * Only table access code currently supported.
+         * Error indication is lack of Data Object Ready
+         */
+        return -1;
+    }
+
+    table_type = (doe->store[2] & CXL_DOE_TABLE_ACCESS_DW2_TYPE) >>
+        ctz32(CXL_DOE_TABLE_ACCESS_DW2_TYPE);
+    if (table_type != 0) {
+        /* Unsuported table ID so just don't set Data Object Ready */
+        return -1;
+    }
+    entry_handle = (doe->store[2] & CXL_DOE_TABLE_ACCESS_DW2_ENTRYHANDLE) >>
+        ctz32(CXL_DOE_TABLE_ACCESS_DW2_ENTRYHANDLE);
+
+    /* Assume entry handle == CDAT structure index */
+    total_entries = g_list_length(cdat->dsmas_list) +
+        g_list_length(cdat->dslbis_list) +
+        g_list_length(cdat->dsmscis_list) +
+        g_list_length(cdat->dsis_list) +
+        g_list_length(cdat->dsemts_list) +
+        g_list_length(cdat->sslbis_list);
+    if (entry_handle + 1 == total_entries) {
+        next_entry = 0xFFFF;
+    } else {
+        next_entry = entry_handle + 1;
+    }
+
+    if (entry_handle < g_list_length(cdat->dsmas_list)) {
+        const int dsmas_len = 24;
+        struct cxl_cdat_dsmas *dsmas =
+            g_list_nth_data(cdat->dsmas_list, entry_handle);
+
+        doe->store[1] = 3 + dsmas_len / sizeof(uint32_t);
+        doe->store[2] = next_entry << 16;
+        doe->store[3] = (dsmas_len << 16) | CXL_CDAT_DSMAS_TYPE;
+
+        /* cxl version of proximity domain */
+        doe->store[4] = dsmas->dsmad_handle;
+        /* flags in 2nd byte of [4] (bit 2 is non volatile) */
+        doe->store[4] |= (dsmas->flags << 8);
+        doe->store[5] = dsmas->dpa_base & 0xFFFFFFFF;
+        doe->store[6] = (dsmas->dpa_base >> 32) & 0xFFFFFFFF;
+        doe->store[7] = dsmas->dpa_length & 0xFFFFFFFF;
+        doe->store[8] = (dsmas->dpa_length >> 32) & 0xFFFFFFFF;
+        return 0;
+    }
+    entry_handle -= g_list_length(cdat->dsmas_list);
+    if (entry_handle < g_list_length(cdat->dslbis_list)) {
+        const int dslbis_len = 24;
+        struct cxl_cdat_dslbis *dslbis =
+            g_list_nth_data(cdat->dslbis_list, entry_handle);
+
+        doe->store[1] = 3 + dslbis_len / sizeof(uint32_t);
+        doe->store[2] = next_entry << 16;
+        doe->store[3] = (dslbis_len << 16) | CXL_CDAT_DSLBIS_TYPE;
+
+        doe->store[4] = (dslbis->data_type << 24) | (dslbis->flags << 8) |
+            dslbis->handle;
+        doe->store[5] = dslbis->entry_base_unit & 0xFFFFFFFF;
+        doe->store[6] = (dslbis->entry_base_unit >> 32) & 0xFFFFFFFF;
+        doe->store[7] = (dslbis->entries[1] << 16) | dslbis->entries[0];
+        doe->store[8] = dslbis->entries[2];
+        return 0;
+    }
+    entry_handle -= g_list_length(cdat->dslbis_list);
+    if (entry_handle < g_list_length(cdat->dsmscis_list)) {
+        const int dsmscis_len = 20;
+        struct cxl_cdat_dsmscis *dsmscis =
+            g_list_nth_data(cdat->dsmscis_list, entry_handle);
+
+        doe->store[1] = 3 + dsmscis_len / sizeof(uint32_t);
+        doe->store[2] = next_entry << 16;
+        doe->store[3] = (dsmscis_len << 16) | CXL_CDAT_DSMSCIS_TYPE;
+        doe->store[4] = dsmscis->dsmas_handle;
+        doe->store[5] = dsmscis->memory_side_cache_size & 0xffffffff;
+        doe->store[6] = (dsmscis->memory_side_cache_size >> 32) & 0xffffffff;
+        doe->store[7] = dsmscis->cache_attributes;
+    }
+    entry_handle -= g_list_length(cdat->dsmscis_list);
+    if (entry_handle < g_list_length(cdat->dsis_list)) {
+        const int dsis_len = 8;
+        struct cxl_cdat_dsis *dsis =
+            g_list_nth_data(cdat->dsis_list, entry_handle);
+
+        doe->store[1] = 3 + dsis_len / sizeof(uint32_t);
+        doe->store[2] = next_entry << 16;
+        doe->store[3] = (dsis_len << 16) | CXL_CDAT_DSMSCIS_TYPE;
+        doe->store[4] = (dsis->handle << 8) | dsis->flags;
+    }
+    entry_handle -= g_list_length(cdat->dsis_list);
+    if (entry_handle < g_list_length(cdat->dsemts_list)) {
+        const int dsemts_len = 24;
+        struct cxl_cdat_dsemts *dsemts =
+            g_list_nth_data(cdat->dsemts_list, entry_handle);
+
+        doe->store[1] = 3 + dsemts_len / sizeof(uint32_t);
+        doe->store[2] = next_entry << 16;
+        doe->store[3] = (dsemts_len << 16) | CXL_CDAT_DSEMTS_TYPE;
+        doe->store[4] = (dsemts->efi_mem_type_attr << 8) | dsemts->dsmas_handle;
+        doe->store[5] = dsemts->dpa_offset & 0xffffffff;
+        doe->store[6] = (dsemts->dpa_offset >> 32) & 0xffffffff;
+        doe->store[7] = dsemts->dpa_length & 0xffffffff;
+        doe->store[8] = (dsemts->dpa_length >> 32) & 0xffffffff;
+    }
+    entry_handle -= g_list_length(cdat->dsemts_list);
+    if (entry_handle < g_list_length(cdat->sslbis_list)) {
+        struct cxl_cdat_sslbis *sslbis =
+            g_list_nth_data(cdat->sslbis_list, entry_handle);
+        int sslbis_len = 16 + 8 * sslbis->num_entries;
+        int i;
+
+        doe->store[1] = 3 + sslbis_len / sizeof(uint32_t);
+        doe->store[2] = next_entry << 16;
+        doe->store[3] = (sslbis_len << 16) | CXL_CDAT_SSLBIS_TYPE;
+        doe->store[4] = sslbis->data_type;
+        doe->store[5] = sslbis->base_unit & 0xffffffff;
+        doe->store[6] = (sslbis->base_unit >> 32) & 0xffffffff;
+        for (i = 0; i < sslbis->num_entries; i++) {
+            doe->store[7 + i * 2] = (sslbis->entries[i].port_y_id << 8) |
+                sslbis->entries[i].port_x_id;
+            doe->store[7 + i * 2 + 1] = sslbis->entries[i].val;
+        }
+    }
+
+    return -1;
+}
diff --git a/hw/cxl/meson.build b/hw/cxl/meson.build
index 0eca715d10..9e2e5f4094 100644
--- a/hw/cxl/meson.build
+++ b/hw/cxl/meson.build
@@ -2,4 +2,5 @@ softmmu_ss.add(when: 'CONFIG_CXL', if_true: files(
   'cxl-component-utils.c',
   'cxl-device-utils.c',
   'cxl-mailbox-utils.c',
+  'cxl-cdat.c',
 ))
diff --git a/include/hw/cxl/cxl_cdat.h b/include/hw/cxl/cxl_cdat.h
new file mode 100644
index 0000000000..d0226c463c
--- /dev/null
+++ b/include/hw/cxl/cxl_cdat.h
@@ -0,0 +1,101 @@
+/*
+ * Support for CDAT entires as defined in
+ * Coherent Device Attribute Table (CDAT) Specification rev 1.02
+ * Available from uefi.org.
+ *
+ * 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_CXL_CDAT_H
+#define QEMU_CXL_CDAT_H
+#include "hw/pci/doe.h"
+/* DW2 is common to the request and response */
+#define CXL_DOE_TABLE_ACCESS_DW2_RCODE          0x000000ff
+#define CXL_DOE_TABLE_ACCESS_DW2_TYPE           0x0000ff00
+#define CXL_DOE_TABLE_ACCESS_DW2_ENTRYHANDLE    0xffff0000
+
+#define CXL_CDAT_DSMAS_TYPE     0
+#define CXL_CDAT_DSLBIS_TYPE    1
+#define CXL_CDAT_DSMSCIS_TYPE   2
+#define CXL_CDAT_DSIS_TYPE      3
+#define CXL_CDAT_DSEMTS_TYPE    4
+#define CXL_CDAT_SSLBIS_TYPE    5
+
+struct cxl_cdat_dsmas {
+    uint8_t dsmad_handle;
+    uint8_t flags;
+#define CDAT_DSMAS_FLAG_NV (1 << 2)
+    uint64_t dpa_base;
+    uint64_t dpa_length;
+};
+
+struct cxl_cdat_dslbis {
+    uint8_t handle;
+    uint8_t flags;
+    uint8_t data_type;
+    uint64_t entry_base_unit;
+    uint16_t entries[3]; /* 6 bytes */
+};
+
+struct cxl_cdat_dsmscis {
+    uint8_t dsmas_handle;
+    uint64_t memory_side_cache_size;
+    uint32_t cache_attributes;
+};
+
+struct cxl_cdat_dsis {
+    uint8_t flags;
+#define CDAT_DSIS_MEMORY_ATTACHED 0x01
+    uint8_t handle;
+};
+
+struct cxl_cdat_dsemts {
+    uint8_t dsmas_handle;
+    uint8_t efi_mem_type_attr;
+    uint64_t dpa_offset;
+    uint64_t dpa_length;
+};
+
+struct cxl_cdat_sslbis_entry {
+    uint16_t port_x_id;
+    uint16_t port_y_id;
+    uint16_t val;
+};
+
+struct cxl_cdat_sslbis {
+    uint8_t num_entries; /* needed to compute length */
+    uint8_t data_type;
+    uint64_t base_unit;
+    struct cxl_cdat_sslbis_entry entries[];
+};
+
+typedef struct cxl_cdat {
+    GList *dsmas_list;
+    GList *dslbis_list;
+    GList *dsmscis_list;
+    GList *dsis_list;
+    GList *dsemts_list;
+    GList *sslbis_list;
+} CXLCDAT;
+
+void cdat_add_dsmas(CXLCDAT *cdat, uint8_t dsmad_handle, uint8_t flags,
+                    uint64_t dpa_base, uint64_t dpa_length);
+void cdat_add_dslbis(CXLCDAT *cdat, uint8_t handle, uint8_t flags,
+                     uint8_t data_type, uint64_t base_unit,
+                     uint16_t entry0, uint16_t entry1, uint16_t entry2);
+void cdat_add_dsmscis(CXLCDAT *cdat, uint8_t dsmas_handle,
+                      uint64_t memory_sc_size, uint64_t cache_attrs);
+void cdat_add_dsis(CXLCDAT *cdat, uint8_t flags, uint8_t handle);
+void cdat_add_dsemts(CXLCDAT *cdat, uint8_t dsmas_handle,
+                     uint8_t efi_mem_type_attr, uint64_t dpa_offset,
+                     uint64_t dpa_length);
+struct cxl_cdat_sslbis *cdat_add_sslbis(CXLCDAT *cdat, uint8_t num_entries,
+                                        uint8_t data_type, uint64_t base_unit);
+int cdata_sslbis_set_entry(struct cxl_cdat_sslbis *sslbis, uint8_t index,
+                           uint16_t portx, uint16_t pory, uint16_t val);
+
+int cxl_table_access(PCIEDOE *doe, uint16_t vendor_id, uint8_t object_type,
+                     void *priv);
+#endif
