Validate the fixed BAR configuration specified through the
fixed-bar-N properties on PCI endpoint devices.

Configuring any fixed-bar-N property on an endpoint device requires
every memory BAR on that device to have a fixed address. Validate
that each assigned address:

- is aligned to its BAR size;
- is within the appropriate PCIe MMIO aperture; and
- does not overlap any other fixed BAR.

Under a PCIe topology with a root port, fixed BAR placement is
all-or-nothing within that root port's subtree. If any one endpoint
device below the root port has a fixed BAR, every other endpoint
device below that same root port with a memory BAR must have all of
its memory BARs fixed too.

Endpoint devices with no root port in their ancestry, such as those
attached directly to the host bridge, are not required to match
their sibling devices. One such device may use fixed-bar-N for all
of its memory BARs while another sibling device on the same host
bridge uses none at all. Per-device completeness still applies
regardless of ancestry. Once any fixed-bar-N property is set on a
device, every memory BAR on that device must be fixed.

Abort QEMU if any validation fails.

Signed-off-by: Tushar Dave <[email protected]>
---
 hw/pci/meson.build              |   1 +
 hw/pci/pci-fixed-bar-validate.c | 326 ++++++++++++++++++++++++++++++++
 hw/pci/pci-fixed-bar-validate.h |  21 ++
 3 files changed, 348 insertions(+)
 create mode 100644 hw/pci/pci-fixed-bar-validate.c
 create mode 100644 hw/pci/pci-fixed-bar-validate.h

diff --git a/hw/pci/meson.build b/hw/pci/meson.build
index 984c7327e5..150085c997 100644
--- a/hw/pci/meson.build
+++ b/hw/pci/meson.build
@@ -19,6 +19,7 @@ endif
 pci_ss.add(files('pcie.c', 'pcie_aer.c'))
 pci_ss.add(files('pcie_doe.c'))
 system_ss.add(when: 'CONFIG_PCI_EXPRESS', if_true: files('pcie_port.c', 
'pcie_host.c'))
+system_ss.add(when: 'CONFIG_PCI_EXPRESS', if_true: 
files('pci-fixed-bar-validate.c'))
 system_ss.add_all(when: 'CONFIG_PCI', if_true: pci_ss)
 
 stub_ss.add(files('pci-stub.c'))
diff --git a/hw/pci/pci-fixed-bar-validate.c b/hw/pci/pci-fixed-bar-validate.c
new file mode 100644
index 0000000000..0b5baad8a0
--- /dev/null
+++ b/hw/pci/pci-fixed-bar-validate.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved
+ * BAR address validation for fixed-BAR placement.
+ *
+ * Written by Tushar Dave
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bridge.h"
+#include "hw/pci/pci_host.h"
+#include "hw/pci/pcie_port.h"
+#include "qemu/error-report.h"
+#include "qemu/range.h"
+#include "qemu/units.h"
+#include "pci-internal.h"
+#include "pci-fixed-bar-validate.h"
+
+/*
+ * Claimed BAR ranges — detect inter-device and inter-hierarchy overlaps
+ * across all fixed BARs system-wide.
+ */
+
+typedef struct {
+    uint64_t    start;
+    uint64_t    end;
+    const char *owner;  /* device name, for error messages */
+    int         bar;
+} FixedClaim;
+
+static GArray *fixed_claims;
+
+static void fixed_claims_init(void)
+{
+    if (fixed_claims) {
+        g_array_free(fixed_claims, true);
+    }
+    fixed_claims = g_array_new(false, true, sizeof(FixedClaim));
+}
+
+static void fixed_claims_free(void)
+{
+    g_array_free(fixed_claims, true);
+    fixed_claims = NULL;
+}
+
+static bool fixed_claims_overlap(uint64_t start, uint64_t end,
+                                 const char **owner_out, int *bar_out,
+                                 uint64_t *start_out, uint64_t *end_out)
+{
+    FixedClaim *c;
+    guint i;
+
+    for (i = 0; i < fixed_claims->len; i++) {
+        c = &g_array_index(fixed_claims, FixedClaim, i);
+        if (ranges_overlap(start, end - start + 1,
+                           c->start, c->end - c->start + 1)) {
+            *owner_out = c->owner;
+            *bar_out   = c->bar;
+            *start_out = c->start;
+            *end_out   = c->end;
+            return true;
+        }
+    }
+    return false;
+}
+
+static void fixed_claims_add(uint64_t start, uint64_t end,
+                             const char *owner, int bar)
+{
+    FixedClaim cl;
+
+    cl.start = start;
+    cl.end   = end;
+    cl.owner = owner;
+    cl.bar   = bar;
+    g_array_append_val(fixed_claims, cl);
+}
+
+static bool validate_bars(PCIDevice *pdev, FixedBarsInfo *info,
+                          bool is_fixed_subtree)
+{
+    const char  *devname = DEVICE(pdev)->id ? DEVICE(pdev)->id : pdev->name;
+    const char  *overlap_owner;
+    const char  *wname;
+    bool         has_fixed_bars = false;
+    bool         is_64bit;
+    uint64_t     addr, end, wbase, wlim, overlap_start, overlap_end;
+    PCIIORegion *r;
+    int          overlap_bar;
+    int          i;
+
+    for (i = 0; i < PCI_NUM_REGIONS - 1; i++) {
+        if (pdev->fixed_bar_addr[i] != PCI_BAR_UNMAPPED) {
+            has_fixed_bars = true;
+            break;
+        }
+    }
+
+    if (is_fixed_subtree && !has_fixed_bars) {
+        error_report("fixed-bar: %s [%02x:%02x.%x] under fixed-bar root "
+                     "port has memory BARs but no fixed-bar-N= specified",
+                     devname, pci_dev_bus_num(pdev),
+                     PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn));
+        exit(1);
+    }
+
+    if (!has_fixed_bars) {
+        return false;
+    }
+
+    /* Completeness: every memory BAR must have an address. */
+    for (i = 0; i < PCI_NUM_REGIONS - 1; i++) {
+        r = &pdev->io_regions[i];
+        if (!r->size || (r->type & PCI_BASE_ADDRESS_SPACE_IO)) {
+            continue;
+        }
+        if (pdev->fixed_bar_addr[i] == PCI_BAR_UNMAPPED) {
+            error_report("fixed-bar: %s [%02x:%02x.%x] BAR%d "
+                         "missing a fixed-bar-N= value",
+                         devname, pci_dev_bus_num(pdev),
+                         PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), i);
+            exit(1);
+        }
+    }
+
+    /* Per-BAR checks: alignment, MMIO window, overlap. */
+    for (i = 0; i < PCI_NUM_REGIONS - 1; i++) {
+        r = &pdev->io_regions[i];
+        if (!r->size || (r->type & PCI_BASE_ADDRESS_SPACE_IO)) {
+            continue;
+        }
+
+        is_64bit = !!(r->type & PCI_BASE_ADDRESS_MEM_TYPE_64);
+        addr     = (uint64_t)pdev->fixed_bar_addr[i];
+
+        if (r->size - 1 > UINT64_MAX - addr) {
+            error_report("fixed-bar: %s [%02x:%02x.%x] BAR%d "
+                         "addr=0x%"PRIx64" + size=0x%"PRIx64" overflows",
+                         devname, pci_dev_bus_num(pdev),
+                         PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn),
+                         i, addr, r->size);
+            exit(1);
+        }
+        end = addr + r->size - 1;
+
+        if (is_64bit && !(r->type & PCI_BASE_ADDRESS_MEM_PREFETCH) &&
+            !pci_bus_is_root(pci_get_bus(pdev)) && addr >= 4 * GiB) {
+            error_report("fixed-bar: %s [%02x:%02x.%x] BAR%d is a "
+                         "non-prefetchable 64-bit BAR behind a PCI bridge "
+                         "and cannot be placed above 4GB",
+                         devname, pci_dev_bus_num(pdev),
+                         PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), i);
+            exit(1);
+        }
+
+        /*
+         * wbase/wlim are both inclusive bounds (mmio*_limit = base+size-1,
+         * set by the caller alongside mmio*_base), matching end's own
+         * inclusive computation above.
+         */
+        if (is_64bit) {
+            wbase = info->mmio64_base;
+            wlim  = info->mmio64_limit;
+            wname = "64-bit MMIO";
+        } else {
+            wbase = info->mmio32_base;
+            wlim  = info->mmio32_limit;
+            wname = "32-bit MMIO";
+        }
+
+        if (addr & (r->size - 1)) {
+            error_report("fixed-bar: %s [%02x:%02x.%x] BAR%d "
+                         "addr=0x%"PRIx64" not aligned to size=0x%"PRIx64,
+                         devname, pci_dev_bus_num(pdev),
+                         PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn),
+                         i, addr, r->size);
+            exit(1);
+        }
+
+        if (addr < wbase || end > wlim) {
+            error_report("fixed-bar: %s [%02x:%02x.%x] BAR%d "
+                         "[0x%"PRIx64"..0x%"PRIx64"] outside %s window "
+                         "[0x%"PRIx64"..0x%"PRIx64"]",
+                         devname, pci_dev_bus_num(pdev),
+                         PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn),
+                         i, addr, end, wname, wbase, wlim);
+            exit(1);
+        }
+
+        if (fixed_claims_overlap(addr, end, &overlap_owner, &overlap_bar,
+                                 &overlap_start, &overlap_end)) {
+            error_report("fixed-bar: %s [%02x:%02x.%x] BAR%d "
+                         "[0x%"PRIx64"..0x%"PRIx64"] overlaps %s BAR%d "
+                         "[0x%"PRIx64"..0x%"PRIx64"]",
+                         devname, pci_dev_bus_num(pdev),
+                         PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn),
+                         i, addr, end, overlap_owner, overlap_bar,
+                         overlap_start, overlap_end);
+            exit(1);
+        }
+
+        fixed_claims_add(addr, end, devname, i);
+    }
+
+    return true;
+}
+
+typedef struct {
+    FixedBarsInfo *info;
+    bool           is_fixed_subtree;
+} ScanState;
+
+static void scan_bus(PCIBus *bus, ScanState *state);
+
+static void mark_if_fixed(PCIBus *bus, PCIDevice *pdev, void *opaque)
+{
+    bool *found = opaque;
+    PCIBus *sec;
+    int i;
+
+    if (*found) {
+        return;
+    }
+    for (i = 0; i < PCI_NUM_REGIONS - 1; i++) {
+        if (pdev->fixed_bar_addr[i] != PCI_BAR_UNMAPPED) {
+            *found = true;
+            return;
+        }
+    }
+    if (object_dynamic_cast(OBJECT(pdev), TYPE_PCI_BRIDGE)) {
+        sec = pci_bridge_get_sec_bus(PCI_BRIDGE(pdev));
+        if (sec) {
+            pci_for_each_device_under_bus(sec, mark_if_fixed, found);
+        }
+    }
+}
+
+/*
+ * True if any device in rp_bus's subtree (the bus behind a root port)
+ * has a fixed BAR.
+ */
+static bool root_port_subtree_has_fixed_bar(PCIBus *rp_bus)
+{
+    bool found = false;
+
+    pci_for_each_device_under_bus(rp_bus, mark_if_fixed, &found);
+    return found;
+}
+
+static void scan_bus_device(PCIBus *bus, PCIDevice *pdev, void *opaque)
+{
+    ScanState   *state = opaque;
+    bool         has_mem_bar = false;
+    PCIIORegion *r;
+    PCIBus      *sec;
+    int          i;
+
+    for (i = 0; i < PCI_NUM_REGIONS - 1; i++) {
+        r = &pdev->io_regions[i];
+        if (r->size && !(r->type & PCI_BASE_ADDRESS_SPACE_IO)) {
+            has_mem_bar = true;
+            break;
+        }
+    }
+
+    if (has_mem_bar) {
+        if (validate_bars(pdev, state->info, state->is_fixed_subtree)) {
+            state->info->any_fixed = true;
+        }
+    }
+
+    if (!object_dynamic_cast(OBJECT(pdev), TYPE_PCI_BRIDGE)) {
+        return;
+    }
+    sec = pci_bridge_get_sec_bus(PCI_BRIDGE(pdev));
+    if (!sec) {
+        return;
+    }
+
+    if (object_dynamic_cast(OBJECT(pdev), TYPE_PCIE_ROOT_PORT)) {
+        ScanState child = {
+            .info = state->info,
+            .is_fixed_subtree = root_port_subtree_has_fixed_bar(sec),
+        };
+        scan_bus(sec, &child);
+    } else {
+        scan_bus(sec, state);
+    }
+}
+
+static void scan_bus(PCIBus *bus, ScanState *state)
+{
+    pci_for_each_device_under_bus(bus, scan_bus_device, state);
+}
+
+static void scan_all_host_bridges(FixedBarsInfo *info)
+{
+    PCIHostState *hb;
+    ScanState state = { .info = info, .is_fixed_subtree = false };
+
+    fixed_claims_init();
+    QLIST_FOREACH(hb, &pci_host_bridges, next) {
+        if (hb->bus) {
+            scan_bus(hb->bus, &state);
+        }
+    }
+    fixed_claims_free();
+}
+
+/*
+ * fixed_bars_validate - scan all PCI devices, validate fixed BAR addresses.
+ *
+ * @info: carries MMIO window bounds for validation.
+ *
+ * Returns true if any fixed BAR devices were found, false if there is
+ * nothing to do. Aborts on any validation error.
+ */
+bool fixed_bars_validate(FixedBarsInfo *info)
+{
+    info->any_fixed = false;
+    scan_all_host_bridges(info);
+    return info->any_fixed;
+}
diff --git a/hw/pci/pci-fixed-bar-validate.h b/hw/pci/pci-fixed-bar-validate.h
new file mode 100644
index 0000000000..297d1480c0
--- /dev/null
+++ b/hw/pci/pci-fixed-bar-validate.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved
+ *
+ * Written by Tushar Dave
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#ifndef HW_PCI_FIXED_BAR_VALIDATE_H
+#define HW_PCI_FIXED_BAR_VALIDATE_H
+
+typedef struct {
+    bool      any_fixed;
+    uint64_t  mmio32_base;
+    uint64_t  mmio32_limit;
+    uint64_t  mmio64_base;
+    uint64_t  mmio64_limit;
+} FixedBarsInfo;
+
+bool fixed_bars_validate(FixedBarsInfo *info);
+
+#endif /* HW_PCI_FIXED_BAR_VALIDATE_H */
-- 
2.34.1


Reply via email to