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
