Add QemuFixedBarsDxe, a DXE driver that enables fixed PCI BAR placement
using metadata provided by QEMU through the "etc/fixed-bars" fw_cfg
file.

The driver installs EFI_INCOMPATIBLE_PCI_DEVICE_SUPPORT_PROTOCOL before
PciBusDxe starts so fixed BAR addresses can be returned during
CheckDevice(). PciBusDxe then programs those addresses directly when
assigning BAR resources.

The driver also reserves the fixed BAR address ranges in GCD before PCI
enumeration and, after enumeration completes, updates PCI bridge memory
windows to cover the programmed BAR ranges.

Signed-off-by: Tushar Dave <[email protected]>
---
 ArmVirtPkg/ArmVirtQemu.dsc                    |   1 +
 ArmVirtPkg/ArmVirtQemuFvMain.fdf.inc          |   1 +
 OvmfPkg/QemuFixedBarsDxe/QemuFixedBarsDxe.c   | 772 ++++++++++++++++++
 OvmfPkg/QemuFixedBarsDxe/QemuFixedBarsDxe.inf |  43 +
 4 files changed, 817 insertions(+)
 create mode 100644 OvmfPkg/QemuFixedBarsDxe/QemuFixedBarsDxe.c
 create mode 100644 OvmfPkg/QemuFixedBarsDxe/QemuFixedBarsDxe.inf

diff --git a/ArmVirtPkg/ArmVirtQemu.dsc b/ArmVirtPkg/ArmVirtQemu.dsc
index 0212be4a33..58164d5418 100644
--- a/ArmVirtPkg/ArmVirtQemu.dsc
+++ b/ArmVirtPkg/ArmVirtQemu.dsc
@@ -469,6 +469,7 @@
   #
   # PCI support
   #
+  OvmfPkg/QemuFixedBarsDxe/QemuFixedBarsDxe.inf
   MdeModulePkg/Bus/Pci/PciHostBridgeDxe/PciHostBridgeDxe.inf
 
   #
diff --git a/ArmVirtPkg/ArmVirtQemuFvMain.fdf.inc 
b/ArmVirtPkg/ArmVirtQemuFvMain.fdf.inc
index ad158f57f1..879165de3f 100644
--- a/ArmVirtPkg/ArmVirtQemuFvMain.fdf.inc
+++ b/ArmVirtPkg/ArmVirtQemuFvMain.fdf.inc
@@ -157,6 +157,7 @@ READ_LOCK_STATUS   = TRUE
   # PCI support
   #
   INF UefiCpuPkg/CpuMmio2Dxe/CpuMmio2Dxe.inf
+  INF OvmfPkg/QemuFixedBarsDxe/QemuFixedBarsDxe.inf
   INF MdeModulePkg/Bus/Pci/PciHostBridgeDxe/PciHostBridgeDxe.inf
   INF MdeModulePkg/Bus/Pci/PciBusDxe/PciBusDxe.inf
   INF OvmfPkg/PciHotPlugInitDxe/PciHotPlugInit.inf
diff --git a/OvmfPkg/QemuFixedBarsDxe/QemuFixedBarsDxe.c 
b/OvmfPkg/QemuFixedBarsDxe/QemuFixedBarsDxe.c
new file mode 100644
index 0000000000..1ae45e1079
--- /dev/null
+++ b/OvmfPkg/QemuFixedBarsDxe/QemuFixedBarsDxe.c
@@ -0,0 +1,772 @@
+/** @file
+  QemuFixedBarsDxe implements EFI_INCOMPATIBLE_PCI_DEVICE_SUPPORT_PROTOCOL
+  to place PCI BAR addresses at fixed addresses using the
+  etc/fixed-bars fw_cfg blob provided by QEMU.
+
+  Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.<BR>
+
+  SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
+
+#include <PiDxe.h>
+#include <IndustryStandard/Acpi.h>
+#include <IndustryStandard/Pci.h>
+#include <Library/BaseMemoryLib.h>
+#include <Library/DebugLib.h>
+#include <Library/DxeServicesTableLib.h>
+#include <Library/MemoryAllocationLib.h>
+#include <Library/QemuFwCfgLib.h>
+#include <Library/UefiBootServicesTableLib.h>
+#include <Library/UefiDriverEntryPoint.h>
+#include <Protocol/IncompatiblePciDeviceSupport.h>
+#include <Protocol/PciEnumerationComplete.h>
+#include <Protocol/PciIo.h>
+
+/* byte-for-byte identical to QEMU's structs in hw/pci/pci-fixed-bar.h */
+#pragma pack (1)
+
+typedef struct {
+  UINT32    Version;
+  UINT32    NumDevices;
+} QEMU_FIXED_BARS_HDR;                       /* 8 bytes */
+
+typedef struct {
+  UINT16    VendorId;
+  UINT16    DeviceId;
+  UINT8     DevFlags;
+  UINT8     RpBus;
+  UINT8     NumBars;
+  UINT8     Reserved;
+} QEMU_FIXED_BARS_DEVICE;                    /* 8 bytes */
+
+typedef struct {
+  UINT8     Bar;
+  UINT8     Reserved[3];
+  UINT32    Flags;
+  UINT64    Address;
+  UINT64    Size;
+} QEMU_FIXED_BARS_BAR;                       /* 24 bytes */
+
+#pragma pack ()
+
+#define QEMU_FIXED_BARS_VERSION      1
+#define QEMU_FIXED_BAR_F_MEM64       BIT0
+#define QEMU_FIXED_BAR_F_PREF        BIT1
+#define QEMU_FIXED_BARS_DEV_F_FIXED  BIT0
+
+/*
+ * ACPI general-flag bits (ACPI 6.x 6.4.3.5.1):
+ *   BIT2 = _MIF (minimum address fixed)
+ *   BIT3 = _MAF (maximum address fixed)
+ * Both set means AddrRangeMin carries the exact required base address.
+ */
+#define ACPI_ADDR_FLAG_FIXED  (BIT2 | BIT3)
+
+/*
+ * P2P bridge memory window register offsets.
+ * PCI Local Bus Specification 2.2, Section 3.2.5.6.
+ */
+#define PPB_MEM32_BASE_OFFSET       OFFSET_OF (PCI_TYPE01, Bridge.MemoryBase)
+#define PPB_MEM32_LIMIT_OFFSET      OFFSET_OF (PCI_TYPE01, Bridge.MemoryLimit)
+#define PPB_PREF_MEM_BASE_OFFSET    OFFSET_OF (PCI_TYPE01, 
Bridge.PrefetchableMemoryBase)
+#define PPB_PREF_MEM_LIMIT_OFFSET   OFFSET_OF (PCI_TYPE01, 
Bridge.PrefetchableMemoryLimit)
+#define PPB_PREF_BASE_HI32_OFFSET   OFFSET_OF (PCI_TYPE01, 
Bridge.PrefetchableBaseUpper32)
+#define PPB_PREF_LIMIT_HI32_OFFSET  OFFSET_OF (PCI_TYPE01, 
Bridge.PrefetchableLimitUpper32)
+
+/* QFBD = Qemu Fixed Bars Dxe; used as the tag in this file's DEBUG() 
messages. */
+/* Raw blob kept in memory; device entries point into it. */
+STATIC UINT8              *mBlobData;
+STATIC QEMU_FIXED_BARS_HDR mHdr;
+
+/* Pre-parsed per-device index built from the blob at startup. */
+typedef struct {
+  UINT16                VendorId;
+  UINT16                DeviceId;
+  UINT8                 DevFlags;
+  UINT8                 RpBus;    /* primary bus of the root port */
+  UINT8                 NumBars;
+  QEMU_FIXED_BARS_BAR  *Bars;    /* pointer into mBlobData */
+} QFBD_DEVICE;
+
+/*
+ * Per-(VendorId, DeviceId) class. CheckDevice() is called once per PCI
+ * function and once per scan pass. Different VID:DID classes must advance
+ * their counters independently so that one class cannot disturb the
+ * position of another across scan passes.
+ *
+ * Assumptions:
+ *   - QEMU emits blob entries in the same order PciBusDxe discovers devices.
+ *   - For identical VID:DID devices, discovery order is stable across passes.
+ *   - The counter is maintained per VID:DID class, not globally.
+ */
+typedef struct {
+  UINT16    VendorId;
+  UINT16    DeviceId;
+  UINTN     Count;       /* number of devices with this VID:DID in the blob */
+  UINTN     CurrentIdx;  /* next entry to serve; wraps at Count */
+} QFBD_DEVICE_CLASS;
+
+STATIC QFBD_DEVICE        *mDevices;
+STATIC UINTN               mNumDevices;
+STATIC QFBD_DEVICE_CLASS  *mClasses;   /* heap-allocated; mNumDevices entries 
max */
+STATIC UINTN               mNumClasses;
+
+STATIC VOID
+ReserveHpaRanges (VOID)
+{
+  UINTN       DevIdx;
+  UINTN       BarIdx;
+  UINT64      Min64;
+  UINT64      Max64;
+  UINT64      Min32;
+  UINT64      Max32;
+  UINT64      Gran;
+  UINT64      Base;
+  UINT64      Size;
+  EFI_STATUS  Status;
+
+  Min64 = MAX_UINT64;
+  Max64 = 0;
+  Min32 = MAX_UINT64;
+  Max32 = 0;
+  Gran  = SIZE_1MB;
+
+  for (DevIdx = 0; DevIdx < mNumDevices; DevIdx++) {
+    QFBD_DEVICE  *Dev = &mDevices[DevIdx];
+
+    if (!(Dev->DevFlags & QEMU_FIXED_BARS_DEV_F_FIXED)) {
+      continue;
+    }
+    for (BarIdx = 0; BarIdx < Dev->NumBars; BarIdx++) {
+      QEMU_FIXED_BARS_BAR  *Bar = &Dev->Bars[BarIdx];
+
+      if (Bar->Flags & QEMU_FIXED_BAR_F_MEM64) {
+        if (Bar->Address < Min64) {
+          Min64 = Bar->Address;
+        }
+        if (Bar->Address + Bar->Size > Max64) {
+          Max64 = Bar->Address + Bar->Size;
+        }
+      } else {
+        if (Bar->Address < Min32) {
+          Min32 = Bar->Address;
+        }
+        if (Bar->Address + Bar->Size > Max32) {
+          Max32 = Bar->Address + Bar->Size;
+        }
+      }
+    }
+  }
+
+  if (Min64 < Max64) {
+    Base   = Min64 & ~(Gran - 1);
+    Size   = ALIGN_VALUE (Max64 - Base, Gran);
+    Status = gDS->AllocateMemorySpace (
+                    EfiGcdAllocateAddress,
+                    EfiGcdMemoryTypeMemoryMappedIo,
+                    0, Size, &Base, gImageHandle, NULL
+                    );
+    DEBUG ((EFI_ERROR (Status) ? DEBUG_ERROR : DEBUG_INFO,
+            "QFBD: reserve 64-bit 0x%016Lx+0x%016Lx: %r\n",
+            Base, Size, Status));
+  }
+
+  if (Min32 < Max32) {
+    Base   = Min32 & ~(Gran - 1);
+    Size   = ALIGN_VALUE (Max32 - Base, Gran);
+    Status = gDS->AllocateMemorySpace (
+                    EfiGcdAllocateAddress,
+                    EfiGcdMemoryTypeMemoryMappedIo,
+                    0, Size, &Base, gImageHandle, NULL
+                    );
+    DEBUG ((EFI_ERROR (Status) ? DEBUG_ERROR : DEBUG_INFO,
+            "QFBD: reserve 32-bit 0x%016Lx+0x%016Lx: %r\n",
+            Base, Size, Status));
+  }
+}
+
+/* Walk the blob once, build the mDevices[] index and the mClasses[] table. */
+STATIC VOID
+InitDevices (
+  IN UINTN  BlobSize
+  )
+{
+  UINT8  *Ptr = mBlobData + sizeof (QEMU_FIXED_BARS_HDR);
+  UINT8  *End = mBlobData + BlobSize;
+  UINTN   DevIdx, ClassIdx;
+
+  for (DevIdx = 0; DevIdx < mHdr.NumDevices; DevIdx++) {
+    QEMU_FIXED_BARS_DEVICE  *Dev;
+    UINT8                   *Next;
+
+    if (Ptr + sizeof (QEMU_FIXED_BARS_DEVICE) > End) {
+      DEBUG ((DEBUG_ERROR, "QFBD: blob truncated at device %u\n", 
(UINT32)DevIdx));
+      break;
+    }
+
+    Dev  = (QEMU_FIXED_BARS_DEVICE *)Ptr;
+    Next = Ptr
+         + sizeof (QEMU_FIXED_BARS_DEVICE)
+         + Dev->NumBars * sizeof (QEMU_FIXED_BARS_BAR);
+
+    if (Next > End) {
+      DEBUG ((DEBUG_ERROR, "QFBD: blob truncated at device %u\n", 
(UINT32)DevIdx));
+      break;
+    }
+
+    mDevices[mNumDevices].VendorId  = Dev->VendorId;
+    mDevices[mNumDevices].DeviceId  = Dev->DeviceId;
+    mDevices[mNumDevices].DevFlags  = Dev->DevFlags;
+    mDevices[mNumDevices].RpBus     = Dev->RpBus;
+    mDevices[mNumDevices].NumBars   = Dev->NumBars;
+    mDevices[mNumDevices].Bars      = (QEMU_FIXED_BARS_BAR *)(
+      Ptr + sizeof (QEMU_FIXED_BARS_DEVICE)
+      );
+    mNumDevices++;
+
+    /* Find or create the class entry for this VID:DID. */
+    for (ClassIdx = 0; ClassIdx < mNumClasses; ClassIdx++) {
+      if (mClasses[ClassIdx].VendorId == Dev->VendorId &&
+          mClasses[ClassIdx].DeviceId == Dev->DeviceId)
+      {
+        mClasses[ClassIdx].Count++;
+        break;
+      }
+    }
+    if (ClassIdx == mNumClasses) {
+      mClasses[mNumClasses].VendorId   = Dev->VendorId;
+      mClasses[mNumClasses].DeviceId   = Dev->DeviceId;
+      mClasses[mNumClasses].Count      = 1;
+      mClasses[mNumClasses].CurrentIdx = 0;
+      mNumClasses++;
+    }
+
+    DEBUG ((DEBUG_INFO,
+            "QFBD: device %04x:%04x num_bars=%u\n",
+            Dev->VendorId, Dev->DeviceId, Dev->NumBars));
+
+    Ptr = Next;
+  }
+
+  for (ClassIdx = 0; ClassIdx < mNumClasses; ClassIdx++) {
+    DEBUG ((DEBUG_INFO,
+            "QFBD: class %04x:%04x count=%u\n",
+            mClasses[ClassIdx].VendorId, mClasses[ClassIdx].DeviceId,
+            (UINT32)mClasses[ClassIdx].Count));
+  }
+}
+
+STATIC
+EFI_STATUS
+EFIAPI
+FixedBarCheckDevice (
+  IN  EFI_INCOMPATIBLE_PCI_DEVICE_SUPPORT_PROTOCOL  *This,
+  IN  UINTN                                          VendorId,
+  IN  UINTN                                          DeviceId,
+  IN  UINTN                                          RevisionId,
+  IN  UINTN                                          SubsystemVendorId,
+  IN  UINTN                                          SubsystemDeviceId,
+  OUT VOID                                           **Configuration
+  )
+{
+  QFBD_DEVICE_CLASS                  *Class;
+  QFBD_DEVICE                        *Dev;
+  UINTN                               BarIdx, DevIdx, MatchCount, ClassIdx;
+  UINT8                              *Buffer;
+  EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR  *Desc;
+  EFI_ACPI_END_TAG_DESCRIPTOR        *End;
+
+  (VOID)RevisionId;
+  (VOID)SubsystemVendorId;
+  (VOID)SubsystemDeviceId;
+
+  if (Configuration == NULL) {
+    return EFI_INVALID_PARAMETER;
+  }
+
+  *Configuration = NULL;
+
+  if (mNumClasses == 0) {
+    return EFI_SUCCESS;
+  }
+
+  /* Look up the per-VID:DID class. */
+  Class = NULL;
+  for (ClassIdx = 0; ClassIdx < mNumClasses; ClassIdx++) {
+    if (mClasses[ClassIdx].VendorId == (UINT16)VendorId &&
+        mClasses[ClassIdx].DeviceId == (UINT16)DeviceId)
+    {
+      Class = &mClasses[ClassIdx];
+      break;
+    }
+  }
+  if (Class == NULL) {
+    return EFI_SUCCESS;  /* not a fixed-bar device */
+  }
+
+  /* Find the CurrentIdx-th device in mDevices[] matching this VID:DID. */
+  Dev        = NULL;
+  MatchCount = 0;
+  for (DevIdx = 0; DevIdx < mNumDevices; DevIdx++) {
+    if (mDevices[DevIdx].VendorId == (UINT16)VendorId &&
+        mDevices[DevIdx].DeviceId == (UINT16)DeviceId)
+    {
+      if (MatchCount == Class->CurrentIdx) {
+        Dev = &mDevices[DevIdx];
+        break;
+      }
+      MatchCount++;
+    }
+  }
+  if (Dev == NULL) {
+    return EFI_SUCCESS;
+  }
+
+  /* Always advance the counter so ordering stays in sync with the blob. */
+  Class->CurrentIdx++;
+  if (Class->CurrentIdx >= Class->Count) {
+    Class->CurrentIdx = 0;
+  }
+
+  if (!(Dev->DevFlags & QEMU_FIXED_BARS_DEV_F_FIXED)) {
+    return EFI_SUCCESS;
+  }
+
+  Buffer = AllocateZeroPool (Dev->NumBars * sizeof (*Desc) + sizeof (*End));
+  if (Buffer == NULL) {
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  Desc = (EFI_ACPI_ADDRESS_SPACE_DESCRIPTOR *)Buffer;
+
+  for (BarIdx = 0; BarIdx < Dev->NumBars; BarIdx++) {
+    QEMU_FIXED_BARS_BAR  *Bar = &Dev->Bars[BarIdx];
+
+    Desc->Desc                  = ACPI_ADDRESS_SPACE_DESCRIPTOR;
+    Desc->Len                   = (UINT16)(sizeof (*Desc) - 3);
+    Desc->ResType               = ACPI_ADDRESS_SPACE_TYPE_MEM;
+    Desc->GenFlag               = ACPI_ADDR_FLAG_FIXED;
+    Desc->SpecificFlag          = 0;
+    Desc->AddrSpaceGranularity  = (Bar->Flags & QEMU_FIXED_BAR_F_MEM64) ? 64 : 
32;
+    Desc->AddrRangeMin          = Bar->Address;
+    Desc->AddrRangeMax          = Bar->Size - 1;
+    Desc->AddrTranslationOffset = BarIdx;
+    Desc->AddrLen               = Bar->Size;
+
+    DEBUG ((DEBUG_INFO,
+            "QFBD: %04x:%04x BAR%u -> 0x%016Lx size 0x%016Lx\n",
+            Dev->VendorId, Dev->DeviceId,
+            (UINT32)Bar->Bar, Bar->Address, Bar->Size));
+
+    Desc++;
+  }
+
+  End           = (EFI_ACPI_END_TAG_DESCRIPTOR *)Desc;
+  End->Desc     = ACPI_END_TAG_DESCRIPTOR;
+  End->Checksum = 0;
+
+  *Configuration = Buffer;
+
+  return EFI_SUCCESS;
+}
+
+STATIC EFI_INCOMPATIBLE_PCI_DEVICE_SUPPORT_PROTOCOL  mFixedBarSupport = {
+  FixedBarCheckDevice
+};
+
+/*
+ * Range type: Min inclusive, Max exclusive (base + size).
+ */
+typedef struct {
+  UINT64   Min;
+  UINT64   Max;
+  BOOLEAN  Valid;
+} QFBD_RANGE;
+
+typedef struct {
+  QFBD_RANGE  Pref;
+  QFBD_RANGE  NonPref;
+} QFBD_BRIDGE_RANGES;
+
+STATIC VOID
+RangeUnion (
+  QFBD_RANGE  *R,
+  UINT64       Addr,
+  UINT64       Size
+  )
+{
+  if (Addr     < R->Min) R->Min = Addr;
+  if (Addr + Size > R->Max) R->Max = Addr + Size;
+  R->Valid = TRUE;
+}
+
+/*
+ * Write PMem and Mem32 window registers on one bridge.
+ */
+STATIC VOID
+SetBridgeWindow (
+  IN EFI_PCI_IO_PROTOCOL  *PciIo,
+  IN UINTN                 B,
+  IN UINTN                 D,
+  IN UINTN                 F,
+  IN QFBD_BRIDGE_RANGES    R
+  )
+{
+  UINT16  Cmd;
+
+  if (R.Pref.Valid) {
+    UINT64  Base  = R.Pref.Min;
+    UINT64  End   = R.Pref.Max - 1;
+    UINT16  B16   = (UINT16)(((UINT32)Base) >> 16);
+    UINT16  L16   = (UINT16)(((UINT32)End)  >> 16);
+    UINT32  BHi   = (UINT32)RShiftU64 (Base, 32);
+    UINT32  LHi   = (UINT32)RShiftU64 (End,  32);
+
+    PciIo->Pci.Write (PciIo, EfiPciIoWidthUint16, PPB_PREF_MEM_BASE_OFFSET,  
1, &B16);
+    PciIo->Pci.Write (PciIo, EfiPciIoWidthUint16, PPB_PREF_MEM_LIMIT_OFFSET, 
1, &L16);
+    PciIo->Pci.Write (PciIo, EfiPciIoWidthUint32, PPB_PREF_BASE_HI32_OFFSET, 
1, &BHi);
+    PciIo->Pci.Write (PciIo, EfiPciIoWidthUint32, PPB_PREF_LIMIT_HI32_OFFSET, 
1, &LHi);
+
+    DEBUG ((DEBUG_INFO, "QFBD: bridge %02Lx:%02Lx.%Lx PMem [0x%Lx, 0x%Lx]\n",
+            (UINT64)B, (UINT64)D, (UINT64)F, Base, End));
+  }
+
+  if (R.NonPref.Valid) {
+    UINT64  Base = R.NonPref.Min;
+    UINT64  End  = R.NonPref.Max - 1;
+    UINT16  B16  = (UINT16)(Base >> 16);
+    UINT16  L16  = (UINT16)(End  >> 16);
+
+    PciIo->Pci.Write (PciIo, EfiPciIoWidthUint16, PPB_MEM32_BASE_OFFSET,  1, 
&B16);
+    PciIo->Pci.Write (PciIo, EfiPciIoWidthUint16, PPB_MEM32_LIMIT_OFFSET, 1, 
&L16);
+
+    DEBUG ((DEBUG_INFO, "QFBD: bridge %02Lx:%02Lx.%Lx Mem32 [0x%Lx, 0x%Lx]\n",
+            (UINT64)B, (UINT64)D, (UINT64)F, Base, End));
+  }
+
+  if (R.Pref.Valid || R.NonPref.Valid) {
+    PciIo->Pci.Read  (PciIo, EfiPciIoWidthUint16, PCI_COMMAND_OFFSET, 1, &Cmd);
+    Cmd |= EFI_PCI_COMMAND_MEMORY_SPACE | EFI_PCI_COMMAND_BUS_MASTER;
+    PciIo->Pci.Write (PciIo, EfiPciIoWidthUint16, PCI_COMMAND_OFFSET, 1, &Cmd);
+  }
+}
+
+/*
+ * Read the BAR ranges (prefetchable and non-prefetchable) from an endpoint
+ * by looking up its programmed BAR addresses in the blob.  Every device
+ * with the FIXED devflag bit set is guaranteed to have blob-provided BAR
+ * entries covering all its BARs, so a match is always found.
+ */
+STATIC VOID
+ClassifyBar (
+  IN OUT QFBD_BRIDGE_RANGES  *R,
+  IN     UINT64              Addr
+  )
+{
+  UINTN  DevIdx, BarIdx;
+
+  for (DevIdx = 0; DevIdx < mNumDevices; DevIdx++) {
+    for (BarIdx = 0; BarIdx < mDevices[DevIdx].NumBars; BarIdx++) {
+      QEMU_FIXED_BARS_BAR  *Bar = &mDevices[DevIdx].Bars[BarIdx];
+
+      if (Bar->Address == Addr && Addr != 0) {
+        if (Bar->Flags & QEMU_FIXED_BAR_F_PREF) {
+          RangeUnion (&R->Pref, Addr, Bar->Size);
+        } else {
+          RangeUnion (&R->NonPref, Addr, Bar->Size);
+        }
+      }
+    }
+  }
+}
+
+STATIC QFBD_BRIDGE_RANGES
+EndpointRanges (
+  IN EFI_PCI_IO_PROTOCOL  *PciIo
+  )
+{
+  QFBD_BRIDGE_RANGES  R = { { MAX_UINT64, 0, FALSE }, { MAX_UINT64, 0, FALSE } 
};
+  UINTN               Off;
+
+  for (Off = PCI_BASE_ADDRESSREG_OFFSET; Off <= 0x24; ) {
+    UINT32  Lo;
+
+    PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, Off, 1, &Lo);
+
+    if ((Lo & 0x1) == 1) {          /* IO BAR */
+      Off += 4;
+      continue;
+    }
+    if ((Lo & 0x6) == 0x4) {        /* 64-bit memory BAR */
+      UINT32  Hi;
+      UINT64  Addr;
+
+      PciIo->Pci.Read (PciIo, EfiPciIoWidthUint32, Off + 4, 1, &Hi);
+      Addr = ((UINT64)Hi << 32) | (Lo & ~(UINT32)0xF);
+      ClassifyBar (&R, Addr);
+      Off += 8;
+    } else {                         /* 32-bit memory BAR */
+      ClassifyBar (&R, (UINT64)(Lo & ~(UINT32)0xF));
+      Off += 4;
+    }
+  }
+  return R;
+}
+
+/*
+ * Recursively walk bridges starting at SecBus.  For each bridge, collect
+ * ranges (prefetchable and non-prefetchable) from all endpoints below it
+ * (bottom-up), program the bridge window, and return the union to the
+ * caller.
+ *
+ * Because every device with the FIXED devflag bit set is guaranteed to have
+ * blob-provided BAR entries, EndpointRanges() only returns non-empty
+ * results for fixed devices.  Branches leading to endpoints with no fixed
+ * BARs naturally produce empty ranges and are not programmed.
+ */
+STATIC QFBD_BRIDGE_RANGES
+WalkAndProgram (
+  IN UINTN    Seg,
+  IN UINT8    Bus,
+  IN UINTN    HandleCount,
+  IN EFI_HANDLE  *Handles
+  )
+{
+  QFBD_BRIDGE_RANGES  Total = { { MAX_UINT64, 0, FALSE }, { MAX_UINT64, 0, 
FALSE } };
+  UINTN               Idx;
+
+  for (Idx = 0; Idx < HandleCount; Idx++) {
+    EFI_PCI_IO_PROTOCOL  *PciIo;
+    UINTN                 S, B, D, F;
+    UINT8                 HeaderType, SecBus;
+    QFBD_BRIDGE_RANGES    Sub;
+
+    if (EFI_ERROR (gBS->HandleProtocol (
+                          Handles[Idx], &gEfiPciIoProtocolGuid,
+                          (VOID **)&PciIo)))
+    {
+      continue;
+    }
+
+    PciIo->GetLocation (PciIo, &S, &B, &D, &F);
+    if (S != Seg || (UINT8)B != Bus) {
+      continue;
+    }
+
+    PciIo->Pci.Read (PciIo, EfiPciIoWidthUint8, PCI_HEADER_TYPE_OFFSET, 1, 
&HeaderType);
+
+    if ((HeaderType & 0x7F) == HEADER_TYPE_PCI_TO_PCI_BRIDGE) {
+      /* Bridge: recurse first, then program with the subtree's range. */
+      PciIo->Pci.Read (PciIo, EfiPciIoWidthUint8,
+                       PCI_BRIDGE_SECONDARY_BUS_REGISTER_OFFSET, 1, &SecBus);
+      Sub = WalkAndProgram (Seg, SecBus, HandleCount, Handles);
+      if (Sub.Pref.Valid || Sub.NonPref.Valid) {
+        SetBridgeWindow (PciIo, B, D, F, Sub);
+      }
+    } else {
+      /* Endpoint: collect its fixed BAR ranges. */
+      Sub = EndpointRanges (PciIo);
+    }
+
+    if (Sub.Pref.Valid) {
+      RangeUnion (&Total.Pref, Sub.Pref.Min, Sub.Pref.Max - Sub.Pref.Min);
+    }
+    if (Sub.NonPref.Valid) {
+      RangeUnion (&Total.NonPref, Sub.NonPref.Min, Sub.NonPref.Max - 
Sub.NonPref.Min);
+    }
+  }
+
+  return Total;
+}
+
+STATIC VOID
+EFIAPI
+FixBridgeWindows (
+  IN EFI_EVENT  Event,
+  IN VOID       *Context
+  )
+{
+  EFI_STATUS  Status;
+  UINTN       HandleCount;
+  EFI_HANDLE  *Handles;
+  UINTN       DevIdx, Idx;
+
+  (VOID)Context;
+
+  gBS->CloseEvent (Event);
+
+  Status = gBS->LocateHandleBuffer (
+                  ByProtocol, &gEfiPciIoProtocolGuid,
+                  NULL, &HandleCount, &Handles
+                  );
+  if (EFI_ERROR (Status)) {
+    return;
+  }
+
+  /*
+   * For each unique rp_bus that has at least one FIXED device, find the
+   * bridge on that bus and walk its subtree bottom-up to set windows.
+   */
+  for (DevIdx = 0; DevIdx < mNumDevices; DevIdx++) {
+    UINT8  RpBus;
+
+    if (!(mDevices[DevIdx].DevFlags & QEMU_FIXED_BARS_DEV_F_FIXED)) {
+      continue;
+    }
+    RpBus = mDevices[DevIdx].RpBus;
+
+    /* Skip if we already processed this rp_bus. */
+    {
+      UINTN  Di;
+      BOOLEAN Already = FALSE;
+      for (Di = 0; Di < DevIdx; Di++) {
+        if ((mDevices[Di].DevFlags & QEMU_FIXED_BARS_DEV_F_FIXED) &&
+            mDevices[Di].RpBus == RpBus)
+        {
+          Already = TRUE;
+          break;
+        }
+      }
+      if (Already) continue;
+    }
+
+    /* Find the RP bridge handle: bridge on Bus == RpBus. */
+    for (Idx = 0; Idx < HandleCount; Idx++) {
+      EFI_PCI_IO_PROTOCOL  *PciIo;
+      UINTN                 S, B, D, F;
+      UINT8                 HeaderType, SecBus;
+      QFBD_BRIDGE_RANGES    Sub;
+
+      if (EFI_ERROR (gBS->HandleProtocol (
+                            Handles[Idx], &gEfiPciIoProtocolGuid,
+                            (VOID **)&PciIo)))
+      {
+        continue;
+      }
+
+      PciIo->GetLocation (PciIo, &S, &B, &D, &F);
+      if ((UINT8)B != RpBus) {
+        continue;
+      }
+
+      PciIo->Pci.Read (PciIo, EfiPciIoWidthUint8,
+                       PCI_HEADER_TYPE_OFFSET, 1, &HeaderType);
+      if ((HeaderType & 0x7F) != HEADER_TYPE_PCI_TO_PCI_BRIDGE) {
+        continue;
+      }
+
+      PciIo->Pci.Read (PciIo, EfiPciIoWidthUint8,
+                       PCI_BRIDGE_SECONDARY_BUS_REGISTER_OFFSET, 1, &SecBus);
+
+      Sub = WalkAndProgram (S, SecBus, HandleCount, Handles);
+      if (Sub.Pref.Valid || Sub.NonPref.Valid) {
+        SetBridgeWindow (PciIo, B, D, F, Sub);
+        DEBUG ((DEBUG_INFO, "QFBD: RP %02Lx:%02Lx.%Lx programmed\n",
+                (UINT64)B, (UINT64)D, (UINT64)F));
+      }
+    }
+  }
+
+  FreePool (Handles);
+}
+
+EFI_STATUS
+EFIAPI
+QemuFixedBarsDxeEntryPoint (
+  IN EFI_HANDLE        ImageHandle,
+  IN EFI_SYSTEM_TABLE  *SystemTable
+  )
+{
+  FIRMWARE_CONFIG_ITEM  FwCfgItem;
+  UINTN                 FwCfgSize;
+  EFI_STATUS            Status;
+  EFI_HANDLE            Handle;
+  EFI_EVENT             Event;
+  VOID                  *Registration;
+
+  Status = QemuFwCfgFindFile ("etc/fixed-bars", &FwCfgItem, &FwCfgSize);
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_INFO, "QFBD: etc/fixed-bars absent, nothing to do\n"));
+    return EFI_SUCCESS;
+  }
+
+  if (FwCfgSize < sizeof (mHdr)) {
+    DEBUG ((DEBUG_ERROR, "QFBD: blob too small (%Lu bytes)\n",
+            (UINT64)FwCfgSize));
+    return EFI_UNSUPPORTED;
+  }
+
+  QemuFwCfgSelectItem (FwCfgItem);
+  QemuFwCfgReadBytes (sizeof (mHdr), &mHdr);
+
+  if (mHdr.Version != QEMU_FIXED_BARS_VERSION) {
+    DEBUG ((DEBUG_ERROR,
+            "QFBD: unsupported blob version %u (expected %u)\n",
+            mHdr.Version, QEMU_FIXED_BARS_VERSION));
+    return EFI_UNSUPPORTED;
+  }
+
+  if (mHdr.NumDevices == 0) {
+    DEBUG ((DEBUG_INFO, "QFBD: zero devices, nothing to do\n"));
+    return EFI_SUCCESS;
+  }
+
+  /* Read the full blob into a persistent buffer; device entries point into 
it. */
+  mBlobData = AllocatePool (FwCfgSize);
+  if (mBlobData == NULL) {
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  QemuFwCfgSelectItem (FwCfgItem);
+  QemuFwCfgReadBytes (FwCfgSize, mBlobData);
+  CopyMem (&mHdr, mBlobData, sizeof (mHdr));
+
+  mDevices = AllocateZeroPool (mHdr.NumDevices * sizeof (QFBD_DEVICE));
+  if (mDevices == NULL) {
+    FreePool (mBlobData);
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  /* Worst case: every device has a unique VID:DID — allocate mNumDevices 
slots. */
+  mClasses = AllocateZeroPool (mHdr.NumDevices * sizeof (QFBD_DEVICE_CLASS));
+  if (mClasses == NULL) {
+    FreePool (mDevices);
+    FreePool (mBlobData);
+    return EFI_OUT_OF_RESOURCES;
+  }
+
+  DEBUG ((DEBUG_INFO, "QFBD: %u device(s)\n", mHdr.NumDevices));
+
+  mNumDevices = 0;
+  mNumClasses = 0;
+  InitDevices (FwCfgSize);
+  ReserveHpaRanges ();
+
+  Handle = NULL;
+  Status = gBS->InstallProtocolInterface (
+                  &Handle,
+                  &gEfiIncompatiblePciDeviceSupportProtocolGuid,
+                  EFI_NATIVE_INTERFACE,
+                  &mFixedBarSupport
+                  );
+  if (EFI_ERROR (Status)) {
+    DEBUG ((DEBUG_ERROR, "QFBD: install protocol failed: %r\n", Status));
+    FreePool (mDevices);
+    FreePool (mBlobData);
+    return Status;
+  }
+
+  /* Register bridge window fixup for after PCI enumeration completes. */
+  Status = gBS->CreateEvent (
+                  EVT_NOTIFY_SIGNAL, TPL_CALLBACK,
+                  FixBridgeWindows, NULL, &Event
+                  );
+  if (!EFI_ERROR (Status)) {
+    gBS->RegisterProtocolNotify (
+           &gEfiPciEnumerationCompleteProtocolGuid,
+           Event, &Registration
+           );
+  }
+
+  return EFI_SUCCESS;
+}
diff --git a/OvmfPkg/QemuFixedBarsDxe/QemuFixedBarsDxe.inf 
b/OvmfPkg/QemuFixedBarsDxe/QemuFixedBarsDxe.inf
new file mode 100644
index 0000000000..8c08a332b3
--- /dev/null
+++ b/OvmfPkg/QemuFixedBarsDxe/QemuFixedBarsDxe.inf
@@ -0,0 +1,43 @@
+## @file
+#  Fixed-BAR support for QEMU passthrough devices.
+#
+#  Reads QEMU's etc/fixed-bars fw_cfg blob and publishes
+#  EFI_INCOMPATIBLE_PCI_DEVICE_SUPPORT_PROTOCOL so that PciBusDxe places
+#  each matching device's BARs at given addresses.
+#
+#  SPDX-License-Identifier: BSD-2-Clause-Patent
+##
+
+[Defines]
+  INF_VERSION    = 0x00010005
+  BASE_NAME      = QemuFixedBarsDxe
+  FILE_GUID      = a1b2c3d4-e5f6-7890-abcd-ef1234567890
+  MODULE_TYPE    = DXE_DRIVER
+  VERSION_STRING = 1.0
+  ENTRY_POINT    = QemuFixedBarsDxeEntryPoint
+
+[Sources]
+  QemuFixedBarsDxe.c
+
+[Packages]
+  MdePkg/MdePkg.dec
+  MdeModulePkg/MdeModulePkg.dec
+  OvmfPkg/OvmfPkg.dec
+
+[LibraryClasses]
+  BaseMemoryLib
+  DebugLib
+  DxeServicesTableLib
+  MemoryAllocationLib
+  QemuFwCfgLib
+  UefiBootServicesTableLib
+  UefiDriverEntryPoint
+
+[Protocols]
+  gEfiIncompatiblePciDeviceSupportProtocolGuid       ## PRODUCES
+  gEfiPciHostBridgeResourceAllocationProtocolGuid    ## CONSUMES
+  gEfiPciEnumerationCompleteProtocolGuid             ## CONSUMES
+  gEfiPciIoProtocolGuid                              ## CONSUMES
+
+[Depex]
+  gEfiPciHostBridgeResourceAllocationProtocolGuid
-- 
2.34.1


Reply via email to