]> git.proxmox.com Git - mirror_edk2.git/blobdiff - OvmfPkg/PlatformPei/MemDetect.c
OvmfPkg/PlatformPei: debug log "etc/reserved-memory-end" from fw_cfg
[mirror_edk2.git] / OvmfPkg / PlatformPei / MemDetect.c
index 455fcbb49d13a590b7a118e3a7fbcf3efed00f9a..2f9e835513649457e2746015a7042cb161d34728 100644 (file)
@@ -1,7 +1,7 @@
 /**@file\r
   Memory Detection for Virtual Machines.\r
 \r
-  Copyright (c) 2006 - 2014, Intel Corporation. All rights reserved.<BR>\r
+  Copyright (c) 2006 - 2016, Intel Corporation. All rights reserved.<BR>\r
   This program and the accompanying materials\r
   are licensed and made available under the terms and conditions of the BSD License\r
   which accompanies this distribution.  The full text of the license may be found at\r
@@ -19,25 +19,194 @@ Module Name:
 //\r
 // The package level header files this module uses\r
 //\r
+#include <IndustryStandard/E820.h>\r
+#include <IndustryStandard/Q35MchIch9.h>\r
 #include <PiPei.h>\r
 \r
 //\r
 // The Library classes this module consumes\r
 //\r
+#include <Library/BaseLib.h>\r
 #include <Library/BaseMemoryLib.h>\r
 #include <Library/DebugLib.h>\r
 #include <Library/HobLib.h>\r
 #include <Library/IoLib.h>\r
 #include <Library/PcdLib.h>\r
+#include <Library/PciLib.h>\r
 #include <Library/PeimEntryPoint.h>\r
 #include <Library/ResourcePublicationLib.h>\r
 #include <Library/MtrrLib.h>\r
+#include <Library/QemuFwCfgLib.h>\r
 \r
 #include "Platform.h"\r
 #include "Cmos.h"\r
 \r
 UINT8 mPhysMemAddressWidth;\r
 \r
+STATIC UINT32 mS3AcpiReservedMemoryBase;\r
+STATIC UINT32 mS3AcpiReservedMemorySize;\r
+\r
+STATIC UINT16 mQ35TsegMbytes;\r
+\r
+VOID\r
+Q35TsegMbytesInitialization (\r
+  VOID\r
+  )\r
+{\r
+  UINT16        ExtendedTsegMbytes;\r
+  RETURN_STATUS PcdStatus;\r
+\r
+  if (mHostBridgeDevId != INTEL_Q35_MCH_DEVICE_ID) {\r
+    DEBUG ((\r
+      DEBUG_ERROR,\r
+      "%a: no TSEG (SMRAM) on host bridge DID=0x%04x; "\r
+      "only DID=0x%04x (Q35) is supported\n",\r
+      __FUNCTION__,\r
+      mHostBridgeDevId,\r
+      INTEL_Q35_MCH_DEVICE_ID\r
+      ));\r
+    ASSERT (FALSE);\r
+    CpuDeadLoop ();\r
+  }\r
+\r
+  //\r
+  // Check if QEMU offers an extended TSEG.\r
+  //\r
+  // This can be seen from writing MCH_EXT_TSEG_MB_QUERY to the MCH_EXT_TSEG_MB\r
+  // register, and reading back the register.\r
+  //\r
+  // On a QEMU machine type that does not offer an extended TSEG, the initial\r
+  // write overwrites whatever value a malicious guest OS may have placed in\r
+  // the (unimplemented) register, before entering S3 or rebooting.\r
+  // Subsequently, the read returns MCH_EXT_TSEG_MB_QUERY unchanged.\r
+  //\r
+  // On a QEMU machine type that offers an extended TSEG, the initial write\r
+  // triggers an update to the register. Subsequently, the value read back\r
+  // (which is guaranteed to differ from MCH_EXT_TSEG_MB_QUERY) tells us the\r
+  // number of megabytes.\r
+  //\r
+  PciWrite16 (DRAMC_REGISTER_Q35 (MCH_EXT_TSEG_MB), MCH_EXT_TSEG_MB_QUERY);\r
+  ExtendedTsegMbytes = PciRead16 (DRAMC_REGISTER_Q35 (MCH_EXT_TSEG_MB));\r
+  if (ExtendedTsegMbytes == MCH_EXT_TSEG_MB_QUERY) {\r
+    mQ35TsegMbytes = PcdGet16 (PcdQ35TsegMbytes);\r
+    return;\r
+  }\r
+\r
+  DEBUG ((\r
+    DEBUG_INFO,\r
+    "%a: QEMU offers an extended TSEG (%d MB)\n",\r
+    __FUNCTION__,\r
+    ExtendedTsegMbytes\r
+    ));\r
+  PcdStatus = PcdSet16S (PcdQ35TsegMbytes, ExtendedTsegMbytes);\r
+  ASSERT_RETURN_ERROR (PcdStatus);\r
+  mQ35TsegMbytes = ExtendedTsegMbytes;\r
+}\r
+\r
+\r
+/**\r
+  Iterate over the RAM entries in QEMU's fw_cfg E820 RAM map that start outside\r
+  of the 32-bit address range.\r
+\r
+  Find the highest exclusive >=4GB RAM address, or produce memory resource\r
+  descriptor HOBs for RAM entries that start at or above 4GB.\r
+\r
+  @param[out] MaxAddress  If MaxAddress is NULL, then ScanOrAdd64BitE820Ram()\r
+                          produces memory resource descriptor HOBs for RAM\r
+                          entries that start at or above 4GB.\r
+\r
+                          Otherwise, MaxAddress holds the highest exclusive\r
+                          >=4GB RAM address on output. If QEMU's fw_cfg E820\r
+                          RAM map contains no RAM entry that starts outside of\r
+                          the 32-bit address range, then MaxAddress is exactly\r
+                          4GB on output.\r
+\r
+  @retval EFI_SUCCESS         The fw_cfg E820 RAM map was found and processed.\r
+\r
+  @retval EFI_PROTOCOL_ERROR  The RAM map was found, but its size wasn't a\r
+                              whole multiple of sizeof(EFI_E820_ENTRY64). No\r
+                              RAM entry was processed.\r
+\r
+  @return                     Error codes from QemuFwCfgFindFile(). No RAM\r
+                              entry was processed.\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+ScanOrAdd64BitE820Ram (\r
+  OUT UINT64 *MaxAddress OPTIONAL\r
+  )\r
+{\r
+  EFI_STATUS           Status;\r
+  FIRMWARE_CONFIG_ITEM FwCfgItem;\r
+  UINTN                FwCfgSize;\r
+  EFI_E820_ENTRY64     E820Entry;\r
+  UINTN                Processed;\r
+\r
+  Status = QemuFwCfgFindFile ("etc/e820", &FwCfgItem, &FwCfgSize);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+  if (FwCfgSize % sizeof E820Entry != 0) {\r
+    return EFI_PROTOCOL_ERROR;\r
+  }\r
+\r
+  if (MaxAddress != NULL) {\r
+    *MaxAddress = BASE_4GB;\r
+  }\r
+\r
+  QemuFwCfgSelectItem (FwCfgItem);\r
+  for (Processed = 0; Processed < FwCfgSize; Processed += sizeof E820Entry) {\r
+    QemuFwCfgReadBytes (sizeof E820Entry, &E820Entry);\r
+    DEBUG ((\r
+      DEBUG_VERBOSE,\r
+      "%a: Base=0x%Lx Length=0x%Lx Type=%u\n",\r
+      __FUNCTION__,\r
+      E820Entry.BaseAddr,\r
+      E820Entry.Length,\r
+      E820Entry.Type\r
+      ));\r
+    if (E820Entry.Type == EfiAcpiAddressRangeMemory &&\r
+        E820Entry.BaseAddr >= BASE_4GB) {\r
+      if (MaxAddress == NULL) {\r
+        UINT64 Base;\r
+        UINT64 End;\r
+\r
+        //\r
+        // Round up the start address, and round down the end address.\r
+        //\r
+        Base = ALIGN_VALUE (E820Entry.BaseAddr, (UINT64)EFI_PAGE_SIZE);\r
+        End = (E820Entry.BaseAddr + E820Entry.Length) &\r
+              ~(UINT64)EFI_PAGE_MASK;\r
+        if (Base < End) {\r
+          AddMemoryRangeHob (Base, End);\r
+          DEBUG ((\r
+            DEBUG_VERBOSE,\r
+            "%a: AddMemoryRangeHob [0x%Lx, 0x%Lx)\n",\r
+            __FUNCTION__,\r
+            Base,\r
+            End\r
+            ));\r
+        }\r
+      } else {\r
+        UINT64 Candidate;\r
+\r
+        Candidate = E820Entry.BaseAddr + E820Entry.Length;\r
+        if (Candidate > *MaxAddress) {\r
+          *MaxAddress = Candidate;\r
+          DEBUG ((\r
+            DEBUG_VERBOSE,\r
+            "%a: MaxAddress=0x%Lx\n",\r
+            __FUNCTION__,\r
+            *MaxAddress\r
+            ));\r
+        }\r
+      }\r
+    }\r
+  }\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+\r
 UINT32\r
 GetSystemMemorySizeBelow4gb (\r
   VOID\r
@@ -87,6 +256,153 @@ GetSystemMemorySizeAbove4gb (
 }\r
 \r
 \r
+/**\r
+  Return the highest address that DXE could possibly use, plus one.\r
+**/\r
+STATIC\r
+UINT64\r
+GetFirstNonAddress (\r
+  VOID\r
+  )\r
+{\r
+  UINT64               FirstNonAddress;\r
+  UINT64               Pci64Base, Pci64Size;\r
+  CHAR8                MbString[7 + 1];\r
+  EFI_STATUS           Status;\r
+  FIRMWARE_CONFIG_ITEM FwCfgItem;\r
+  UINTN                FwCfgSize;\r
+  UINT64               HotPlugMemoryEnd;\r
+  RETURN_STATUS        PcdStatus;\r
+\r
+  //\r
+  // set FirstNonAddress to suppress incorrect compiler/analyzer warnings\r
+  //\r
+  FirstNonAddress = 0;\r
+\r
+  //\r
+  // If QEMU presents an E820 map, then get the highest exclusive >=4GB RAM\r
+  // address from it. This can express an address >= 4GB+1TB.\r
+  //\r
+  // Otherwise, get the flat size of the memory above 4GB from the CMOS (which\r
+  // can only express a size smaller than 1TB), and add it to 4GB.\r
+  //\r
+  Status = ScanOrAdd64BitE820Ram (&FirstNonAddress);\r
+  if (EFI_ERROR (Status)) {\r
+    FirstNonAddress = BASE_4GB + GetSystemMemorySizeAbove4gb ();\r
+  }\r
+\r
+  //\r
+  // If DXE is 32-bit, then we're done; PciBusDxe will degrade 64-bit MMIO\r
+  // resources to 32-bit anyway. See DegradeResource() in\r
+  // "PciResourceSupport.c".\r
+  //\r
+#ifdef MDE_CPU_IA32\r
+  if (!FeaturePcdGet (PcdDxeIplSwitchToLongMode)) {\r
+    return FirstNonAddress;\r
+  }\r
+#endif\r
+\r
+  //\r
+  // Otherwise, in order to calculate the highest address plus one, we must\r
+  // consider the 64-bit PCI host aperture too. Fetch the default size.\r
+  //\r
+  Pci64Size = PcdGet64 (PcdPciMmio64Size);\r
+\r
+  //\r
+  // See if the user specified the number of megabytes for the 64-bit PCI host\r
+  // aperture. The number of non-NUL characters in MbString allows for\r
+  // 9,999,999 MB, which is approximately 10 TB.\r
+  //\r
+  // As signaled by the "X-" prefix, this knob is experimental, and might go\r
+  // away at any time.\r
+  //\r
+  Status = QemuFwCfgFindFile ("opt/ovmf/X-PciMmio64Mb", &FwCfgItem,\r
+             &FwCfgSize);\r
+  if (!EFI_ERROR (Status)) {\r
+    if (FwCfgSize >= sizeof MbString) {\r
+      DEBUG ((EFI_D_WARN,\r
+        "%a: ignoring malformed 64-bit PCI host aperture size from fw_cfg\n",\r
+        __FUNCTION__));\r
+    } else {\r
+      QemuFwCfgSelectItem (FwCfgItem);\r
+      QemuFwCfgReadBytes (FwCfgSize, MbString);\r
+      MbString[FwCfgSize] = '\0';\r
+      Pci64Size = LShiftU64 (AsciiStrDecimalToUint64 (MbString), 20);\r
+    }\r
+  }\r
+\r
+  if (Pci64Size == 0) {\r
+    if (mBootMode != BOOT_ON_S3_RESUME) {\r
+      DEBUG ((EFI_D_INFO, "%a: disabling 64-bit PCI host aperture\n",\r
+        __FUNCTION__));\r
+      PcdStatus = PcdSet64S (PcdPciMmio64Size, 0);\r
+      ASSERT_RETURN_ERROR (PcdStatus);\r
+    }\r
+\r
+    //\r
+    // There's nothing more to do; the amount of memory above 4GB fully\r
+    // determines the highest address plus one. The memory hotplug area (see\r
+    // below) plays no role for the firmware in this case.\r
+    //\r
+    return FirstNonAddress;\r
+  }\r
+\r
+  //\r
+  // The "etc/reserved-memory-end" fw_cfg file, when present, contains an\r
+  // absolute, exclusive end address for the memory hotplug area. This area\r
+  // starts right at the end of the memory above 4GB. The 64-bit PCI host\r
+  // aperture must be placed above it.\r
+  //\r
+  Status = QemuFwCfgFindFile ("etc/reserved-memory-end", &FwCfgItem,\r
+             &FwCfgSize);\r
+  if (!EFI_ERROR (Status) && FwCfgSize == sizeof HotPlugMemoryEnd) {\r
+    QemuFwCfgSelectItem (FwCfgItem);\r
+    QemuFwCfgReadBytes (FwCfgSize, &HotPlugMemoryEnd);\r
+    DEBUG ((DEBUG_VERBOSE, "%a: HotPlugMemoryEnd=0x%Lx\n", __FUNCTION__,\r
+      HotPlugMemoryEnd));\r
+\r
+    ASSERT (HotPlugMemoryEnd >= FirstNonAddress);\r
+    FirstNonAddress = HotPlugMemoryEnd;\r
+  }\r
+\r
+  //\r
+  // SeaBIOS aligns both boundaries of the 64-bit PCI host aperture to 1GB, so\r
+  // that the host can map it with 1GB hugepages. Follow suit.\r
+  //\r
+  Pci64Base = ALIGN_VALUE (FirstNonAddress, (UINT64)SIZE_1GB);\r
+  Pci64Size = ALIGN_VALUE (Pci64Size, (UINT64)SIZE_1GB);\r
+\r
+  //\r
+  // The 64-bit PCI host aperture should also be "naturally" aligned. The\r
+  // alignment is determined by rounding the size of the aperture down to the\r
+  // next smaller or equal power of two. That is, align the aperture by the\r
+  // largest BAR size that can fit into it.\r
+  //\r
+  Pci64Base = ALIGN_VALUE (Pci64Base, GetPowerOfTwo64 (Pci64Size));\r
+\r
+  if (mBootMode != BOOT_ON_S3_RESUME) {\r
+    //\r
+    // The core PciHostBridgeDxe driver will automatically add this range to\r
+    // the GCD memory space map through our PciHostBridgeLib instance; here we\r
+    // only need to set the PCDs.\r
+    //\r
+    PcdStatus = PcdSet64S (PcdPciMmio64Base, Pci64Base);\r
+    ASSERT_RETURN_ERROR (PcdStatus);\r
+    PcdStatus = PcdSet64S (PcdPciMmio64Size, Pci64Size);\r
+    ASSERT_RETURN_ERROR (PcdStatus);\r
+\r
+    DEBUG ((EFI_D_INFO, "%a: Pci64Base=0x%Lx Pci64Size=0x%Lx\n",\r
+      __FUNCTION__, Pci64Base, Pci64Size));\r
+  }\r
+\r
+  //\r
+  // The useful address space ends with the 64-bit PCI host aperture.\r
+  //\r
+  FirstNonAddress = Pci64Base + Pci64Size;\r
+  return FirstNonAddress;\r
+}\r
+\r
+\r
 /**\r
   Initialize the mPhysMemAddressWidth variable, based on guest RAM size.\r
 **/\r
@@ -103,7 +419,7 @@ AddressWidthInitialization (
   // The DXL IPL keys off of the physical address bits advertized in the CPU\r
   // HOB. To conserve memory, we calculate the minimum address width here.\r
   //\r
-  FirstNonAddress      = BASE_4GB + GetSystemMemorySizeAbove4gb ();\r
+  FirstNonAddress      = GetFirstNonAddress ();\r
   mPhysMemAddressWidth = (UINT8)HighBitSet64 (FirstNonAddress);\r
 \r
   //\r
@@ -206,21 +522,34 @@ PublishPeiMemory (
   EFI_STATUS                  Status;\r
   EFI_PHYSICAL_ADDRESS        MemoryBase;\r
   UINT64                      MemorySize;\r
-  UINT64                      LowerMemorySize;\r
+  UINT32                      LowerMemorySize;\r
   UINT32                      PeiMemoryCap;\r
 \r
+  LowerMemorySize = GetSystemMemorySizeBelow4gb ();\r
+  if (FeaturePcdGet (PcdSmmSmramRequire)) {\r
+    //\r
+    // TSEG is chipped from the end of low RAM\r
+    //\r
+    LowerMemorySize -= mQ35TsegMbytes * SIZE_1MB;\r
+  }\r
+\r
+  //\r
+  // If S3 is supported, then the S3 permanent PEI memory is placed next,\r
+  // downwards. Its size is primarily dictated by CpuMpPei. The formula below\r
+  // is an approximation.\r
+  //\r
+  if (mS3Supported) {\r
+    mS3AcpiReservedMemorySize = SIZE_512KB +\r
+      mMaxCpuCount *\r
+      PcdGet32 (PcdCpuApStackSize);\r
+    mS3AcpiReservedMemoryBase = LowerMemorySize - mS3AcpiReservedMemorySize;\r
+    LowerMemorySize = mS3AcpiReservedMemoryBase;\r
+  }\r
+\r
   if (mBootMode == BOOT_ON_S3_RESUME) {\r
-    MemoryBase = PcdGet32 (PcdS3AcpiReservedMemoryBase);\r
-    MemorySize = PcdGet32 (PcdS3AcpiReservedMemorySize);\r
+    MemoryBase = mS3AcpiReservedMemoryBase;\r
+    MemorySize = mS3AcpiReservedMemorySize;\r
   } else {\r
-    LowerMemorySize = GetSystemMemorySizeBelow4gb ();\r
-    if (FeaturePcdGet (PcdSmmSmramRequire)) {\r
-      //\r
-      // TSEG is chipped from the end of low RAM\r
-      //\r
-      LowerMemorySize -= FixedPcdGet8 (PcdQ35TsegMbytes) * SIZE_1MB;\r
-    }\r
-\r
     PeiMemoryCap = GetPeiMemoryCap ();\r
     DEBUG ((EFI_D_INFO, "%a: mPhysMemAddressWidth=%d PeiMemoryCap=%u KB\n",\r
       __FUNCTION__, mPhysMemAddressWidth, PeiMemoryCap >> 10));\r
@@ -278,7 +607,29 @@ QemuInitializeRam (
   LowerMemorySize = GetSystemMemorySizeBelow4gb ();\r
   UpperMemorySize = GetSystemMemorySizeAbove4gb ();\r
 \r
-  if (mBootMode != BOOT_ON_S3_RESUME) {\r
+  if (mBootMode == BOOT_ON_S3_RESUME) {\r
+    //\r
+    // Create the following memory HOB as an exception on the S3 boot path.\r
+    //\r
+    // Normally we'd create memory HOBs only on the normal boot path. However,\r
+    // CpuMpPei specifically needs such a low-memory HOB on the S3 path as\r
+    // well, for "borrowing" a subset of it temporarily, for the AP startup\r
+    // vector.\r
+    //\r
+    // CpuMpPei saves the original contents of the borrowed area in permanent\r
+    // PEI RAM, in a backup buffer allocated with the normal PEI services.\r
+    // CpuMpPei restores the original contents ("returns" the borrowed area) at\r
+    // End-of-PEI. End-of-PEI in turn is emitted by S3Resume2Pei before\r
+    // transferring control to the OS's wakeup vector in the FACS.\r
+    //\r
+    // We expect any other PEIMs that "borrow" memory similarly to CpuMpPei to\r
+    // restore the original contents. Furthermore, we expect all such PEIMs\r
+    // (CpuMpPei included) to claim the borrowed areas by producing memory\r
+    // allocation HOBs, and to honor preexistent memory allocation HOBs when\r
+    // looking for an area to borrow.\r
+    //\r
+    AddMemoryRangeHob (0, BASE_512KB + BASE_128KB);\r
+  } else {\r
     //\r
     // Create memory HOBs\r
     //\r
@@ -287,7 +638,7 @@ QemuInitializeRam (
     if (FeaturePcdGet (PcdSmmSmramRequire)) {\r
       UINT32 TsegSize;\r
 \r
-      TsegSize = FixedPcdGet8 (PcdQ35TsegMbytes) * SIZE_1MB;\r
+      TsegSize = mQ35TsegMbytes * SIZE_1MB;\r
       AddMemoryRangeHob (BASE_1MB, LowerMemorySize - TsegSize);\r
       AddReservedMemoryBaseSizeHob (LowerMemorySize - TsegSize, TsegSize,\r
         TRUE);\r
@@ -295,8 +646,14 @@ QemuInitializeRam (
       AddMemoryRangeHob (BASE_1MB, LowerMemorySize);\r
     }\r
 \r
-    if (UpperMemorySize != 0) {\r
-      AddUntestedMemoryBaseSizeHob (BASE_4GB, UpperMemorySize);\r
+    //\r
+    // If QEMU presents an E820 map, then create memory HOBs for the >=4GB RAM\r
+    // entries. Otherwise, create a single memory HOB with the flat >=4GB\r
+    // memory size read from the CMOS.\r
+    //\r
+    Status = ScanOrAdd64BitE820Ram (NULL);\r
+    if (EFI_ERROR (Status) && UpperMemorySize != 0) {\r
+      AddMemoryBaseSizeHob (BASE_4GB, UpperMemorySize);\r
     }\r
   }\r
 \r
@@ -366,8 +723,8 @@ InitializeRamRegions (
     // This is the memory range that will be used for PEI on S3 resume\r
     //\r
     BuildMemoryAllocationHob (\r
-      (EFI_PHYSICAL_ADDRESS)(UINTN) PcdGet32 (PcdS3AcpiReservedMemoryBase),\r
-      (UINT64)(UINTN) PcdGet32 (PcdS3AcpiReservedMemorySize),\r
+      mS3AcpiReservedMemoryBase,\r
+      mS3AcpiReservedMemorySize,\r
       EfiACPIMemoryNVS\r
       );\r
 \r
@@ -436,7 +793,7 @@ InitializeRamRegions (
       // Make sure the TSEG area that we reported as a reserved memory resource\r
       // cannot be used for reserved memory allocations.\r
       //\r
-      TsegSize = FixedPcdGet8 (PcdQ35TsegMbytes) * SIZE_1MB;\r
+      TsegSize = mQ35TsegMbytes * SIZE_1MB;\r
       BuildMemoryAllocationHob (\r
         GetSystemMemorySizeBelow4gb() - TsegSize,\r
         TsegSize,\r