From: Manish Honap <[email protected]>

QEMU has no firmware that commits the HDM decoders of a CXL route at
boot, so a guest finds no committed region.

Let a cold-plugged device register one range with its pxb-cxl, which
then commits HDM decoder 0 for it at every reset. A host bridge with a
registered range keeps its decoder instead of passing accesses through.
If the device resets first, pxb-cxl retries the direct mapping after
the commit.

The CXL Type-2 device registers a range later in this series.

AI-used-for: code (prototype)
Signed-off-by: Manish Honap <[email protected]>
---
 hw/cxl/cxl-component-utils.c            | 47 +++++++++++++++++++++++++
 hw/pci-bridge/pci_expander_bridge_cxl.c | 22 ++++++++++--
 include/hw/cxl/cxl_component.h          | 13 +++++++
 include/hw/pci/pci_bridge.h             |  5 +++
 4 files changed, 85 insertions(+), 2 deletions(-)

diff --git a/hw/cxl/cxl-component-utils.c b/hw/cxl/cxl-component-utils.c
index f9bb1684fa..c61966b202 100644
--- a/hw/cxl/cxl-component-utils.c
+++ b/hw/cxl/cxl-component-utils.c
@@ -9,6 +9,7 @@
 
 #include "qemu/osdep.h"
 #include "qemu/log.h"
+#include "qemu/units.h"
 #include "qapi/error.h"
 #include "hw/pci/pci.h"
 #include "hw/cxl/cxl.h"
@@ -500,6 +501,52 @@ void cxl_component_register_init_common(uint32_t 
*reg_state,
                                       CXL_HDM_DECODER_COUNT);
 }
 
+/* One range per host bridge; registering the same range again succeeds */
+bool cxl_firmware_hdm_set(CXLFirmwareHDM *fw, const char *owner,
+                          uint64_t base, uint64_t size, uint8_t target,
+                          Error **errp)
+{
+    if (!size || !QEMU_IS_ALIGNED(base, 256 * MiB) ||
+        !QEMU_IS_ALIGNED(size, 256 * MiB)) {
+        error_setg(errp, "firmware HDM range must be 256 MiB aligned");
+        return false;
+    }
+
+    if (fw->valid) {
+        if (fw->base == base && fw->size == size && fw->target == target) {
+            return true;
+        }
+        error_setg(errp, "%s supports one firmware HDM range", owner);
+        return false;
+    }
+
+    *fw = (CXLFirmwareHDM) {
+        .valid = true,
+        .base = base,
+        .size = size,
+        .target = target,
+    };
+    return true;
+}
+
+/*
+ * The caller sets HDM Decoder Enable where it applies. It reads as 0 on host
+ * bridges (CXL r4.0 Section 8.2.4.20.2).
+ */
+void cxl_firmware_hdm_program(const CXLFirmwareHDM *fw, uint32_t *cache_mem)
+{
+    uint32_t ctrl = 0;
+
+    stl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_LO, fw->base & 0xf0000000);
+    stl_le_p(cache_mem + R_CXL_HDM_DECODER0_BASE_HI, fw->base >> 32);
+    stl_le_p(cache_mem + R_CXL_HDM_DECODER0_SIZE_LO, fw->size & 0xf0000000);
+    stl_le_p(cache_mem + R_CXL_HDM_DECODER0_SIZE_HI, fw->size >> 32);
+    stl_le_p(cache_mem + R_CXL_HDM_DECODER0_TARGET_LIST_LO, fw->target);
+    ctrl = FIELD_DP32(ctrl, CXL_HDM_DECODER0_CTRL, COMMIT, 1);
+    ctrl = FIELD_DP32(ctrl, CXL_HDM_DECODER0_CTRL, COMMITTED, 1);
+    stl_le_p(cache_mem + R_CXL_HDM_DECODER0_CTRL, ctrl);
+}
+
 /*
  * Helper to creates a DVSEC header for a CXL entity. The caller is responsible
  * for tracking the valid offset.
diff --git a/hw/pci-bridge/pci_expander_bridge_cxl.c 
b/hw/pci-bridge/pci_expander_bridge_cxl.c
index ceb8928f8f..01fd479a9e 100644
--- a/hw/pci-bridge/pci_expander_bridge_cxl.c
+++ b/hw/pci-bridge/pci_expander_bridge_cxl.c
@@ -111,9 +111,18 @@ static const TypeInfo cxl_host_info = {
     .class_init    = pxb_cxl_host_class_init,
 };
 
+bool pxb_cxl_register_firmware_hdm(PXBCXLDev *pxb, uint64_t base,
+                                   uint64_t size, uint8_t target,
+                                   Error **errp)
+{
+    return cxl_firmware_hdm_set(&pxb->firmware_hdm, "pxb-cxl", base, size,
+                                target, errp);
+}
+
 static void pxb_cxl_dev_reset(DeviceState *dev)
 {
-    CXLHost *cxl = PXB_CXL_DEV(dev)->cxl_host_bridge;
+    PXBCXLDev *pxb = PXB_CXL_DEV(dev);
+    CXLHost *cxl = pxb->cxl_host_bridge;
     CXLComponentState *cxl_cstate = &cxl->cxl_cstate;
     PCIHostState *hb = PCI_HOST_BRIDGE(cxl);
     uint32_t *reg_state = cxl_cstate->crb.cache_mem_registers;
@@ -121,11 +130,12 @@ static void pxb_cxl_dev_reset(DeviceState *dev)
     int dsp_count = 0;
 
     cxl_component_register_init_common(reg_state, write_msk, CXL2_RC, false);
+    cxl->passthrough = false;
     /*
      * The CXL specification allows for host bridges with no HDM decoders
      * if they only have a single root port.
      */
-    if (!PXB_CXL_DEV(dev)->hdm_for_passthrough) {
+    if (!pxb->hdm_for_passthrough && !pxb->firmware_hdm.valid) {
         dsp_count = pcie_count_ds_ports(hb->bus);
     }
     /* Initial reset will have 0 dsp so wait until > 0 */
@@ -136,6 +146,14 @@ static void pxb_cxl_dev_reset(DeviceState *dev)
     } else {
         ARRAY_FIELD_DP32(reg_state, CXL_HDM_DECODER_CAPABILITY, TARGET_COUNT,
                          8);
+        if (pxb->firmware_hdm.valid) {
+            cxl_firmware_hdm_program(&pxb->firmware_hdm, reg_state);
+            /*
+             * An endpoint that committed before this reset could not map
+             * its memory directly, as this decoder was not committed yet.
+             */
+            cfmws_update_non_interleaved(true);
+        }
     }
 }
 
diff --git a/include/hw/cxl/cxl_component.h b/include/hw/cxl/cxl_component.h
index b38e81d129..eb71f1c80a 100644
--- a/include/hw/cxl/cxl_component.h
+++ b/include/hw/cxl/cxl_component.h
@@ -328,6 +328,19 @@ void cxl_component_create_dvsec(CXLComponentState 
*cxl_cstate,
 void cxl_component_update_dvsec(CXLComponentState *cxl, uint16_t length,
                                 uint16_t type, uint8_t *body);
 
+/* The range that firmware routes through HDM decoder 0 to one port */
+typedef struct CXLFirmwareHDM {
+    bool valid;
+    uint64_t base;
+    uint64_t size;
+    uint8_t target;
+} CXLFirmwareHDM;
+
+bool cxl_firmware_hdm_set(CXLFirmwareHDM *fw, const char *owner,
+                          uint64_t base, uint64_t size, uint8_t target,
+                          Error **errp);
+void cxl_firmware_hdm_program(const CXLFirmwareHDM *fw, uint32_t *cache_mem);
+
 int cxl_decoder_count_enc(int count);
 int cxl_decoder_count_dec(int enc_cnt);
 
diff --git a/include/hw/pci/pci_bridge.h b/include/hw/pci/pci_bridge.h
index f114f0a651..d0227a4c38 100644
--- a/include/hw/pci/pci_bridge.h
+++ b/include/hw/pci/pci_bridge.h
@@ -117,11 +117,16 @@ typedef struct PXBCXLDev {
 
     bool hdm_for_passthrough;
     CXLHost *cxl_host_bridge; /* Pointer to a CXLHost */
+    CXLFirmwareHDM firmware_hdm;
 } PXBCXLDev;
 
 #define TYPE_PXB_CXL_DEV "pxb-cxl"
 OBJECT_DECLARE_SIMPLE_TYPE(PXBCXLDev, PXB_CXL_DEV)
 
+bool pxb_cxl_register_firmware_hdm(PXBCXLDev *pxb, uint64_t base,
+                                   uint64_t size, uint8_t target,
+                                   Error **errp);
+
 int pci_bridge_ssvid_init(PCIDevice *dev, uint8_t offset,
                           uint16_t svid, uint16_t ssid,
                           Error **errp);
-- 
2.53.0


Reply via email to