Introduce a fixed BAR fw_cfg blob exporter that serializes PCI device and BAR information into the "etc/fixed-bars" fw_cfg file.
The blob contains an entry for every PCI function with memory BARs. Each entry carries the device's vendor and device ID, whether it has a fixed BAR configuration, and its firmware device path. Fixed BAR entries also include the configured BAR address, size, and type information for each BAR. Document the format in docs/specs/pci-fixed-bars.rst. Signed-off-by: Tushar Dave <[email protected]> --- docs/specs/index.rst | 1 + docs/specs/pci-fixed-bars.rst | 75 ++++++++ hw/pci/meson.build | 1 + hw/pci/pci-fixed-bar-blob.c | 308 +++++++++++++++++++++++++++++++++ include/hw/pci/pci-fixed-bar.h | 63 +++++++ 5 files changed, 448 insertions(+) create mode 100644 docs/specs/pci-fixed-bars.rst create mode 100644 hw/pci/pci-fixed-bar-blob.c create mode 100644 include/hw/pci/pci-fixed-bar.h diff --git a/docs/specs/index.rst b/docs/specs/index.rst index 44e108db02..15cdf4698b 100644 --- a/docs/specs/index.rst +++ b/docs/specs/index.rst @@ -11,6 +11,7 @@ guest hardware that is specific to QEMU. pci-ids pci-serial pci-testdev + pci-fixed-bars ppc-xive ppc-spapr-xive ppc-spapr-numa diff --git a/docs/specs/pci-fixed-bars.rst b/docs/specs/pci-fixed-bars.rst new file mode 100644 index 0000000000..708566550e --- /dev/null +++ b/docs/specs/pci-fixed-bars.rst @@ -0,0 +1,75 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +.. + Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved + +PCI fixed BAR fw_cfg +==================== + +A PCI device can be given a fixed guest address for each of its memory BARs +with the ``fixed-bar-N`` device properties, where ``N`` is the BAR number, +for example ``-device e1000,fixed-bar-0=0x20000000``. A machine that +supports this validates the addresses and passes them to the firmware in the +``etc/fixed-bars`` fw_cfg file, so that the firmware can place each BAR at +the requested address instead of assigning one. + +The file is present only when at least one device has a fixed BAR. All +fields are little-endian and the structures are packed. + +Layout +------ + +The file is a header followed by one device record for every PCI function +that has a memory BAR, fixed or not. A device record is followed by one BAR +record for each of its fixed BARs. A device with any fixed BAR has all of its +memory BARs fixed, so it has a BAR record for each of them. + +The device records are in the order in which the firmware discovers the +devices. Host bridges come in ascending bus number order, the devices of a +bus in devfn order, and the devices behind a bridge follow the bridge. + +Header +~~~~~~ + +====== ======= ==================================================== +Offset Size Description +====== ======= ==================================================== +0 4 ``version``, currently 1 +4 4 ``num_devices``, the number of device records +====== ======= ==================================================== + +Device record +~~~~~~~~~~~~~ + +====== ======= ==================================================== +Offset Size Description +====== ======= ==================================================== +0 2 ``vendor_id`` +2 2 ``device_id`` +4 1 ``dev_flags``, bit 0 is set if the device has fixed BARs +5 1 ``rp_bus``, the primary bus number of the root port the + device is under, or the bus number of its host bridge + if it is not under a root port +6 1 ``num_bars``, the number of BAR records that follow, + 0 if the device has no fixed BARs +7 1 reserved, 0 +8 128 ``fw_path``, the firmware device path of the device, + NUL-terminated +====== ======= ==================================================== + +BAR record +~~~~~~~~~~ + +====== ======= ==================================================== +Offset Size Description +====== ======= ==================================================== +0 1 ``bar``, the BAR number +1 3 reserved, 0 +4 4 ``flags``, bit 0 indicates the BAR type (0 for 32-bit, + 1 for 64-bit), and bit 1 indicates whether the BAR is + prefetchable (0 for non-prefetchable, 1 for + prefetchable). All other bits are reserved and must + be zero. +8 8 ``address``, the fixed guest address of the BAR +16 8 ``size``, the size of the BAR +====== ======= ==================================================== diff --git a/hw/pci/meson.build b/hw/pci/meson.build index 150085c997..b81cc056c0 100644 --- a/hw/pci/meson.build +++ b/hw/pci/meson.build @@ -20,6 +20,7 @@ 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(when: 'CONFIG_PCI_EXPRESS', if_true: files('pci-fixed-bar-blob.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-blob.c b/hw/pci/pci-fixed-bar-blob.c new file mode 100644 index 0000000000..dc94debfad --- /dev/null +++ b/hw/pci/pci-fixed-bar-blob.c @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved + * Write validated fixed-BAR device info to the etc/fixed-bars fw_cfg blob. + * + * Written by Tushar Dave + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/nvram/fw_cfg.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 "hw/core/qdev.h" +#include "qemu/error-report.h" +#include "pci-internal.h" +#include "hw/pci/pci-fixed-bar.h" +#include "pci-fixed-bar-validate.h" + +typedef struct { + uint8_t bar; + uint32_t flags; + uint64_t addr; + uint64_t size; +} BarInfo; + +/* One PCI function entry to be written to the blob. */ +typedef struct { + uint8_t rp_bus; /* primary bus of the root port this device is under */ + uint16_t vendor_id; + uint16_t device_id; + bool is_fixed; + char fw_path[QEMU_FIXED_BARS_MAX_PATH]; + GArray *bars; /* array of BarInfo */ +} DeviceInfo; + +typedef struct { + GArray *devices; + PCIBus *root_bus; +} CollectCtx; + +static void collect_device(PCIBus *bus, PCIDevice *pdev, void *opaque); + +/* Visit every device on this bus, in devfn order. */ +static void collect_bus(PCIBus *bus, void *opaque) +{ + pci_for_each_device_under_bus(bus, collect_device, opaque); +} + +/* + * Primary bus number of the nearest TYPE_PCIE_ROOT_PORT ancestor of bus + * — i.e. the bus the root port device itself lives on, not its + * secondary bus. Falls back to the host bridge's own bus number if bus + * isn't under any root port (e.g. a device directly on the host + * bridge's primary bus). + */ +static uint8_t find_root_port_bus(PCIBus *bus, PCIBus *host_bus) +{ + PCIDevice *parent; + + while (bus) { + parent = bus->parent_dev; + if (!parent) { + break; + } + if (object_dynamic_cast(OBJECT(parent), TYPE_PCIE_ROOT_PORT)) { + return pci_bus_num(pci_get_bus(parent)); + } + bus = pci_get_bus(parent); + } + return pci_bus_num(host_bus); +} + +static void collect_device(PCIBus *bus, PCIDevice *pdev, void *opaque) +{ + CollectCtx *ctx = opaque; + bool has_mem_bar = false; + bool is_64bit, is_pref; + DeviceInfo dev; + PCIIORegion *r; + PCIBus *sec; + BarInfo bi; + 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; + } + } + + /* + * Every device with a memory BAR gets an entry, fixed or not — not + * just ones with fixed-bar-N= set. This preserves the exact device + * sequence CheckDevice() needs for positional VID:DID matching (see + * collect_all_host_bridges()); only fixed devices carry BAR records + * (dev.bars stays empty, dev.is_fixed false, otherwise). + */ + if (has_mem_bar) { + g_autofree char *fw_path = qdev_get_fw_dev_path(DEVICE(pdev)); + + memset(&dev, 0, sizeof(dev)); + dev.rp_bus = find_root_port_bus(bus, ctx->root_bus); + dev.vendor_id = pci_get_word(pdev->config + PCI_VENDOR_ID); + dev.device_id = pci_get_word(pdev->config + PCI_DEVICE_ID); + dev.is_fixed = false; + for (i = 0; i < PCI_NUM_REGIONS - 1; i++) { + if (pdev->fixed_bar_addr[i] != PCI_BAR_UNMAPPED) { + dev.is_fixed = true; + break; + } + } + if (fw_path) { + g_strlcpy(dev.fw_path, fw_path, sizeof(dev.fw_path)); + } + dev.bars = g_array_new(false, true, sizeof(BarInfo)); + + if (dev.is_fixed) { + 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); + is_pref = !!(r->type & PCI_BASE_ADDRESS_MEM_PREFETCH); + + memset(&bi, 0, sizeof(bi)); + bi.bar = i; + bi.addr = (uint64_t)pdev->fixed_bar_addr[i]; + bi.size = r->size; + bi.flags = (is_64bit ? QEMU_FIXED_BAR_F_MEM64 : 0) | + (is_pref ? QEMU_FIXED_BAR_F_PREF : 0); + g_array_append_val(dev.bars, bi); + } + } + g_array_append_val(ctx->devices, dev); + } + + if (!object_dynamic_cast(OBJECT(pdev), TYPE_PCI_BRIDGE)) { + return; + } + sec = pci_bridge_get_sec_bus(PCI_BRIDGE(pdev)); + if (sec) { + collect_bus(sec, opaque); + } +} + +static gint cmp_host_bus_num(gconstpointer a, gconstpointer b) +{ + PCIHostState *ha = *(PCIHostState **)a; + PCIHostState *hb = *(PCIHostState **)b; + return (gint)pci_bus_num(ha->bus) - (gint)pci_bus_num(hb->bus); +} + +/* + * EFI_INCOMPATIBLE_PCI_DEVICE_SUPPORT_PROTOCOL's CheckDevice interface + * (UEFI PI Spec 1.2 Vol 5) only passes VendorId/DeviceId/RevisionId/ + * SubsystemVendorId/SubsystemDeviceId, no BDF — so in EDK2 we can't + * match a blob entry to a specific device directly using BDF. To work + * around that, we prepare the blob entries in the same order PciBusDxe + * discovers devices, so matching by VID:DID inherently works. Each entry + * also carries the firmware device path. The CheckDevice interface cannot + * use it today, but it could if the interface is extended. + */ +static void collect_all_host_bridges(CollectCtx *ctx) +{ + PCIHostState *hb; + GPtrArray *sorted; + guint i; + + sorted = g_ptr_array_new(); + QLIST_FOREACH(hb, &pci_host_bridges, next) { + if (hb->bus) { + g_ptr_array_add(sorted, hb); + } + } + g_ptr_array_sort(sorted, cmp_host_bus_num); + + for (i = 0; i < sorted->len; i++) { + hb = g_ptr_array_index(sorted, i); + ctx->root_bus = hb->bus; + collect_bus(hb->bus, ctx); + } + + g_ptr_array_free(sorted, true); +} + +static uint8_t *create_blob(CollectCtx *ctx, size_t *blob_size) +{ + QemuFixedBarsDevice *dout; + QemuFixedBarsBar *bout; + QemuFixedBarsHdr *hdr; + DeviceInfo *dev; + uint8_t *blob; + uint8_t *ptr; + BarInfo *bi; + size_t sz; + guint d, b; + + sz = sizeof(QemuFixedBarsHdr); + for (d = 0; d < ctx->devices->len; d++) { + dev = &g_array_index(ctx->devices, DeviceInfo, d); + sz += sizeof(QemuFixedBarsDevice); + sz += dev->bars->len * sizeof(QemuFixedBarsBar); + } + + blob = g_malloc0(sz); + hdr = (QemuFixedBarsHdr *)blob; + hdr->version = cpu_to_le32(QEMU_FIXED_BARS_VERSION); + hdr->num_devices = cpu_to_le32(ctx->devices->len); + + ptr = blob + sizeof(*hdr); + for (d = 0; d < ctx->devices->len; d++) { + dev = &g_array_index(ctx->devices, DeviceInfo, d); + dout = (QemuFixedBarsDevice *)ptr; + + dout->vendor_id = cpu_to_le16(dev->vendor_id); + dout->device_id = cpu_to_le16(dev->device_id); + dout->dev_flags = dev->is_fixed ? QEMU_FIXED_BARS_DEV_F_FIXED : 0; + dout->rp_bus = dev->rp_bus; + dout->num_bars = dev->bars->len; + dout->reserved = 0; + memcpy(dout->fw_path, dev->fw_path, sizeof(dout->fw_path)); + ptr += sizeof(QemuFixedBarsDevice); + + for (b = 0; b < dev->bars->len; b++) { + bi = &g_array_index(dev->bars, BarInfo, b); + bout = (QemuFixedBarsBar *)ptr; + + bout->bar = bi->bar; + bout->flags = cpu_to_le32(bi->flags); + bout->address = cpu_to_le64(bi->addr); + bout->size = cpu_to_le64(bi->size); + ptr += sizeof(QemuFixedBarsBar); + } + } + + *blob_size = sz; + return blob; +} + +/* + * fixed_bars_write_blob - validate fixed BAR addresses and write fw_cfg blob. + * + * @fw_cfg: fw_cfg state to write the etc/fixed-bars blob into. + * @mmio32_base: base of the machine's 32-bit PCIe MMIO aperture. + * @mmio32_size: size of the machine's 32-bit PCIe MMIO aperture. + * @mmio64_base: base of the machine's 64-bit PCIe MMIO aperture. + * @mmio64_size: size of the machine's 64-bit PCIe MMIO aperture, or 0 + * if the machine has none. + * @errp: set if the apertures or any BAR address fail validation. + * + * Returns true if any fixed BAR device was found and the blob was + * written, false if there was nothing to do or on error. + */ +bool fixed_bars_write_blob(FWCfgState *fw_cfg, + uint64_t mmio32_base, + uint64_t mmio32_size, + uint64_t mmio64_base, + uint64_t mmio64_size, + Error **errp) +{ + FixedBarsInfo info; + CollectCtx ctx; + uint8_t *blob; + size_t blob_size; + guint d; + + if (!mmio32_size) { + error_setg(errp, "fixed-bar: zero-size 32-bit PCIe MMIO aperture"); + return false; + } + if (mmio32_size - 1 > UINT64_MAX - mmio32_base || + (mmio64_size && mmio64_size - 1 > UINT64_MAX - mmio64_base)) { + error_setg(errp, "fixed-bar: PCIe MMIO aperture base+size overflows " + "(32-bit base=0x%"PRIx64" size=0x%"PRIx64", " + "64-bit base=0x%"PRIx64" size=0x%"PRIx64")", + mmio32_base, mmio32_size, mmio64_base, mmio64_size); + return false; + } + + info.mmio32_base = mmio32_base; + info.mmio32_limit = mmio32_base + mmio32_size - 1; + /* An absent 64-bit aperture is an empty window, no BAR fits in it. */ + info.mmio64_base = mmio64_size ? mmio64_base : 1; + info.mmio64_limit = mmio64_size ? mmio64_base + mmio64_size - 1 : 0; + + if (!fixed_bars_validate(&info, errp) || !info.any_fixed) { + return false; + } + + ctx.devices = g_array_new(false, true, sizeof(DeviceInfo)); + ctx.root_bus = NULL; + collect_all_host_bridges(&ctx); + + blob = create_blob(&ctx, &blob_size); + fw_cfg_add_file(fw_cfg, FW_CFG_FIXED_BARS, blob, blob_size); + + for (d = 0; d < ctx.devices->len; d++) { + g_array_free(g_array_index(ctx.devices, DeviceInfo, d).bars, true); + } + g_array_free(ctx.devices, true); + + return true; +} diff --git a/include/hw/pci/pci-fixed-bar.h b/include/hw/pci/pci-fixed-bar.h new file mode 100644 index 0000000000..2c5af536dc --- /dev/null +++ b/include/hw/pci/pci-fixed-bar.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved + * Fixed-BAR — QEMU side declarations. + * + * Written by Tushar Dave + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#ifndef HW_PCI_FIXED_BAR_H +#define HW_PCI_FIXED_BAR_H + +#include "hw/nvram/fw_cfg.h" + +#define FW_CFG_FIXED_BARS "etc/fixed-bars" +#define QEMU_FIXED_BARS_VERSION 1 +#define QEMU_FIXED_BARS_MAX_PATH 128 + +/* + * On-wire layout (little-endian, packed): + * + * QemuFixedBarsHdr + * + * For each device (num_devices total): + * QemuFixedBarsDevice -- device header + * QemuFixedBarsBar[num_bars] -- one record per BAR (FIXED only) + */ + +typedef struct { + uint32_t version; /* QEMU_FIXED_BARS_VERSION */ + uint32_t num_devices; +} QEMU_PACKED QemuFixedBarsHdr; /* 8 bytes */ + +typedef struct { + uint16_t vendor_id; + uint16_t device_id; + uint8_t dev_flags; /* QEMU_FIXED_BARS_DEV_F_* */ + uint8_t rp_bus; /* primary bus of the root port this device is under */ + uint8_t num_bars; + uint8_t reserved; + /* qdev_get_fw_dev_path(), NUL-terminated */ + char fw_path[QEMU_FIXED_BARS_MAX_PATH]; +} QEMU_PACKED QemuFixedBarsDevice; /* 8 + QEMU_FIXED_BARS_MAX_PATH bytes */ + +typedef struct { + uint8_t bar; + uint8_t reserved[3]; + uint32_t flags; /* QEMU_FIXED_BAR_F_* */ + uint64_t address; + uint64_t size; +} QEMU_PACKED QemuFixedBarsBar; /* 24 bytes */ + +#define QEMU_FIXED_BAR_F_MEM64 (1u << 0) +#define QEMU_FIXED_BAR_F_PREF (1u << 1) +#define QEMU_FIXED_BARS_DEV_F_FIXED (1u << 0) + +bool fixed_bars_write_blob(FWCfgState *fw_cfg, + uint64_t mmio32_base, + uint64_t mmio32_size, + uint64_t mmio64_base, + uint64_t mmio64_size, + Error **errp); + +#endif /* HW_PCI_FIXED_BAR_H */ -- 2.34.1
