diff mbox

[edk2,2/5] OvmfPkg: PlatformPei: enable PCIEXBAR (aka MMCONFIG / ECAM) on Q35

Message ID 1457102794-25499-3-git-send-email-lersek@redhat.com
State New
Headers show

Commit Message

Laszlo Ersek March 4, 2016, 2:46 p.m. UTC
The comments in the code should speak for themselves; here we note only
two facts:

- The PCI config space writes (to the PCIEXBAR register) are performed
  using the 0xCF8 / 0xCFC IO ports, by virtue of PciLib being resolved to
  BasePciLibCf8. (This library resolution will permanently remain in place
  for the PEI phase.)

- Since PCIEXBAR counts as a chipset register, it is the responsibility of
  the firmware to reprogram it at S3 resume. Therefore
  PciExBarInitialization() is called regardless of the boot path. (Marcel
  recently posted patches for SeaBIOS that implement this.)

This patch suffices to enable PCIEXBAR (and the dependent ACPI table
generation in QEMU), for the sake of "PCIeHotplug" in the Linux guest:

  ACPI: MCFG 0x000000007E193000 00003C
        (v01 BOCHS  BXPCMCFG 00000001 BXPC 00000001)
  PCI: MMCONFIG for domain 0000 [bus 00-ff] at [mem 0xb0000000-0xbfffffff]
       (base 0xb0000000)
  PCI: MMCONFIG at [mem 0xb0000000-0xbfffffff] reserved in E820
  acpi PNP0A08:00: _OSC: OS supports
                   [ExtendedConfig ASPM ClockPM Segments MSI]
  acpi PNP0A08:00: _OSC: OS now controls
                   [PCIeHotplug PME AER PCIeCapability]

In the following patches, we'll equip the core PCI host bridge / root
bridge driver and the rest of DXE as well to utilize ECAM on Q35.

Cc: Gabriel Somlo <somlo@cmu.edu>
Cc: Jordan Justen <jordan.l.justen@intel.com>
Cc: Marcel Apfelbaum <marcel@redhat.com>
Cc: Michał Zegan <webczat_200@poczta.onet.pl>
Ref: https://github.com/tianocore/edk2/issues/32
Ref: http://thread.gmane.org/gmane.comp.bios.coreboot.seabios/10548
Suggested-by: Marcel Apfelbaum <marcel@redhat.com>
Reported-by: Michał Zegan <webczat_200@poczta.onet.pl>
Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
---
 OvmfPkg/OvmfPkgIa32.dsc             |  8 ++
 OvmfPkg/OvmfPkgIa32X64.dsc          |  8 ++
 OvmfPkg/OvmfPkgX64.dsc              |  8 ++
 OvmfPkg/PlatformPei/PlatformPei.inf |  3 +
 OvmfPkg/PlatformPei/Platform.c      | 81 ++++++++++++++++++++
 5 files changed, 108 insertions(+)

Comments

Laszlo Ersek March 6, 2016, 12:33 p.m. UTC | #1
On 03/06/16 11:54, Marcel Apfelbaum wrote:
> On 03/04/2016 04:46 PM, Laszlo Ersek wrote:
>> The comments in the code should speak for themselves; here we note only
>> two facts:
>>
>> - The PCI config space writes (to the PCIEXBAR register) are performed
>>    using the 0xCF8 / 0xCFC IO ports, by virtue of PciLib being
>> resolved to
>>    BasePciLibCf8. (This library resolution will permanently remain in
>> place
>>    for the PEI phase.)
>>
>> - Since PCIEXBAR counts as a chipset register, it is the
>> responsibility of
>>    the firmware to reprogram it at S3 resume. Therefore
>>    PciExBarInitialization() is called regardless of the boot path.
>> (Marcel
>>    recently posted patches for SeaBIOS that implement this.)
>>
>> This patch suffices to enable PCIEXBAR (and the dependent ACPI table
>> generation in QEMU), for the sake of "PCIeHotplug" in the Linux guest:
>>
>>    ACPI: MCFG 0x000000007E193000 00003C
>>          (v01 BOCHS  BXPCMCFG 00000001 BXPC 00000001)
>>    PCI: MMCONFIG for domain 0000 [bus 00-ff] at [mem
>> 0xb0000000-0xbfffffff]
>>         (base 0xb0000000)
>>    PCI: MMCONFIG at [mem 0xb0000000-0xbfffffff] reserved in E820
>>    acpi PNP0A08:00: _OSC: OS supports
>>                     [ExtendedConfig ASPM ClockPM Segments MSI]
>>    acpi PNP0A08:00: _OSC: OS now controls
>>                     [PCIeHotplug PME AER PCIeCapability]
>>
>> In the following patches, we'll equip the core PCI host bridge / root
>> bridge driver and the rest of DXE as well to utilize ECAM on Q35.
>>
>> Cc: Gabriel Somlo <somlo@cmu.edu>
>> Cc: Jordan Justen <jordan.l.justen@intel.com>
>> Cc: Marcel Apfelbaum <marcel@redhat.com>
>> Cc: Michał Zegan <webczat_200@poczta.onet.pl>
>> Ref: https://github.com/tianocore/edk2/issues/32
>> Ref: http://thread.gmane.org/gmane.comp.bios.coreboot.seabios/10548
>> Suggested-by: Marcel Apfelbaum <marcel@redhat.com>
>> Reported-by: Michał Zegan <webczat_200@poczta.onet.pl>
>> Contributed-under: TianoCore Contribution Agreement 1.0
>> Signed-off-by: Laszlo Ersek <lersek@redhat.com>
>> ---
>>   OvmfPkg/OvmfPkgIa32.dsc             |  8 ++
>>   OvmfPkg/OvmfPkgIa32X64.dsc          |  8 ++
>>   OvmfPkg/OvmfPkgX64.dsc              |  8 ++
>>   OvmfPkg/PlatformPei/PlatformPei.inf |  3 +
>>   OvmfPkg/PlatformPei/Platform.c      | 81 ++++++++++++++++++++
>>   5 files changed, 108 insertions(+)
>>
>> diff --git a/OvmfPkg/OvmfPkgIa32.dsc b/OvmfPkg/OvmfPkgIa32.dsc
>> index 2b928506e481..d1f91172044e 100644
>> --- a/OvmfPkg/OvmfPkgIa32.dsc
>> +++ b/OvmfPkg/OvmfPkgIa32.dsc
>> @@ -395,6 +395,14 @@ [PcdsFixedAtBuild]
>>     gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x2F
>>   !endif
>>
>> +  # This PCD is used to set the base address of the PCI express
>> hierarchy. It
>> +  # is only consulted when OVMF runs on Q35. In that case it is
>> programmed into
>> +  # the PCIEXBAR register.
>> +  #
>> +  # The value we set below matches both the QEMU default, and the
>> value that
>> +  # SeaBIOS hard-codes.
>> +  gEfiMdePkgTokenSpaceGuid.PcdPciExpressBaseAddress|0xB0000000
>> +
>>   !ifdef $(SOURCE_DEBUG_ENABLE)
>>     gEfiSourceLevelDebugPkgTokenSpaceGuid.PcdDebugLoadImageMethod|0x2
>>   !endif
>> diff --git a/OvmfPkg/OvmfPkgIa32X64.dsc b/OvmfPkg/OvmfPkgIa32X64.dsc
>> index 8d6271824f89..c9eeace4a134 100644
>> --- a/OvmfPkg/OvmfPkgIa32X64.dsc
>> +++ b/OvmfPkg/OvmfPkgIa32X64.dsc
>> @@ -400,6 +400,14 @@ [PcdsFixedAtBuild]
>>     gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x2F
>>   !endif
>>
>> +  # This PCD is used to set the base address of the PCI express
>> hierarchy. It
>> +  # is only consulted when OVMF runs on Q35. In that case it is
>> programmed into
>> +  # the PCIEXBAR register.
>> +  #
>> +  # The value we set below matches both the QEMU default, and the
>> value that
>> +  # SeaBIOS hard-codes.
>> +  gEfiMdePkgTokenSpaceGuid.PcdPciExpressBaseAddress|0xB0000000
>> +
>>   !ifdef $(SOURCE_DEBUG_ENABLE)
>>     gEfiSourceLevelDebugPkgTokenSpaceGuid.PcdDebugLoadImageMethod|0x2
>>   !endif
>> diff --git a/OvmfPkg/OvmfPkgX64.dsc b/OvmfPkg/OvmfPkgX64.dsc
>> index e3f97f16f28b..b6f4b252ff03 100644
>> --- a/OvmfPkg/OvmfPkgX64.dsc
>> +++ b/OvmfPkg/OvmfPkgX64.dsc
>> @@ -400,6 +400,14 @@ [PcdsFixedAtBuild]
>>     gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x2F
>>   !endif
>>
>> +  # This PCD is used to set the base address of the PCI express
>> hierarchy. It
>> +  # is only consulted when OVMF runs on Q35. In that case it is
>> programmed into
>> +  # the PCIEXBAR register.
>> +  #
>> +  # The value we set below matches both the QEMU default, and the
>> value that
>> +  # SeaBIOS hard-codes.
>> +  gEfiMdePkgTokenSpaceGuid.PcdPciExpressBaseAddress|0xB0000000
>> +
>>   !ifdef $(SOURCE_DEBUG_ENABLE)
>>     gEfiSourceLevelDebugPkgTokenSpaceGuid.PcdDebugLoadImageMethod|0x2
>>   !endif
>> diff --git a/OvmfPkg/PlatformPei/PlatformPei.inf
>> b/OvmfPkg/PlatformPei/PlatformPei.inf
>> index 8480839efc8e..6dc5ff079f53 100644
>> --- a/OvmfPkg/PlatformPei/PlatformPei.inf
>> +++ b/OvmfPkg/PlatformPei/PlatformPei.inf
>> @@ -94,6 +94,9 @@ [Pcd]
>>     gEfiMdeModulePkgTokenSpaceGuid.PcdPropertiesTableEnable
>>     gUefiCpuPkgTokenSpaceGuid.PcdCpuLocalApicBaseAddress
>>
>> +[FixedPcd]
>> +  gEfiMdePkgTokenSpaceGuid.PcdPciExpressBaseAddress
>> +
>>   [FeaturePcd]
>>     gUefiOvmfPkgTokenSpaceGuid.PcdSmmSmramRequire
>>
>> diff --git a/OvmfPkg/PlatformPei/Platform.c
>> b/OvmfPkg/PlatformPei/Platform.c
>> index 7d0941209f25..f0a488bcfc2b 100644
>> --- a/OvmfPkg/PlatformPei/Platform.c
>> +++ b/OvmfPkg/PlatformPei/Platform.c
>> @@ -214,6 +214,7 @@ MemMapInitialization (
>>       UINT32  TopOfLowRam;
>>       UINT32  PciBase;
>>       UINT32  PciSize;
>> +    UINT64  PciExBarBase;
>>
>>       TopOfLowRam = GetSystemMemorySizeBelow4gb ();
>>       if (mHostBridgeDevId == INTEL_Q35_MCH_DEVICE_ID) {
>> @@ -224,6 +225,16 @@ MemMapInitialization (
>>         //
>>         PciBase = BASE_2GB + BASE_1GB;
>>         ASSERT (TopOfLowRam <= PciBase);
>> +
>> +      //
>> +      // The (exclusive) end of the MMCONFIG area should be
>> expressible as a
>> +      // UINT64. Additionally, the MMCONFIG area is expected to fall
>> between
>> +      // the top of low RAM and the base of the 32-bit PCI host
>> aperture.
>> +      //
>> +      PciExBarBase = FixedPcdGet64 (PcdPciExpressBaseAddress);
>> +      ASSERT (PciExBarBase <= MAX_UINT64 - SIZE_256MB);
>> +      ASSERT (TopOfLowRam <= PciExBarBase);
>> +      ASSERT (PciExBarBase + SIZE_256MB <= PciBase);
>>       } else {
>>         PciBase = (TopOfLowRam < BASE_2GB) ? BASE_2GB : TopOfLowRam;
>>       }
>> @@ -249,6 +260,30 @@ MemMapInitialization (
>>       AddIoMemoryBaseSizeHob (0xFED00000, SIZE_1KB);
>>       if (mHostBridgeDevId == INTEL_Q35_MCH_DEVICE_ID) {
>>         AddIoMemoryBaseSizeHob (ICH9_ROOT_COMPLEX_BASE, SIZE_16KB);
>> +      //
>> +      // Note: there should be an
>> +      //
>> +      //   AddIoMemoryBaseSizeHob (PciExBarBase, SIZE_256MB);
>> +      //
>> +      // call below, just like the one above for RCBA. However, Linux
>> insists
>> +      // that the MMCONFIG area be marked in the E820 or UEFI memory
>> map as
>> +      // "reserved memory" -- Linux does not content itself with a
>> simple gap
>> +      // in the memory map wherever the MCFG ACPI table points to.
>> +      //
>> +      // This appears to be a safety measure. The PCI Firmware
>> Specification
>> +      // (rev 3.1) says in 4.1.2. "MCFG Table Description": "The
>> resources can
>> +      // *optionally* be returned in [...] EFIGetMemoryMap as
>> reserved memory
>> +      // [...]". (Emphasis added here.)
>> +      //
>> +      // Normally we add memory resource descriptor HOBs in
>> +      // QemuInitializeRam(), and pre-allocate from those with memory
>> +      // allocation HOBs in InitializeRamRegions(). However, the
>> MMCONFIG area
>> +      // is most definitely not RAM; so, as an exception, cover it with
>> +      // uncacheable reserved memory right here.
>> +      //
>> +      AddReservedMemoryBaseSizeHob (PciExBarBase, SIZE_256MB, FALSE);
>> +      BuildMemoryAllocationHob (PciExBarBase, SIZE_256MB,
>> +        EfiReservedMemoryType);
>>       }
>>       AddIoMemoryBaseSizeHob (PcdGet32(PcdCpuLocalApicBaseAddress),
>> SIZE_1MB);
>>     }
>> @@ -318,6 +353,47 @@ NoexecDxeInitialization (
>>   }
>>
>>   VOID
>> +PciExBarInitialization (
>> +  VOID
>> +  )
>> +{
>> +  union {
>> +    UINT64 Uint64;
>> +    UINT32 Uint32[2];
>> +  } PciExBarBase;
>> +
>> +  //
>> +  // We only support the 256MB size for the MMCONFIG area:
>> +  // 256 buses * 32 devices * 8 functions * 4096 bytes config space.
>> +  //
>> +  // The masks used below enforce the Q35 requirements that the
>> MMCONFIG area
>> +  // be (a) correctly aligned -- here at 256 MB --, (b) located under
>> 64 GB.
>> +  //
>> +  // Note that (b) also ensures that the minimum address width we have
>> +  // determined in AddressWidthInitialization(), i.e., 36 bits, will
>> suffice
>> +  // for DXE's page tables to cover the MMCONFIG area.
>> +  //
>> +  PciExBarBase.Uint64 = FixedPcdGet64 (PcdPciExpressBaseAddress);
>> +  ASSERT ((PciExBarBase.Uint32[1] & MCH_PCIEXBAR_HIGHMASK) == 0);
>> +  ASSERT ((PciExBarBase.Uint32[0] & MCH_PCIEXBAR_LOWMASK) == 0);
>> +
>> +  //
>> +  // Clear the PCIEXBAREN bit first, before programming the high
>> register.
>> +  //
>> +  PciWrite32 (DRAMC_REGISTER_Q35 (MCH_PCIEXBAR_LOW), 0);
>> +
>> +  //
>> +  // Program the high register. Then program the low register,
>> setting the
>> +  // MMCONFIG area size and enabling decoding at once.
>> +  //
>> +  PciWrite32 (DRAMC_REGISTER_Q35 (MCH_PCIEXBAR_HIGH),
>> PciExBarBase.Uint32[1]);
>> +  PciWrite32 (
>> +    DRAMC_REGISTER_Q35 (MCH_PCIEXBAR_LOW),
>> +    PciExBarBase.Uint32[0] | MCH_PCIEXBAR_BUS_FF | MCH_PCIEXBAR_EN
>> +    );
>> +}
>> +
>> +VOID
>>   MiscInitialization (
>>     VOID
>>     )
>> @@ -394,6 +470,11 @@ MiscInitialization (
>>         POWER_MGMT_REGISTER_Q35 (ICH9_RCBA),
>>         ICH9_ROOT_COMPLEX_BASE | ICH9_RCBA_EN
>>         );
>> +
>> +    //
>> +    // Set PCI Express Register Range Base Address
>> +    //
>> +    PciExBarInitialization ();
>>     }
>>   }
>>
>>
> 
> Hi,
> 
> The PCI config accesses to PCIEXBAR are correct.
> 
> Reviewed-by: Marcel Apfelbaum <marcel@redhat.com>
> 
> Thanks,
> Marcel

Thanks, Marcel!
Laszlo
Laszlo Ersek March 7, 2016, 9:20 a.m. UTC | #2
Thank you for this message! Some comments below:

On 03/07/16 09:47, Gerd Hoffmann wrote:
>   Hi,

> 

>>   PCI: MMCONFIG at [mem 0xb0000000-0xbfffffff] reserved in E820

> 

> Ok, I see you mimic seabios behavior here, resulting in a somewhat odd

> memory layout (not your fault, of course).

> 

> Short history lesson:

> 

> Traditional i440fx memory layout (before we did gigabyte alignment) was

> memory up to 0xe0000000, pci io space above that.  Which caused problems

> now and then, because it isn't that much address space for pci.

> 

> So, when figuring how to do things on q35 I wanted more space.  Decided

> to go down to 0xc0000000, giving additional 512m address space for pci.

> But we also have to place the xbar somewhere.  The seabios code to do

> pci bar placement is somewhat simple and can deal with a single (32bit)

> I/O window only.  Decided to place the xbar @ 0xb0000000, so seabios has

> a single region above 0xc0000000 for the I/O bars.

> 

> So, this is where the traditional q35 memory layout with memory up to

> 0xafffffff comes from.  These days it doesn't look that way any more

> because we added gigabyte alignment meanwhile, so it's this now:

> 

>    low memory  0x00000000 -> 0x7fffffff.  

>    free space  0x80000000 -> 0xafffffff,

>    mmcfg xbar  0xb0000000 -> 0xbfffffff,

>    free space  0xc0000000 -> ioapic-base

> 

> i.e. the xbar splits the free space in a somewhat unclever way.

> 

> The good news is that xbar location is not fixed.  qemu is supposed to

> check now the firmware programmed the q35 host bridge and fill the acpi

> tables accordingly, so you should be able to move the xbar to another

> location.

> 

> I think there are two reasonable choices: Either 0x80000000 or

> 0xe0000000.

> 

> Using 0x80000000 maintains a single I/O window. (above 0x90000000).

> Gives up backward compatibility to really old (pre gigabyte alignment)

> q35 machines types.  But we are about to drop support for them in

> upstream qemu anyway, so I'll probably switch seabios to do that.

> 

> Using 0xe0000000 still splits the I/O address space into two pieces, but

> I don't think the edk2 resource management code has problems dealing

> with that.


The current abstraction we're using with the new core PCI host bridge
driver allows for a single contiguous range as MMIO32 aperture. (And, in
OVMF's case, that range is shared between the root bridges, if there are
several of them -- but I digress.) The point is, we should indeed come
up with the best split.

>   It places the xbar at the highest possible address, so we

> get a big I/O window below the xbar (and a small one above).  The main

> advantage over using 0xb0000000 is that you can better fit big bars

> then, for example you can map even a 1G bar (at 0x8000000).


Okay, my vote is 0x8000_0000 (2GB) then.

I will modify the patch as follows: simultaneously with programming the
exbar at 2GB, I will lower the start of the 32-bit PCI aperture from the
current 3GB (0xC000_0000) to (2GB+256MB == 0x9000_0000). Is that alright?

Thanks
Laszlo
_______________________________________________
edk2-devel mailing list
edk2-devel@lists.01.org
https://lists.01.org/mailman/listinfo/edk2-devel
diff mbox

Patch

diff --git a/OvmfPkg/OvmfPkgIa32.dsc b/OvmfPkg/OvmfPkgIa32.dsc
index 2b928506e481..d1f91172044e 100644
--- a/OvmfPkg/OvmfPkgIa32.dsc
+++ b/OvmfPkg/OvmfPkgIa32.dsc
@@ -395,6 +395,14 @@  [PcdsFixedAtBuild]
   gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x2F
 !endif
 
+  # This PCD is used to set the base address of the PCI express hierarchy. It
+  # is only consulted when OVMF runs on Q35. In that case it is programmed into
+  # the PCIEXBAR register.
+  #
+  # The value we set below matches both the QEMU default, and the value that
+  # SeaBIOS hard-codes.
+  gEfiMdePkgTokenSpaceGuid.PcdPciExpressBaseAddress|0xB0000000
+
 !ifdef $(SOURCE_DEBUG_ENABLE)
   gEfiSourceLevelDebugPkgTokenSpaceGuid.PcdDebugLoadImageMethod|0x2
 !endif
diff --git a/OvmfPkg/OvmfPkgIa32X64.dsc b/OvmfPkg/OvmfPkgIa32X64.dsc
index 8d6271824f89..c9eeace4a134 100644
--- a/OvmfPkg/OvmfPkgIa32X64.dsc
+++ b/OvmfPkg/OvmfPkgIa32X64.dsc
@@ -400,6 +400,14 @@  [PcdsFixedAtBuild]
   gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x2F
 !endif
 
+  # This PCD is used to set the base address of the PCI express hierarchy. It
+  # is only consulted when OVMF runs on Q35. In that case it is programmed into
+  # the PCIEXBAR register.
+  #
+  # The value we set below matches both the QEMU default, and the value that
+  # SeaBIOS hard-codes.
+  gEfiMdePkgTokenSpaceGuid.PcdPciExpressBaseAddress|0xB0000000
+
 !ifdef $(SOURCE_DEBUG_ENABLE)
   gEfiSourceLevelDebugPkgTokenSpaceGuid.PcdDebugLoadImageMethod|0x2
 !endif
diff --git a/OvmfPkg/OvmfPkgX64.dsc b/OvmfPkg/OvmfPkgX64.dsc
index e3f97f16f28b..b6f4b252ff03 100644
--- a/OvmfPkg/OvmfPkgX64.dsc
+++ b/OvmfPkg/OvmfPkgX64.dsc
@@ -400,6 +400,14 @@  [PcdsFixedAtBuild]
   gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0x2F
 !endif
 
+  # This PCD is used to set the base address of the PCI express hierarchy. It
+  # is only consulted when OVMF runs on Q35. In that case it is programmed into
+  # the PCIEXBAR register.
+  #
+  # The value we set below matches both the QEMU default, and the value that
+  # SeaBIOS hard-codes.
+  gEfiMdePkgTokenSpaceGuid.PcdPciExpressBaseAddress|0xB0000000
+
 !ifdef $(SOURCE_DEBUG_ENABLE)
   gEfiSourceLevelDebugPkgTokenSpaceGuid.PcdDebugLoadImageMethod|0x2
 !endif
diff --git a/OvmfPkg/PlatformPei/PlatformPei.inf b/OvmfPkg/PlatformPei/PlatformPei.inf
index 8480839efc8e..6dc5ff079f53 100644
--- a/OvmfPkg/PlatformPei/PlatformPei.inf
+++ b/OvmfPkg/PlatformPei/PlatformPei.inf
@@ -94,6 +94,9 @@  [Pcd]
   gEfiMdeModulePkgTokenSpaceGuid.PcdPropertiesTableEnable
   gUefiCpuPkgTokenSpaceGuid.PcdCpuLocalApicBaseAddress
 
+[FixedPcd]
+  gEfiMdePkgTokenSpaceGuid.PcdPciExpressBaseAddress
+
 [FeaturePcd]
   gUefiOvmfPkgTokenSpaceGuid.PcdSmmSmramRequire
 
diff --git a/OvmfPkg/PlatformPei/Platform.c b/OvmfPkg/PlatformPei/Platform.c
index 7d0941209f25..f0a488bcfc2b 100644
--- a/OvmfPkg/PlatformPei/Platform.c
+++ b/OvmfPkg/PlatformPei/Platform.c
@@ -214,6 +214,7 @@  MemMapInitialization (
     UINT32  TopOfLowRam;
     UINT32  PciBase;
     UINT32  PciSize;
+    UINT64  PciExBarBase;
 
     TopOfLowRam = GetSystemMemorySizeBelow4gb ();
     if (mHostBridgeDevId == INTEL_Q35_MCH_DEVICE_ID) {
@@ -224,6 +225,16 @@  MemMapInitialization (
       //
       PciBase = BASE_2GB + BASE_1GB;
       ASSERT (TopOfLowRam <= PciBase);
+
+      //
+      // The (exclusive) end of the MMCONFIG area should be expressible as a
+      // UINT64. Additionally, the MMCONFIG area is expected to fall between
+      // the top of low RAM and the base of the 32-bit PCI host aperture.
+      //
+      PciExBarBase = FixedPcdGet64 (PcdPciExpressBaseAddress);
+      ASSERT (PciExBarBase <= MAX_UINT64 - SIZE_256MB);
+      ASSERT (TopOfLowRam <= PciExBarBase);
+      ASSERT (PciExBarBase + SIZE_256MB <= PciBase);
     } else {
       PciBase = (TopOfLowRam < BASE_2GB) ? BASE_2GB : TopOfLowRam;
     }
@@ -249,6 +260,30 @@  MemMapInitialization (
     AddIoMemoryBaseSizeHob (0xFED00000, SIZE_1KB);
     if (mHostBridgeDevId == INTEL_Q35_MCH_DEVICE_ID) {
       AddIoMemoryBaseSizeHob (ICH9_ROOT_COMPLEX_BASE, SIZE_16KB);
+      //
+      // Note: there should be an
+      //
+      //   AddIoMemoryBaseSizeHob (PciExBarBase, SIZE_256MB);
+      //
+      // call below, just like the one above for RCBA. However, Linux insists
+      // that the MMCONFIG area be marked in the E820 or UEFI memory map as
+      // "reserved memory" -- Linux does not content itself with a simple gap
+      // in the memory map wherever the MCFG ACPI table points to.
+      //
+      // This appears to be a safety measure. The PCI Firmware Specification
+      // (rev 3.1) says in 4.1.2. "MCFG Table Description": "The resources can
+      // *optionally* be returned in [...] EFIGetMemoryMap as reserved memory
+      // [...]". (Emphasis added here.)
+      //
+      // Normally we add memory resource descriptor HOBs in
+      // QemuInitializeRam(), and pre-allocate from those with memory
+      // allocation HOBs in InitializeRamRegions(). However, the MMCONFIG area
+      // is most definitely not RAM; so, as an exception, cover it with
+      // uncacheable reserved memory right here.
+      //
+      AddReservedMemoryBaseSizeHob (PciExBarBase, SIZE_256MB, FALSE);
+      BuildMemoryAllocationHob (PciExBarBase, SIZE_256MB,
+        EfiReservedMemoryType);
     }
     AddIoMemoryBaseSizeHob (PcdGet32(PcdCpuLocalApicBaseAddress), SIZE_1MB);
   }
@@ -318,6 +353,47 @@  NoexecDxeInitialization (
 }
 
 VOID
+PciExBarInitialization (
+  VOID
+  )
+{
+  union {
+    UINT64 Uint64;
+    UINT32 Uint32[2];
+  } PciExBarBase;
+
+  //
+  // We only support the 256MB size for the MMCONFIG area:
+  // 256 buses * 32 devices * 8 functions * 4096 bytes config space.
+  //
+  // The masks used below enforce the Q35 requirements that the MMCONFIG area
+  // be (a) correctly aligned -- here at 256 MB --, (b) located under 64 GB.
+  //
+  // Note that (b) also ensures that the minimum address width we have
+  // determined in AddressWidthInitialization(), i.e., 36 bits, will suffice
+  // for DXE's page tables to cover the MMCONFIG area.
+  //
+  PciExBarBase.Uint64 = FixedPcdGet64 (PcdPciExpressBaseAddress);
+  ASSERT ((PciExBarBase.Uint32[1] & MCH_PCIEXBAR_HIGHMASK) == 0);
+  ASSERT ((PciExBarBase.Uint32[0] & MCH_PCIEXBAR_LOWMASK) == 0);
+
+  //
+  // Clear the PCIEXBAREN bit first, before programming the high register.
+  //
+  PciWrite32 (DRAMC_REGISTER_Q35 (MCH_PCIEXBAR_LOW), 0);
+
+  //
+  // Program the high register. Then program the low register, setting the
+  // MMCONFIG area size and enabling decoding at once.
+  //
+  PciWrite32 (DRAMC_REGISTER_Q35 (MCH_PCIEXBAR_HIGH), PciExBarBase.Uint32[1]);
+  PciWrite32 (
+    DRAMC_REGISTER_Q35 (MCH_PCIEXBAR_LOW),
+    PciExBarBase.Uint32[0] | MCH_PCIEXBAR_BUS_FF | MCH_PCIEXBAR_EN
+    );
+}
+
+VOID
 MiscInitialization (
   VOID
   )
@@ -394,6 +470,11 @@  MiscInitialization (
       POWER_MGMT_REGISTER_Q35 (ICH9_RCBA),
       ICH9_ROOT_COMPLEX_BASE | ICH9_RCBA_EN
       );
+
+    //
+    // Set PCI Express Register Range Base Address
+    //
+    PciExBarInitialization ();
   }
 }