On 10/27/2026 10:41 AM, Shameer Kolothum Thodi wrote:
> 
> 
>> -----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?

Sure.

> 
>> +
>> +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?

We shouldn't have to. I will amend this in V2.
> 
>> +            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?

Yes.

Thanks.
-Tushar

> 
> Thanks,
> Shameer


Reply via email to