Validate the fixed BAR configuration specified through the pci-bars property on PCI devices and the fixed-bar property on PCIe root ports.
When fixed-bar=on is set on a root port, every device in its hierarchy with a memory BAR must have pci-bars= specified. For any device with pci-bars= specified, validate that every memory BAR has an assigned address, and 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. 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 | 279 ++++++++++++++++++++++++++++++++ hw/pci/pci-fixed-bar-validate.h | 21 +++ 3 files changed, 301 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 a6cbd89c0a..44b94a24b4 100644 --- a/hw/pci/meson.build +++ b/hw/pci/meson.build @@ -17,6 +17,7 @@ pci_ss.add(files( 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..a8cce3d296 --- /dev/null +++ b/hw/pci/pci-fixed-bar-validate.c @@ -0,0 +1,279 @@ +/* + * 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/pcie_port.h" +#include "hw/pci/pci_host.h" +#include "qemu/error-report.h" +#include "qemu/range.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_pci_bars = (pdev->fixed_bar_addrs != NULL); + bool is_64bit; + uint64_t addr, end, wbase, wlim, overlap_start, overlap_end; + PCIIORegion *r; + int overlap_bar; + int i; + + if (is_fixed_subtree && !has_pci_bars) { + error_report("pci-bars: %s [%02x:%02x.%x] under fixed-bar root port " + "has memory BARs but no pci-bars= specified", + devname, pci_dev_bus_num(pdev), + PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn)); + exit(1); + } + + if (!has_pci_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_addrs[i] == PCI_BAR_UNMAPPED) { + error_report("pci-bars: %s [%02x:%02x.%x] BAR%d " + "missing from pci-bars=", + 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_addrs[i]; + + if (r->size - 1 > UINT64_MAX - addr) { + error_report("pci-bars: %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; + + /* + * 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("pci-bars: %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("pci-bars: %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("pci-bars: %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; +} + +/* + * True if bus, or any of its ancestor buses, hangs off a fixed-bar=on + * root port. Walks up via bus->parent_dev / pci_get_bus(). + */ +static bool bus_is_fixed_subtree(PCIBus *bus) +{ + PCIDevice *parent; + + while (bus) { + parent = bus->parent_dev; + if (!parent) { + return false; + } + if (object_dynamic_cast(OBJECT(parent), TYPE_PCIE_ROOT_PORT) && + PCIE_SLOT(parent)->fixed_bar) { + return true; + } + bus = pci_get_bus(parent); + } + return false; +} + +static void scan_bus(PCIBus *bus, void *opaque); + +static void scan_bus_device(PCIBus *bus, PCIDevice *pdev, void *opaque) +{ + FixedBarsInfo *info = 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, info, bus_is_fixed_subtree(bus))) { + 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) { + scan_bus(sec, opaque); + } +} + +static void scan_bus(PCIBus *bus, void *opaque) +{ + pci_for_each_device_under_bus(bus, scan_bus_device, opaque); +} + +static void scan_all_host_bridges(FixedBarsInfo *info) +{ + PCIHostState *hb; + + fixed_claims_init(); + QLIST_FOREACH(hb, &pci_host_bridges, next) { + if (hb->bus) { + scan_bus(hb->bus, info); + } + } + 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
