> -----Original Message-----
> From: Tushar Dave <[email protected]>
> Sent: 16 September 2026 19:56
> To: [email protected]
> Cc: [email protected]; [email protected]; [email protected];
> [email protected]; [email protected]; Alex Williamson
> <[email protected]>; Jason Gunthorpe <[email protected]>; Shameer
> Kolothum Thodi <[email protected]>
> Subject: [PATCH 2/5] pci: add validation for fixed BAR configuration
> 
> 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;

Can this live in the FixedBarsInfo instead? IIUC, the info context is
already passed through the walk, and the claims only need to exist
for that validation pass?

> +
> +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) {

This will reject a 64-bit BAR whose requested address falls within
the low MMIO aperture. That is a valid placement, right?

Is restricting 64-bit BARs to the high aperture intentional?

> +            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);

May be better if we could pass an Error **errp through the helpers instead
of calling exit(1) here and let the machine code handle the error?

Thanks,
Shameer

Reply via email to