/** @file | |
A PEIM with the following responsibilities: | |
- verify & configure the Q35 TSEG in the entry point, | |
- provide SMRAM access by producing PEI_SMM_ACCESS_PPI | |
This PEIM runs from RAM, so we can write to variables with static storage | |
duration. | |
Copyright (C) 2013, 2015, Red Hat, Inc.<BR> | |
Copyright (c) 2010 - 2024, Intel Corporation. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <Library/BaseLib.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/IoLib.h> | |
#include <Library/PcdLib.h> | |
#include <Library/PciLib.h> | |
#include <Library/PeiServicesLib.h> | |
#include <Ppi/SmmAccess.h> | |
#include <OvmfPlatforms.h> | |
#include "SmramInternal.h" | |
// | |
// PEI_SMM_ACCESS_PPI implementation. | |
// | |
/** | |
Opens the SMRAM area to be accessible by a PEIM driver. | |
This function "opens" SMRAM so that it is visible while not inside of SMM. | |
The function should return EFI_UNSUPPORTED if the hardware does not support | |
hiding of SMRAM. The function should return EFI_DEVICE_ERROR if the SMRAM | |
configuration is locked. | |
@param PeiServices General purpose services available to every | |
PEIM. | |
@param This The pointer to the SMM Access Interface. | |
@param DescriptorIndex The region of SMRAM to Open. | |
@retval EFI_SUCCESS The region was successfully opened. | |
@retval EFI_DEVICE_ERROR The region could not be opened because locked | |
by chipset. | |
@retval EFI_INVALID_PARAMETER The descriptor index was out of bounds. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
SmmAccessPeiOpen ( | |
IN EFI_PEI_SERVICES **PeiServices, | |
IN PEI_SMM_ACCESS_PPI *This, | |
IN UINTN DescriptorIndex | |
) | |
{ | |
EFI_HOB_GUID_TYPE *GuidHob; | |
EFI_SMRAM_HOB_DESCRIPTOR_BLOCK *DescriptorBlock; | |
// | |
// Get the number of regions in the system that can be usable for SMRAM | |
// | |
GuidHob = GetFirstGuidHob (&gEfiSmmSmramMemoryGuid); | |
DescriptorBlock = GET_GUID_HOB_DATA (GuidHob); | |
ASSERT (DescriptorBlock); | |
if (DescriptorIndex >= DescriptorBlock->NumberOfSmmReservedRegions) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// According to current practice, DescriptorIndex is not considered at all, | |
// beyond validating it. | |
// | |
return SmramAccessOpen (&This->LockState, &This->OpenState); | |
} | |
/** | |
Inhibits access to the SMRAM. | |
This function "closes" SMRAM so that it is not visible while outside of SMM. | |
The function should return EFI_UNSUPPORTED if the hardware does not support | |
hiding of SMRAM. | |
@param PeiServices General purpose services available to every | |
PEIM. | |
@param This The pointer to the SMM Access Interface. | |
@param DescriptorIndex The region of SMRAM to Close. | |
@retval EFI_SUCCESS The region was successfully closed. | |
@retval EFI_DEVICE_ERROR The region could not be closed because | |
locked by chipset. | |
@retval EFI_INVALID_PARAMETER The descriptor index was out of bounds. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
SmmAccessPeiClose ( | |
IN EFI_PEI_SERVICES **PeiServices, | |
IN PEI_SMM_ACCESS_PPI *This, | |
IN UINTN DescriptorIndex | |
) | |
{ | |
EFI_HOB_GUID_TYPE *GuidHob; | |
EFI_SMRAM_HOB_DESCRIPTOR_BLOCK *DescriptorBlock; | |
// | |
// Get the number of regions in the system that can be usable for SMRAM | |
// | |
GuidHob = GetFirstGuidHob (&gEfiSmmSmramMemoryGuid); | |
DescriptorBlock = GET_GUID_HOB_DATA (GuidHob); | |
ASSERT (DescriptorBlock); | |
if (DescriptorIndex >= DescriptorBlock->NumberOfSmmReservedRegions) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// According to current practice, DescriptorIndex is not considered at all, | |
// beyond validating it. | |
// | |
return SmramAccessClose (&This->LockState, &This->OpenState); | |
} | |
/** | |
Inhibits access to the SMRAM. | |
This function prohibits access to the SMRAM region. This function is usually | |
implemented such that it is a write-once operation. | |
@param PeiServices General purpose services available to every | |
PEIM. | |
@param This The pointer to the SMM Access Interface. | |
@param DescriptorIndex The region of SMRAM to Close. | |
@retval EFI_SUCCESS The region was successfully locked. | |
@retval EFI_DEVICE_ERROR The region could not be locked because at | |
least one range is still open. | |
@retval EFI_INVALID_PARAMETER The descriptor index was out of bounds. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
SmmAccessPeiLock ( | |
IN EFI_PEI_SERVICES **PeiServices, | |
IN PEI_SMM_ACCESS_PPI *This, | |
IN UINTN DescriptorIndex | |
) | |
{ | |
EFI_HOB_GUID_TYPE *GuidHob; | |
EFI_SMRAM_HOB_DESCRIPTOR_BLOCK *DescriptorBlock; | |
// | |
// Get the number of regions in the system that can be usable for SMRAM | |
// | |
GuidHob = GetFirstGuidHob (&gEfiSmmSmramMemoryGuid); | |
DescriptorBlock = GET_GUID_HOB_DATA (GuidHob); | |
ASSERT (DescriptorBlock); | |
if (DescriptorIndex >= DescriptorBlock->NumberOfSmmReservedRegions) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// According to current practice, DescriptorIndex is not considered at all, | |
// beyond validating it. | |
// | |
return SmramAccessLock (&This->LockState, &This->OpenState); | |
} | |
/** | |
Queries the memory controller for the possible regions that will support | |
SMRAM. | |
@param PeiServices General purpose services available to every | |
PEIM. | |
@param This The pointer to the SmmAccessPpi Interface. | |
@param SmramMapSize The pointer to the variable containing size of | |
the buffer to contain the description | |
information. | |
@param SmramMap The buffer containing the data describing the | |
Smram region descriptors. | |
@retval EFI_BUFFER_TOO_SMALL The user did not provide a sufficient buffer. | |
@retval EFI_SUCCESS The user provided a sufficiently-sized buffer. | |
**/ | |
STATIC | |
EFI_STATUS | |
EFIAPI | |
SmmAccessPeiGetCapabilities ( | |
IN EFI_PEI_SERVICES **PeiServices, | |
IN PEI_SMM_ACCESS_PPI *This, | |
IN OUT UINTN *SmramMapSize, | |
IN OUT EFI_SMRAM_DESCRIPTOR *SmramMap | |
) | |
{ | |
return SmramAccessGetCapabilities ( | |
SmramMapSize, | |
SmramMap | |
); | |
} | |
// | |
// LockState and OpenState will be filled in by the entry point. | |
// | |
STATIC PEI_SMM_ACCESS_PPI mAccess = { | |
&SmmAccessPeiOpen, | |
&SmmAccessPeiClose, | |
&SmmAccessPeiLock, | |
&SmmAccessPeiGetCapabilities | |
}; | |
STATIC EFI_PEI_PPI_DESCRIPTOR mPpiList[] = { | |
{ | |
EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST, | |
&gPeiSmmAccessPpiGuid, &mAccess | |
} | |
}; | |
// | |
// Utility functions. | |
// | |
STATIC | |
UINT8 | |
CmosRead8 ( | |
IN UINT8 Index | |
) | |
{ | |
IoWrite8 (0x70, Index); | |
return IoRead8 (0x71); | |
} | |
STATIC | |
UINT32 | |
GetSystemMemorySizeBelow4gb ( | |
VOID | |
) | |
{ | |
UINT32 Cmos0x34; | |
UINT32 Cmos0x35; | |
Cmos0x34 = CmosRead8 (0x34); | |
Cmos0x35 = CmosRead8 (0x35); | |
return ((Cmos0x35 << 8 | Cmos0x34) << 16) + SIZE_16MB; | |
} | |
// | |
// Entry point of this driver. | |
// | |
EFI_STATUS | |
EFIAPI | |
SmmAccessPeiEntryPoint ( | |
IN EFI_PEI_FILE_HANDLE FileHandle, | |
IN CONST EFI_PEI_SERVICES **PeiServices | |
) | |
{ | |
UINT16 HostBridgeDevId; | |
UINT8 EsmramcVal; | |
UINT8 RegMask8; | |
UINT32 TopOfLowRam, TopOfLowRamMb; | |
// | |
// This module should only be included if SMRAM support is required. | |
// | |
ASSERT (FeaturePcdGet (PcdSmmSmramRequire)); | |
// | |
// Verify if we're running on a Q35 machine type. | |
// | |
HostBridgeDevId = PciRead16 (OVMF_HOSTBRIDGE_DID); | |
if (HostBridgeDevId != INTEL_Q35_MCH_DEVICE_ID) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"%a: no SMRAM with host bridge DID=0x%04x; only " | |
"DID=0x%04x (Q35) is supported\n", | |
__func__, | |
HostBridgeDevId, | |
INTEL_Q35_MCH_DEVICE_ID | |
)); | |
goto WrongConfig; | |
} | |
// | |
// Confirm if QEMU supports SMRAM. | |
// | |
// With no support for it, the ESMRAMC (Extended System Management RAM | |
// Control) register reads as zero. If there is support, the cache-enable | |
// bits are hard-coded as 1 by QEMU. | |
// | |
EsmramcVal = PciRead8 (DRAMC_REGISTER_Q35 (MCH_ESMRAMC)); | |
RegMask8 = MCH_ESMRAMC_SM_CACHE | MCH_ESMRAMC_SM_L1 | MCH_ESMRAMC_SM_L2; | |
if ((EsmramcVal & RegMask8) != RegMask8) { | |
DEBUG (( | |
DEBUG_ERROR, | |
"%a: this Q35 implementation lacks SMRAM\n", | |
__func__ | |
)); | |
goto WrongConfig; | |
} | |
TopOfLowRam = GetSystemMemorySizeBelow4gb (); | |
ASSERT ((TopOfLowRam & (SIZE_1MB - 1)) == 0); | |
TopOfLowRamMb = TopOfLowRam >> 20; | |
// | |
// Some of the following registers are no-ops for QEMU at the moment, but it | |
// is recommended to set them correctly, since the ESMRAMC that we ultimately | |
// care about is in the same set of registers. | |
// | |
// First, we disable the integrated VGA, and set both the GTT Graphics Memory | |
// Size and the Graphics Mode Select memory pre-allocation fields to zero. | |
// This takes just one write to the Graphics Control Register. | |
// | |
PciWrite16 (DRAMC_REGISTER_Q35 (MCH_GGC), MCH_GGC_IVD); | |
// | |
// Set Top of Low Usable DRAM. | |
// | |
PciWrite16 ( | |
DRAMC_REGISTER_Q35 (MCH_TOLUD), | |
(UINT16)(TopOfLowRamMb << MCH_TOLUD_MB_SHIFT) | |
); | |
// | |
// Given the zero graphics memory sizes configured above, set the | |
// graphics-related stolen memory bases to the same as TOLUD. | |
// | |
PciWrite32 ( | |
DRAMC_REGISTER_Q35 (MCH_GBSM), | |
TopOfLowRamMb << MCH_GBSM_MB_SHIFT | |
); | |
PciWrite32 ( | |
DRAMC_REGISTER_Q35 (MCH_BGSM), | |
TopOfLowRamMb << MCH_BGSM_MB_SHIFT | |
); | |
// | |
// Set TSEG Memory Base. | |
// | |
InitQ35TsegMbytes (); | |
PciWrite32 ( | |
DRAMC_REGISTER_Q35 (MCH_TSEGMB), | |
(TopOfLowRamMb - mQ35TsegMbytes) << MCH_TSEGMB_MB_SHIFT | |
); | |
// | |
// Set TSEG size, and disable TSEG visibility outside of SMM. Note that the | |
// T_EN bit has inverse meaning; when T_EN is set, then TSEG visibility is | |
// *restricted* to SMM. | |
// | |
EsmramcVal &= ~(UINT32)MCH_ESMRAMC_TSEG_MASK; | |
EsmramcVal |= mQ35TsegMbytes == 8 ? MCH_ESMRAMC_TSEG_8MB : | |
mQ35TsegMbytes == 2 ? MCH_ESMRAMC_TSEG_2MB : | |
mQ35TsegMbytes == 1 ? MCH_ESMRAMC_TSEG_1MB : | |
MCH_ESMRAMC_TSEG_EXT; | |
EsmramcVal |= MCH_ESMRAMC_T_EN; | |
PciWrite8 (DRAMC_REGISTER_Q35 (MCH_ESMRAMC), EsmramcVal); | |
// | |
// TSEG should be closed (see above), but unlocked, initially. Set G_SMRAME | |
// (Global SMRAM Enable) too, as both D_LCK and T_EN depend on it. | |
// | |
PciAndThenOr8 ( | |
DRAMC_REGISTER_Q35 (MCH_SMRAM), | |
(UINT8)((~(UINT32)MCH_SMRAM_D_LCK) & 0xff), | |
MCH_SMRAM_G_SMRAME | |
); | |
GetStates (&mAccess.LockState, &mAccess.OpenState); | |
// | |
// SmramAccessLock() depends on "mQ35SmramAtDefaultSmbase"; init the latter | |
// just before exposing the former via PEI_SMM_ACCESS_PPI.Lock(). | |
// | |
InitQ35SmramAtDefaultSmbase (); | |
// | |
// We're done. The next step should succeed, but even if it fails, we can't | |
// roll back the above BuildGuidHob() allocation, because PEI doesn't support | |
// releasing memory. | |
// | |
return PeiServicesInstallPpi (mPpiList); | |
WrongConfig: | |
// | |
// We really don't want to continue in this case. | |
// | |
ASSERT (FALSE); | |
CpuDeadLoop (); | |
return EFI_UNSUPPORTED; | |
} |