/**
* @file SpsPei.c
* @brief SpsPei PEIM - Server Platform Services PEI Module
*
* Source: e:\hs\PurleySktPkg\Me\Sps\Pei\SpsPei.c
* Platform: Intel Purley (PurleySktPkg, Lewisburg PCH), IA32
* Compiler: VS2015 DEBUG
*
* ImageBase: 0xFFDA3630 ImageSize: 0x3460 MD5: 38dec5b1...
*
* SpsPei initializes and manages the ME (Management Engine) during the
* PEI phase. It determines the boot path (S3 vs. non-S3), waits for ME
* firmware initialization, establishes the ME-BIOS interface version,
* manages Node Manager (NM) power policies, sends END_OF_POST during S3
* resume, configures ICC (Integrated Clock Control) clock settings,
* and performs pre-DID resets when required.
*
* GUIDs (in .rdata):
* 0xFFDA6860: gMeFwHobGuid ({52885E62-...})
* 0xFFDA6880: gSpsInfoHobGuid ({489D2A71-...})
* 0xFFDA6888: gSpsPolicyHobGuid ({D14319E2-...})
* 0xFFDA6898: gSpsPolicyPpiGuid ({6B30CE48-...})
* 0xFFDA68A8: gHeci1PpiGuid ({12AA57CB-...})
*
* PPI Notify Descriptors (in .data):
* 0xFFDA68B0: S3 path -> SpsS3Path (0xFFDA3CC4)
* 0xFFDA68BC: Non-S3 path -> PpiNotifyHeciReady (0xFFDA4580)
*
* Major flows:
* 1. ModuleEntryPoint -> SpsPeiEntryPoint
* 2. SpsPeiEntryPoint detects ME is SPS firmware, reads boot mode
* 3. Branches to SpsS3Path (S3 resume) or SpsNonS3Path (normal boot)
* 4. Each path waits for ME init via HECI, negotiates protocol version
* 5. Publishes SPS info HOB for DXE consumption
*/
#include <Uefi.h>
#include <Pi/PiPeiCis.h>
#include <Library/DebugLib.h>
#include <Library/HobLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/IoLib.h>
#include <Library/PcdLib.h>
#include <Library/TimerLib.h>
#include <Library/PeiServicesTablePointerLib.h>
#include "SpsPei.h"
//
// External GUIDs defined in the .data section
//
extern EFI_GUID gHeci1PpiGuid;
extern EFI_GUID gSpsPolicyPpiGuid;
extern EFI_GUID gMeFwHobGuid;
extern EFI_GUID gSpsInfoHobGuid;
extern EFI_GUID gSpsPolicyHobGuid;
//
// Notify descriptors for PPI registration
//
extern EFI_PEI_NOTIFY_DESCRIPTOR mS3NotifyDesc;
extern EFI_PEI_NOTIFY_DESCRIPTOR mNonS3NotifyDesc;
// ---------------------------------------------------------------------------
// Library Wrappers / Internal Helpers
// ---------------------------------------------------------------------------
/**
* Zero-fill a memory buffer. Wrapper around memset.
*/
VOID *
InternalZeroMem (
VOID *Buffer,
UINTN Size
)
{
return ZeroMem (Buffer, Size);
}
/**
* Copy a memory buffer. Overlapping-safe backward copy if src < dst.
*/
VOID *
InternalCopyMem (
VOID *Destination,
CONST VOID *Source,
UINTN Length
)
{
return CopyMem (Destination, Source, Length);
}
/**
* Set a memory buffer to a byte value.
*/
VOID *
InternalSetMem (
VOID *Buffer,
UINTN Count,
UINT8 Value
)
{
return SetMem (Buffer, Count, Value);
}
/**
* Set a block of 8-byte pairs (non-standard, used for structure init).
*/
VOID
InternalSetMem32_Block (
OUT UINT32 *Buffer,
INT32 Count,
UINT32 ValueLow,
UINT32 ValueHigh
)
{
while (Count > 0) {
Buffer[0] = ValueLow;
Buffer[1] = ValueHigh;
Buffer += 2;
Count--;
}
}
/**
* Fill memory with 32-bit values.
*/
VOID *
InternalSetMem32 (
OUT VOID *Buffer,
IN UINTN Count,
IN UINT32 Value
)
{
return SetMem32 (Buffer, Count, Value);
}
/**
* Truncate a 64-bit value to lower 32 bits.
*/
UINT32
TruncateTo32 (
UINT64 Value
)
{
return (UINT32)Value;
}
/**
* Read the Interrupt Descriptor Table Register.
*/
VOID
ReadIdtr (
OUT IA32_DESCRIPTOR *Idtr
)
{
ASSERT (Idtr != NULL);
AsmReadIdtr (Idtr);
}
/**
* Read an unaligned 64-bit value.
*/
UINT64
ReadUnaligned64 (
IN CONST VOID *Buffer
)
{
ASSERT (Buffer != NULL);
return *(volatile UINT64 *)Buffer;
}
/**
* Write an unaligned 64-bit value.
*/
VOID
WriteUnaligned64 (
OUT VOID *Buffer,
IN UINT64 Value
)
{
ASSERT (Buffer != NULL);
*(volatile UINT64 *)Buffer = Value;
}
/**
* Get timestamp counter frequency (3.579545 MHz for this platform).
*/
UINT64
GetTimestampFrequency (
VOID
)
{
return TIMER_FREQUENCY;
}
// ---------------------------------------------------------------------------
// I/O Port Access
// ---------------------------------------------------------------------------
/**
* Read a 16-bit I/O port (aligned).
*/
UINT16
IoRead16 (
IN UINT16 Port
)
{
ASSERT ((Port & 1) == 0);
return __inword (Port);
}
/**
* Write a 16-bit I/O port (aligned).
*/
UINT16
IoWrite16 (
IN UINT16 Port,
IN UINT16 Value
)
{
ASSERT ((Port & 1) == 0);
return __outword (Port, Value);
}
/**
* Read a 32-bit I/O port (4-byte aligned).
*/
UINT32
IoRead32 (
IN UINT16 Port
)
{
ASSERT ((Port & 3) == 0);
return __indword (Port);
}
// ---------------------------------------------------------------------------
// Debug and Assert
// ---------------------------------------------------------------------------
/**
* Locate the Debug Library protocol via PEI services LocatePpi.
*/
EFI_PEI_PROTOCOL_ENTRY *
GetDebugLibProtocol (
VOID
)
{
EFI_PEI_PROTOCOL_ENTRY *Protocol;
EFI_STATUS Status;
Status = PeiServicesLocatePpi (NULL, (VOID **)&Protocol);
if (!EFI_ERROR (Status)) {
return Protocol;
}
return NULL;
}
/**
* Debug print wrapper. Checks debug level and forwards to protocol.
*
* @param ErrorLevel Debug error level
* @param Format Format string
*/
VOID
SpsDebugPrint (
IN UINTN ErrorLevel,
IN CONST CHAR8 *Format,
...
)
{
EFI_PEI_PROTOCOL_ENTRY *Protocol;
UINT32 DebugLevel;
Protocol = GetDebugLibProtocol ();
if (Protocol == NULL) {
return;
}
DebugLevel = GetDebugLevel ();
if ((DebugLevel & ErrorLevel) != 0) {
VA_LIST Marker;
VA_START (Marker, Format);
Protocol->DebugPrint (ErrorLevel, Format, Marker);
VA_END (Marker);
}
}
/**
* Assert handler.
*/
VOID
DebugAssert (
IN CONST CHAR8 *FileName,
IN UINTN LineNumber,
IN CONST CHAR8 *Description
)
{
EFI_PEI_PROTOCOL_ENTRY *Protocol;
Protocol = GetDebugLibProtocol ();
if (Protocol != NULL) {
Protocol->DebugAssert (FileName, LineNumber, Description);
}
}
/**
* Determine the current debug level using CMOS/NVRAM.
*
* Reads CMOS index 0x4A (bit 7 preserved from original index register value).
* Returns debug level mask:
* 0 = EFI_D_ERROR | EFI_D_WARN
* -1 = all disabled
* 1 = EFI_D_ERROR only
* -2147483578 = EFI_D_INFO only
* -2147483644 = EFI_D_WARN only
*/
UINT32
GetDebugLevel (
VOID
)
{
UINT8 Original;
UINT8 NvramIndex;
UINT8 Value;
UINT32 Result;
Original = __inbyte (0x70);
__outbyte (0x70, (Original & 0x80) | 0x4A);
Value = __inbyte (0x71);
if (Value <= 3) {
//
// Level 0-3: directly interpret
//
if (Value == 0) {
return 0;
}
} else {
//
// Value > 3: check if n3_0 is zero (backup from memory)
//
if (n3_0 == 0) {
//
// Read fallback from memory-mapped register
//
Value = (*(volatile UINT8 *)0xFDAF0490 & 2) | 1;
if (Value == 0) {
return 0;
}
}
}
if (Value == (UINT8)-1) {
return 0;
}
if (Value == 1) {
return (UINT32)-2147483644; // EFI_D_WARN only
}
return (UINT32)-2147483578; // EFI_D_INFO? (0x80000046)
}
// ---------------------------------------------------------------------------
// PCD Access via PEI PCD Protocol
// ---------------------------------------------------------------------------
/**
* Locate the PEI PCD protocol.
*/
VOID *
GetPcdProtocol (
VOID *This
)
{
EFI_STATUS Status;
VOID *PcdProtocol = This;
Status = PeiServicesLocatePpi (NULL, &PcdProtocol);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "ASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (Status);
}
return PcdProtocol;
}
/**
* Get a 32-bit PCD value.
*/
UINT32
PcdGet32 (
UINTN Token
)
{
EFI_PEI_PCD_PROTOCOL *PcdProtocol;
PcdProtocol = (EFI_PEI_PCD_PROTOCOL *)GetPcdProtocol ((VOID *)Token);
return PcdProtocol->Get32 (Token);
}
/**
* Get a PCD pointer.
*/
VOID *
PcdGetPtr (
UINTN Token
)
{
EFI_PEI_PCD_PROTOCOL *PcdProtocol;
PcdProtocol = (EFI_PEI_PCD_PROTOCOL *)GetPcdProtocol ((VOID *)Token);
return PcdProtocol->GetPtr (Token);
}
//
// PCD call-back data structures (used for lazy initialization)
//
STATIC UINT32 mPcdTable[] = { 0 };
STATIC UINT32 mPcdCache[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
/**
* Initialize PCD call-back structures (first-call lazy init).
*/
EFI_STATUS
PcdCallbackInit (
OUT VOID *EntryData,
IN UINTN EntryCount
)
{
EFI_PEI_PCD_PROTOCOL *PcdProtocol;
UINT32 TableSize;
TableSize = 16 * EntryCount + 16;
PcdProtocol = (EFI_PEI_PCD_PROTOCOL *)PcdGetPtr (PCD_TOKEN_PLATFORM_TYPE);
if (mPcdCache[0] == 0 && mPcdCache[1] == 0) {
mPcdCache[2] = PcdGet32 (PCD_TOKEN_PLATFORM_TYPE + 1);
mPcdCache[3] = 0;
}
return EFI_SUCCESS;
}
/**
* Resolve a PCD value via the protocol (with table/cache lookups).
*
* @param[in] PcdEntry Pointer to PCD entry descriptor
* @return Resolved PCD value (or default if not found)
*/
UINT32
PcdGetFromProtocol (
IN VOID *PcdEntry
)
{
EFI_PEI_PCD_PROTOCOL *PcdProtocol;
UINT32 LocalData;
UINT32 PcdPtr;
UINT32 Size;
UINT16 TokenNum;
TokenNum = *(UINT16 *)((UINTN)PcdEntry + 4);
PcdProtocol = (EFI_PEI_PCD_PROTOCOL *)PcdGetPtr (PCD_TOKEN_PLATFORM_TYPE);
if (*(UINT32 *)((UINTN)PcdEntry + 12) != 0) {
//
// External table path
//
PcdPtr = *(UINT32 *)(*(UINT32 *)((UINTN)PcdEntry + 12) +
4 * TokenNum + 255544);
Size = *(UINT32 *)(*(UINT32 *)((UINTN)PcdEntry + 12) +
4 * TokenNum + 255560);
} else {
//
// Local protocol path
//
if (PcdProtocol->GetSize (PcdGetPtr) != 0) {
PcdPtr = *(UINT32 *)((UINTN)PcdProtocol + 16 * TokenNum + 20);
Size = *(UINT32 *)((UINTN)PcdProtocol + 16 * TokenNum + 16);
} else {
PcdCallbackInit (mPcdTable, 8);
PcdPtr = mPcdCache[4 * TokenNum];
Size = mPcdCache[4 * TokenNum + 1];
if (Size == 0 && PcdPtr == 0) {
Size = PcdGet32 (PCD_TOKEN_PLATFORM_TYPE + 1);
PcdPtr = 0;
}
}
}
if (Size == 0 && PcdPtr == 0) {
return PcdGet32 (PCD_TOKEN_PLATFORM_TYPE + 1);
}
return Size;
}
/**
* Perform a PCI config read using a Direct Address (ECAM) mechanism.
*/
INT32 *
PciCfgRead (
IN UINT32 *Address,
OUT UINT32 *Value
)
{
UINT32 SegmentNum;
UINT32 PhysAddress;
PhysAddress = PcdGetFromProtocol (Address);
PhysAddress += (*Address & 0xFFFFFFF);
*Value = PhysAddress;
return Value;
}
// ---------------------------------------------------------------------------
// PCH / Platform Helpers
// ---------------------------------------------------------------------------
/**
* Read a PCH register for LPC device ID, then get the PWRM base address.
*
* @param[out] PwrmBase Pointer to receive PWRM base address
* @retval EFI_SUCCESS PWRM base read successfully
* @retval EFI_DEVICE_ERROR LPC device not found (LpcDeviceId = 0xFFFF)
*/
EFI_STATUS
PchPwrmBaseGet (
OUT UINT32 *PwrmBase
)
{
UINT16 LpcDeviceId;
if (PwrmBase == NULL) {
DEBUG ((EFI_D_ERROR, "PchPwrmBaseGet Error. Invalid pointer.\n"));
ASSERT_EFI_ERROR (EFI_INVALID_PARAMETER);
return EFI_INVALID_PARAMETER;
}
//
// Read LPC device ID from PCH config space at Bus 0, Dev 1F, Func 0
//
LpcDeviceId = IoRead16 (GetMeFwHob (31, 2));
if (LpcDeviceId == 0xFFFF) {
ASSERT_EFI_ERROR (EFI_DEVICE_ERROR);
return EFI_DEVICE_ERROR;
}
//
// Extract PWRM base from the same config space
//
*PwrmBase = *(volatile UINT32 *)(GetMeFwHob (31, 2) + 72) & 0xFFFF0000;
return EFI_SUCCESS;
}
/**
* Get the PCH SKU type by reading LPC Device ID.
*
* Returns:
* 1 = PCH-LP (low power)
* 2 = PCH-H (regular)
* 3 = unknown (default)
*/
UINT32
GetPchSkuType (
VOID
)
{
STATIC UINT32 mPchSkuType = 3;
UINT16 LpcDeviceId;
if (mPchSkuType != 3) {
return mPchSkuType;
}
LpcDeviceId = IoRead16 ((UINT16 *)(GetMeFwHob (31, 0) + 2));
if ((LpcDeviceId >= 0xA1C0 && LpcDeviceId <= 0xA1CF) ||
LpcDeviceId == 0xA243 ||
(LpcDeviceId >= 0xA240 && LpcDeviceId <= 0xA24F)) {
//
// PCH-LP (Low Power)
//
mPchSkuType = 1;
} else if (LpcDeviceId == 0x9D40 || LpcDeviceId == 0x9D41 ||
LpcDeviceId == 0x9D42 || LpcDeviceId == 0x9D43 ||
LpcDeviceId == 0x9D46 || LpcDeviceId == 0x9D48) {
//
// PCH-H (Regular)
//
mPchSkuType = 2;
} else {
DEBUG ((EFI_D_ERROR, "Unsupported PCH SKU, LpcDeviceId: 0x%04x!\n", LpcDeviceId));
ASSERT (FALSE);
}
return mPchSkuType;
}
/**
* Set the PCH MMIO range register (for S3 path).
*
* Writes the restored MMIO value to the platform register, after
* resolving the PCH SKU type.
*/
VOID
SetPchMmioRange (
IN UINT32 NewMmioValue
)
{
GetPchSkuType ();
*(volatile UINT32 *)PCH_MMIO_RANGE_REGISTER = NewMmioValue;
}
// ---------------------------------------------------------------------------
// HECI (Host Embedded Controller Interface) Access
// ---------------------------------------------------------------------------
/**
* Retrieve the HECI protocol interface pointer from PEI services.
*
* Uses the Interrupt Descriptor Table Register (IDTR) to locate the
* PEI Services Table, then extracts the HECI protocol entry.
*/
EFI_PEI_HECI_PROTOCOL *
HeciGetProtocol (
VOID
)
{
IA32_DESCRIPTOR Idtr;
UINT32 *PeiServiceTable;
ReadIdtr (&Idtr);
PeiServiceTable = *(UINT32 **)(*(UINT32 *)&Idtr[2] - 4);
ASSERT (PeiServiceTable != NULL);
return (EFI_PEI_HECI_PROTOCOL *)PeiServiceTable;
}
/**
* Locate a PEI PPI protocol by GUID.
*
* Thin wrapper around PeiServices->LocatePpi.
*/
EFI_STATUS
PeiServicesLocatePpi (
IN EFI_GUID *Guid OPTIONAL,
OUT VOID **PpiInterface
)
{
EFI_PEI_SERVICES **PeiServices;
EFI_PEI_HECI_PROTOCOL *HeciProtocol;
HeciProtocol = HeciGetProtocol ();
return HeciProtocol->LocatePpi (
PeiServices,
Guid,
0,
NULL,
PpiInterface
);
}
// ---------------------------------------------------------------------------
// ME Firmware HOB Access
// ---------------------------------------------------------------------------
/**
* Get the ME Firmware HOB (Hand-Off Block) with config space offset.
*
* Constructs a PCI config space address from the function/device
* encoding and reads the result into the output parameter.
*
* @param[in] Flags Configuration flags (device/func encoding)
* @param[in] Value Additional address bits (used for lower bits)
* @return Pointer to the HOB config data
*/
UINT32 *
GetMeFwHob (
IN UINT8 Flags,
IN UINT32 Value
)
{
UINT32 Address[5];
Address[2] = 512;
Address[0] = ((Value & 7 | (8 * (Flags & 0x1F))) << 12);
Address[3] = 0;
Address[1] = 0;
PciCfgRead (Address, (UINT32 *)&Value);
return (UINT32 *)Value;
}
/**
* Get the HOB list pointer via PEI services.
*/
VOID *
GetHobList (
VOID
)
{
EFI_PEI_SERVICES **PeiServices;
VOID *HobList;
EFI_STATUS Status;
PeiServices = (EFI_PEI_SERVICES **)HeciGetProtocol ();
Status = (*PeiServices)->GetHobList (PeiServices, &HobList);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (Status);
}
ASSERT (HobList != NULL);
return HobList;
}
/**
* Get the next HOB in the chain with GUID type.
*/
EFI_HOB_GUID_TYPE *
HobGetNextGuid (
IN VOID *HobStart
)
{
EFI_HOB_GUID_TYPE *HobGuid;
ASSERT (HobStart != NULL);
for (HobGuid = (EFI_HOB_GUID_TYPE *)HobStart;
HobGuid->Header.HobType != EFI_HOB_TYPE_END_OF_HOB_LIST;
HobGuid = NEXT_HOB (HobGuid))
{
if (HobGuid->Header.HobType == EFI_HOB_TYPE_GUID_EXTENSION) {
return HobGuid;
}
}
return NULL;
}
/**
* Find a GUID extension HOB matching a given GUID.
*
* @param[in] Guid Pointer to the GUID to match
* @return Pointer to the GUID HOB, or NULL if not found
*/
VOID *
HobGetGuid (
IN CONST EFI_GUID *Guid
)
{
EFI_HOB_GUID_TYPE *HobGuid;
HobGuid = (EFI_HOB_GUID_TYPE *)GetHobList ();
while (HobGuid != NULL) {
if (HobIsMatchingGuid ((VOID *)Guid, (VOID *)((UINTN)HobGuid + 8))) {
return HobGuid;
}
HobGuid = HobGetNextGuid ((UINTN)HobGuid + HobGuid->Header.HobLength);
}
return NULL;
}
/**
* Compare a Name GUID with a HOB GUID (both 16-byte EFI_GUID).
*
* @param[in] NameGuid Known GUID to match
* @param[in] HobGuid HOB GUID from the HOB
* @retval TRUE GUIDs match
* @retval FALSE GUIDs do not match
*/
BOOLEAN
HobIsMatchingGuid (
IN CONST EFI_GUID *NameGuid,
IN CONST VOID *HobGuid
)
{
UINT64 NameLow;
UINT64 NameHigh;
UINT64 HobLow;
UINT64 HobHigh;
NameLow = ReadUnaligned64 (NameGuid);
NameHigh = ReadUnaligned64 ((UINTN)NameGuid + 8);
HobLow = ReadUnaligned64 (HobGuid);
HobHigh = ReadUnaligned64 ((UINTN)HobGuid + 8);
return (NameLow == HobLow) && (NameHigh == HobHigh);
}
/**
* Get ME FStatus1 from the MeFwHob.
*
* Reads the ME FW HOB GUID entry and extracts MEFS1 value.
* Returns -1 on failure.
*/
UINT32
GetMeFs1FromHob (
VOID
)
{
EFI_HOB_GUID_TYPE *Hob;
UINT32 MeFs1;
MeFs1 = (UINT32)-1;
Hob = (EFI_HOB_GUID_TYPE *)HobGetGuid (&gMeFwHobGuid);
if (Hob == NULL) {
DEBUG ((EFI_D_ERROR, "HECI: GetMeFs1FromHob() Can't read correctly MeFwHob info\n"));
return (UINT32)-1;
}
if (*(UINT32 *)((UINTN)Hob + 28) != 0) {
//
// Error path: assert MeFwHob->Group[0].FunNumber == HECI1_DEVICE
//
DEBUG ((EFI_D_ERROR, "HECI: GetMeFs1FromHob() Can't read correctly MeFwHob info\n"));
ASSERT (FALSE);
} else {
MeFs1 = *(UINT32 *)((UINTN)Hob + 32);
}
if (MeFs1 == (UINT32)-1) {
DEBUG ((EFI_D_ERROR, "HECI: GetMeFs1FromHob() Can't read correctly MeFwHob info\n"));
return (UINT32)(-1);
}
DEBUG ((EFI_D_INFO, "HECI: GetMeFs1FromHob() returns MEFS1 = %d\n", MeFs1));
return MeFs1;
}
/**
* Determine if the running ME firmware is SPS firmware.
*
* Reads MEFS1, MEFS2 from HOB, checks operation mode.
*
* @retval 1 ME is SPS firmware
* @retval 2 ME is DFX
* @retval 15 ME is Other/Not-SPS
* @retval 255 ME is TAP
* @retval 0 ME type not recognized
*/
UINT32
IsSpsFw (
VOID
)
{
UINT32 PwrmBase;
UINT32 MeFs1;
PwrmBase = 0;
PchPwrmBaseGet (&PwrmBase);
if (PwrmBase != 0) {
//
// Check DWR flow
//
if ((*(volatile UINT32 *)(PwrmBase + 300) & 0x8000) != 0) {
DEBUG ((EFI_D_INFO, "HECI: GetOnBoardMeType() for DWR flow return Dfx type\n"));
return 15;
}
} else {
ASSERT_EFI_ERROR (EFI_DEVICE_ERROR);
}
MeFs1 = *(volatile UINT32 *)(GetMeFwHob (0) + 64);
if (MeFs1 == (UINT32)-1) {
//
// HOB not yet available; read from HOB directly
//
MeFs1 = GetMeFs1FromHob ();
DEBUG ((EFI_D_INFO, "HECI: GetOnBoardMeType() reads Hfs info from HOB = %d\n", MeFs1));
}
if ((MeFs1 & MEFS1_ME_STATE_MASK) != 0xF) {
//
// Valid FSM state
//
if ((MeFs1 & MEFS1_ME_STATE_MASK) == 4) {
return 255; // TAP
}
DEBUG ((EFI_D_INFO, "HECI: MeOperationMode = %d\n",
(MeFs1 & MEFS1_OPERATION_MODE_MASK) >> 16));
switch ((MeFs1 >> 16) & 0xF) {
case 0: // Normal
case 1: // Debug
return 2;
case 2: // Manufacturing
return 255;
case 3: case 4: case 5:
return 2;
case 7: // Secure
return 255;
case 15: // SPS
return 1;
default:
DEBUG ((EFI_D_ERROR,
"HECI: ME type not recognized (MEFS1: 0x%08X)\n", MeFs1));
DEBUG ((EFI_D_ERROR,
" (MEFS2: 0x%08X)\n",
*(volatile UINT32 *)(GetMeFwHob (0) + 72)));
return 0;
}
}
return 15; // No ME firmware state
}
/**
* Check if ME is in SPS normal mode and the boot is S3 resume.
*
* @retval TRUE S3 resume path for SPS ME
* @retval FALSE Not S3 or not SPS
*/
BOOLEAN
IsSpsNormalModeS3 (
VOID
)
{
if (IsSpsFw () != 1) {
return FALSE;
}
return ((*(volatile UINT32 *)(GetMeFwHob (22, 0) + 64) & MEFS1_ME_STATE_MASK)
== MEFS1_ME_STATE_RECOVERY);
}
// ---------------------------------------------------------------------------
// HOB Building
// ---------------------------------------------------------------------------
/**
* Build SPS Policy HOB (builds HobGuid for gSpsPolicyHobGuid + gSpsInfoHobGuid).
*/
VOID *
HobBuildSpsPolicy (
IN VOID *Data
)
{
UINT64 Temp;
UINT64 Temp2;
Temp = ReadUnaligned64 (&gSpsInfoHobGuid);
Temp2 = ReadUnaligned64 (&gSpsPolicyHobGuid);
WriteUnaligned64 ((UINT64 *)Data + 0, Temp);
WriteUnaligned64 ((UINT64 *)Data + 1, Temp2);
return Data;
}
/**
* Create SPS Info HOB from Heci (PEI Services BuildHob).
*
* @param[in] HobGuidPtr GUID for the HOB
* @return Pointer to the created HOB, or NULL
*/
VOID *
HobCreateSpsInfo (
IN CONST EFI_GUID *HobGuidPtr
)
{
VOID *Hob;
EFI_STATUS Status;
Hob = HeciGetProtocol ();
Status = ((EFI_PEI_SERVICES **)Hob)->CreateHob (
(EFI_PEI_SERVICES **)Hob,
sizeof (EFI_HOB_GUID_TYPE) + (UINTN)HobGuidPtr,
&Hob
);
if (EFI_ERROR (Status)) {
Hob = NULL;
}
if (Hob == NULL) {
ASSERT_EFI_ERROR (EFI_OUT_OF_RESOURCES);
}
return Hob;
}
// ---------------------------------------------------------------------------
// Timer / Delay
// ---------------------------------------------------------------------------
/**
* Spin-loop microsecond delay using timestamp counter.
*
* @param[in] MicroSeconds Number of microseconds to delay
*/
VOID
MicroSecondDelaySpin (
IN UINT32 MicroSeconds
)
{
UINT64 Ticks;
UINT64 TargetTsc;
Ticks = MicroSeconds * (TIMER_FREQUENCY / 1000000);
TargetTsc = (AsmReadTsc () & 0xFFFFFF) + Ticks;
while (((AsmReadTsc () - TargetTsc) & 0x800000) == 0) {
_mm_pause ();
}
MicroSeconds = (UINT32)(Ticks >> 22);
while (MicroSeconds-- > 0) {
_mm_pause ();
}
}
/**
* Microsecond delay (1000us = 1ms).
*
* @param[in] This Ignored
* @return 1000 (always)
*/
UINTN
MicroSecondDelay (
VOID *This
)
{
UINT64 Ticks;
Ticks = (UINT64)GetTimestampFrequency ();
MicroSecondDelaySpin ((UINT32)(Ticks / 1000000));
return 1000;
}
// ---------------------------------------------------------------------------
// ME Message Functions
// ---------------------------------------------------------------------------
/**
* Wait for the ME to reach Normal (0x02) or Recovery (0x05) state
* within a timeout loop.
*
* @param[in] PollCount Timeout in loop iterations (default 2000 if NULL)
* @retval EFI_SUCCESS ME in Normal or Recovery state
* @retval EFI_TIMEOUT ME state transition timed out
* @retval EFI_UNSUPPORTED ME in unexpected state
*/
EFI_STATUS
WaitForMeNormalOrRecovery (
IN UINT32 *PollCount OPTIONAL
)
{
UINT32 Count;
UINT32 MeState;
Count = TIMEOUT_2000_MS;
if (PollCount != NULL) {
Count = 1000 * *PollCount;
}
DEBUG ((EFI_D_INFO, "[SPS] Wait for ME Normal or Recovery state\n"));
while (Count > 0) {
MeState = *(volatile UINT32 *)(GetMeFwHob (0) + 64) & MEFS1_ME_STATE_MASK;
if (MeState == MEFS1_ME_STATE_RECOVERY ||
MeState == MEFS1_ME_STATE_NORMAL) {
DEBUG ((EFI_D_INFO, "[SPS] Wait for ME state reaches ME State = %d\n", MeState));
return EFI_SUCCESS;
}
MicroSecondDelay (NULL);
Count--;
}
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Timeout when waiting for ME meaningful state\n"));
return EFI_TIMEOUT;
}
/**
* Wait for the ME firmware init complete flag (MEFS1 BIT9).
*
* @param[in] SpsPolicy Pointer to SPS policy PPI (optional)
* @retval EFI_SUCCESS ME init completed
* @retval EFI_TIMEOUT Timeout waiting for init complete
* @retval EFI_UNSUPPORTED ME not in valid state
*/
EFI_STATUS
WaitForMeFwInitComplete (
IN VOID *SpsPolicy OPTIONAL
)
{
UINT32 WaitCount;
UINT32 MeStatus;
UINT32 BootMode;
WaitCount = TIMEOUT_2000_MS;
if (SpsPolicy != NULL) {
WaitCount = 1000 * *(UINT32 *)((UINTN)SpsPolicy + 20);
}
DEBUG ((EFI_D_INFO, "[SPS] Waiting for ME firmware init complete\n"));
while (TRUE) {
MeStatus = *(volatile UINT32 *)(GetMeFwHob (0) + 64);
if ((MeStatus & MEFS1_FW_INIT_COMPLETE) != 0) {
break;
}
if (WaitCount == 0) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Timeout when waiting for ME init complete\n"));
return EFI_DEVICE_ERROR;
}
MicroSecondDelay (NULL);
WaitCount--;
}
//
// Read boot mode for context
//
BootMode = 4;
if (HeciGetProtocol ()->GetBootMode (&BootMode) < 0) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Cannot read boot mode (%r)\n"));
BootMode = 4;
}
//
// Validate ME state
//
switch ((MeStatus & MEFS1_ME_STATE_MASK) - 2) {
case 0: // Recovery
GetMeFwHob (0);
DEBUG ((EFI_D_WARN, "[SPS] WARNING: ME is in recovery mode (cause: %d)\n"));
return EFI_SUCCESS;
case 3: // Normal (5 - 2 = 3)
return EFI_SUCCESS;
default:
return EFI_DEVICE_ERROR;
}
}
/**
* Wait for the ME Reset Counter to change.
*
* @param[in] OldResetCounter Previous reset counter value to compare
* @param[in] pTimeout Pointer to timeout counter
* @retval EFI_SUCCESS Reset counter changed
* @retval EFI_TIMEOUT Timed out waiting
*/
EFI_STATUS
WaitForMeResetCounterChange (
IN UINT32 OldResetCounter,
IN UINT32 *pTimeout OPTIONAL
)
{
UINT32 Timeout = TIMEOUT_2000_MS;
UINT32 CurrentReset;
if (pTimeout == NULL) {
pTimeout = &Timeout;
}
while (TRUE) {
CurrentReset = (*(volatile UINT32 *)(GetMeFwHob (0) + 64) >> 20) & 0xF;
if (OldResetCounter != CurrentReset) {
return EFI_SUCCESS;
}
if (*pTimeout == 0) {
break;
}
MicroSecondDelay (NULL);
(*pTimeout)--;
}
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Timeout when waiting for ME Reset Counter Change\n"));
return EFI_TIMEOUT;
}
/**
* Set the NM (Node Manager) Boot Mode via MSR.
*
* @param[in] PerformanceOptimized TRUE = Performance, FALSE = Power Optimized
*/
VOID
SetNmBootMode (
IN BOOLEAN PerformanceOptimized
)
{
UINT64 PlatformId;
UINT64 MiscEnables;
UINT64 NewMisc;
PlatformId = AsmReadMsr64 (MSR_IA32_PLATFORM_ID);
MiscEnables = AsmReadMsr64 (MSR_IA32_MISC_ENABLES);
MiscEnables &= 0xFFFF80FF;
if (PerformanceOptimized) {
NewMisc = (PlatformId & 0x7F00) | MiscEnables;
DEBUG ((EFI_D_INFO, "[SPS] NM Boot Mode: Performance Optimized\n"));
} else {
NewMisc = (TruncateTo32 (AsmReadTsc ()) & 0x7F00) | MiscEnables;
DEBUG ((EFI_D_INFO, "[SPS] NM Boot Mode: Power Optimized\n"));
}
AsmWriteMsr64 (MSR_IA32_MISC_ENABLES, NewMisc);
}
/**
* Send END_OF_POST message to the ME via HECI.
* Used during S3 resume path.
*/
EFI_STATUS
SpsS3Path (
VOID
)
{
UINT32 MmioRange;
UINT32 MeFlags1, MeFlags2;
UINT8 MeState;
UINT8 MeExtendedState;
HECI_PROTOCOL *HeciPpi;
UINT32 MkhiMsg;
UINT32 ResponseSize;
EFI_STATUS Status;
DEBUG ((EFI_D_INFO, "[SPS] SpsS3Path called.\n"));
//
// Wait for ME firmware init
//
Status = WaitForMeFwInitComplete (GetSpsPolicyPpi ());
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: ME not initialized correctly (%r)\n", Status));
FinalizeSpsInit (0, 0, 0, 0, 0);
return Status;
}
//
// Locate HECI PPI
//
Status = PeiServicesLocatePpi (&gHeci1PpiGuid, (VOID **)&HeciPpi);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Cannot locate HECI PPI (%r)\n", Status));
return Status;
}
//
// Save and adjust MMIO range
//
MmioRange = *(volatile UINT32 *)PCH_MMIO_RANGE_REGISTER;
SetPchMmioRange (MmioRange & 0xFFFFFEFF);
MeFlags1 = *(volatile UINT32 *)(GetMeFwHob (0) + 16);
MeFlags2 = *(volatile UINT32 *)(GetMeFwHob (0) + 20);
MeState = *(volatile UINT8 *)(GetMeFwHob (0) + 4);
MeExtendedState = *(volatile UINT8 *)(GetMeFwHob (0) + 60);
if (MeFlags2 != 0 && (MeFlags1 & 4) != 0) {
*(volatile UINT32 *)(GetMeFwHob (0) + 16) = 0;
}
//
// Re-initialize HECI
//
HeciPpi->ReInit (0);
//
// Check if END_OF_POST should be sent
//
if (GetHeciPpi (NULL) != NULL &&
(*(volatile UINT8 *)((UINTN)GetHeciPpi (NULL) + 12) & 3) == 0) {
DEBUG ((EFI_D_INFO, "[SPS] Sending END_OF_POST to ME\n"));
MkhiMsg = HECI_MSG_END_OF_POST;
ResponseSize = HECI_MSG_EOP_LENGTH;
Status = HeciPpi->SendMessage (
0, &MkhiMsg, 4, &ResponseSize,
0, 7
);
if (!EFI_ERROR (Status)) {
if (MkhiMsg != HECI_MSG_EOP_RESPONSE_MKHI) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Invalid END_OF_POST response (MKHI: 0x%X)\n", MkhiMsg));
}
} else {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Cannot send END_OF_POST (%r)\n", Status));
}
}
//
// Restore flags
//
*(volatile UINT32 *)(GetMeFwHob (0) + 20) = MeFlags2;
*(volatile UINT32 *)(GetMeFwHob (0) + 16) = MeFlags1;
*(volatile UINT8 *)(GetMeFwHob (0) + 4) = MeExtendedState;
SetPchMmioRange (MmioRange);
return EFI_SUCCESS;
}
/**
* Normal (non-S3) boot path for SPS.
*
* Negotiates ME-BIOS interface version, configures Node Manager (NM),
* sets ICC clock settings if required.
*/
EFI_STATUS
SpsNonS3Path (
VOID
)
{
SPS_POLICY_PPI *SpsPolicy;
HECI_PROTOCOL *HeciPpi;
UINT8 Msg[HECI_MSG_GET_MEBIOS_LENGTH];
UINT32 MsgSize;
UINT32 RetryCount;
EFI_STATUS Status;
UINT32 FeatureSet;
UINT8 NmfsValue;
ICC_PROTOCOL *IccPpi;
BOOLEAN PowerOptimized;
UINT32 CoresToDisable;
BOOLEAN NodeManagerEnabled;
DEBUG ((EFI_D_INFO, "[SPS] SpsNonS3Path called.\n"));
//
// Get SPS policy
//
SpsPolicy = (SPS_POLICY_PPI *)GetSpsPolicyPpi ();
if (SpsPolicy == NULL) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Cannot locate SPS Policy PPI, defaults will be used\n"));
}
//
// Wait for ME firmware init
//
Status = WaitForMeFwInitComplete (SpsPolicy);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: ME not initialized correctly.\n"));
FinalizeSpsInit (0, 0, 0, 0, 0);
return Status;
}
//
// Locate the HECI PPI
//
GetMeFwHob (0);
Status = PeiServicesLocatePpi (&gHeci1PpiGuid, (VOID **)&HeciPpi);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Cannot locate HECI PPI\n"));
return Status;
}
//
// Reset HECI and send ME-BIOS Interface Version request
//
HeciPpi->ReInit (0);
ZeroMem (Msg, HECI_MSG_GET_MEBIOS_LENGTH);
DEBUG ((EFI_D_INFO, "[SPS] Sending ME-BIOS Interface Version request\n"));
//
// Retry loop for interface version negotiation
//
for (RetryCount = 0; RetryCount < HECI_MSG_GET_MEBIOS_RETRY; RetryCount++) {
Msg[0] = HECI_MSG_GET_MEBIOS_INTERFACE;
MsgSize = 1;
Status = HeciPpi->SendMessage (0, Msg, 1, 0, 32);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_WARN, "[SPS] WARNING: Cannot send SPS_GET_MEBIOS_INTERFACE (status %r)\n", Status));
continue;
}
MsgSize = HECI_MSG_GET_MEBIOS_LENGTH;
Status = HeciPpi->ReadMessage (0, 1, Msg, &MsgSize);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_WARN,
"[SPS] WARNING: Cannot get SPS_GET_MEBIOS_INTERFACE response (status: %r)\n",
Status));
continue;
}
if (Msg[0] != HECI_MSG_GET_MEBIOS_RESPONSE || MsgSize != HECI_MSG_GET_MEBIOS_LENGTH) {
DEBUG ((EFI_D_ERROR,
"[SPS] ERROR: Invalid GET_MEBIOS_INTERFACE response (cmd: 0x%X, len %d)\n",
Msg[0], MsgSize));
Status = EFI_PROTOCOL_ERROR;
continue;
}
//
// Success - interface version negotiated
//
break;
}
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Error sending SPS_GET_MEBIOS_INTERFACE message (Status = %r)\n", Status));
FinalizeSpsInit (0, 0, 0, 0, 0);
return EFI_PROTOCOL_ERROR;
}
DEBUG ((EFI_D_INFO, "[SPS] SPS ME-BIOS interface version is %d.%d\n", Msg[1], Msg[2]));
DEBUG ((EFI_D_INFO, " Feature set is 0x%08X\n", *(UINT32 *)&Msg[3]));
DEBUG ((EFI_D_INFO, " Feature set 2 is 0x%08X\n", *(UINT32 *)&Msg[7]));
FeatureSet = *(UINT32 *)&Msg[3];
//
// Configure Node Manager (NM) if enabled
//
if ((FeatureSet & FEATURE_NODE_MANAGER) != 0) {
DEBUG ((EFI_D_INFO, "[SPS] Node Manager enabled\n"));
NmfsValue = *(volatile UINT8 *)(GetMeFwHob (1) + 64);
if ((NmfsValue & 0x80) != 0) {
//
// NMFS configured
//
if (SpsPolicy != NULL) {
if ((SpsPolicy->Flags & ME_POLICY_INVERT_NMFS) != 0) {
NmfsValue ^= (UINT8)((UINT32)(NmfsValue ^ ~(UINT8)SpsPolicy->NmVariant) & 1);
}
if ((SpsPolicy->Flags & ME_POLICY_SET_NMFS) != 0) {
NmfsValue ^= (UINT8)((UINT32)(NmfsValue ^ SpsPolicy->NmVariant) & 0xFE);
}
}
SetNmBootMode ((BOOLEAN)(NmfsValue & 1));
} else {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: NMFS not configured while NM enabled \n"));
DEBUG ((EFI_D_ERROR,
"(feature set: 0x%08X, feature set 2: 0x%08X, NMFS: 0x%08X)\n",
FeatureSet, *(UINT32 *)&Msg[7], NmfsValue));
SetNmBootMode ((BOOLEAN)(FeatureSet & 1));
}
//
// Initialize HECI-2
//
if (EFI_ERROR (PeiServicesLocatePpi (&gHeci1PpiGuid, (VOID **)&IccPpi)) ||
IccPpi->Initialize () < 0) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Cannot initialize HECI-2 (%r)\n"));
}
//
// Apply ICC clock settings
//
if (IccSetGetCurrentClockingMode (NULL) >= 0) {
Status = PeiHeciSetSscAlternate (FALSE);
ASSERT_EFI_ERROR (Status);
} else {
DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (Status);
}
NodeManagerEnabled = TRUE;
} else {
NodeManagerEnabled = FALSE;
PowerOptimized = TRUE;
}
//
// Apply ICC clock settings if feature enabled
//
if ((FeatureSet & FEATURE_ICC_CLOCK_SETTINGS) != 0 &&
SpsPolicy != NULL &&
(SpsPolicy->Flags & ME_POLICY_ICC_SETTINGS) != 0) {
DEBUG ((EFI_D_INFO, "[SPS][ICC] Sending IccSetClockSettings request \n"));
if (PeiHeciSetSscAlternate (
(BOOLEAN)((SpsPolicy->Flags >> 14) & 1)) >= 0) {
DEBUG ((EFI_D_INFO, "[SPS][ICC] IccSetClockSettings: status: Success\n"));
} else {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Unable to set clock settings (%r)\n"));
}
}
//
// Finalize SPS init with HOB publishing
//
FinalizeSpsInit (
(UINT8)NodeManagerEnabled,
(UINT8)(NodeManagerEnabled ? !PowerOptimized : 0),
(UINT8)(NodeManagerEnabled ? (PowerOptimized ? 1 : 0) : 0),
PowerOptimized,
NodeManagerEnabled
);
return EFI_SUCCESS;
}
/**
* Finalize SPS initialization by creating the SPS info HOB, building the
* SPS policy HOB, and registering the HOB.
*
* @param[in] FlowType ME flow type (1=normal, 0=non-functional)
* @param[in] FeatureSet Feature set value (from ME-BIOS interface)
* @param[in] FeatureSet2 Feature set 2 value
* @param[in] PowerOptimizedBoot Power optimized boot indicator
* @param[in] CoresToDisable Number of cores to disable
*/
VOID
FinalizeSpsInit (
IN UINT8 FlowType,
IN UINT32 FeatureSet,
IN UINT32 FeatureSet2,
IN BOOLEAN PowerOptimizedBoot,
IN UINT8 CoresToDisable
)
{
//
// Data buffer published in the HOB (16 bytes)
//
struct {
UINT32 FeatureSet;
UINT32 FeatureSet2;
UINT8 PowerOptimizedBoot;
UINT8 CoresToDisable;
UINT8 FlowType;
UINT8 Reserved;
UINT32 Zero;
} SpsData;
SPS_INFO_HOB *SpsInfoHob;
ZeroMem (&SpsData, sizeof (SpsData));
SpsData.FeatureSet = FeatureSet;
SpsData.FeatureSet2 = FeatureSet2;
SpsData.PowerOptimizedBoot = (UINT8)PowerOptimizedBoot;
SpsData.CoresToDisable = CoresToDisable;
SpsData.FlowType = FlowType;
//
// Build the SPS info HOB via PEI services
//
SpsInfoHob = (SPS_INFO_HOB *)HobCreateSpsInfo (
sizeof (SPS_INFO_HOB)
);
if (SpsInfoHob != NULL) {
HobBuildSpsPolicy ((VOID *)SpsInfoHob);
SafeCopyMem ((CHAR8 *)SpsInfoHob, (CHAR8 *)&SpsData);
}
DEBUG ((EFI_D_INFO,
"[SPS] HOB: flow %d, feature set 0x%08X, features 2 0x%08x, "
"pwr opt boot %d, cores2disable %d\n",
FlowType, FeatureSet, FeatureSet2, PowerOptimizedBoot, CoresToDisable));
if (FlowType != 1) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: ME is non functional (flow %d)\n", FlowType));
}
//
// Verify SPS info HOB was registered
//
if (HobGetGuid (&gSpsInfoHobGuid) == NULL) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: SPS info HOB has not been registered\n"));
}
}
/**
* PPI notify callback: called when HECI PPI is ready during non-S3 path.
*
* Locates the SPS info HOB and updates it with the HECI readiness
* indication.
*
* @param[in] PeiServices Pointer to PEI services table
* @param[in] NotifyDesc Pointer to notify descriptor
* @param[in] Ppi Pointer to the HECI PPI
* @retval EFI_SUCCESS HOB updated
* @retval EFI_UNSUPPORTED SPS info HOB not found or not flagged ready
*/
EFI_STATUS
EFIAPI
PpiNotifyHeciReady (
IN EFI_PEI_SERVICES **PeiServices,
IN EFI_PEI_NOTIFY_DESCRIPTOR *NotifyDesc,
IN VOID *Ppi
)
{
SPS_INFO_HOB *SpsInfoHob;
//
// Locate existing SPS info HOB
//
SpsInfoHob = (SPS_INFO_HOB *)HobGetGuid (&gSpsInfoHobGuid);
if (SpsInfoHob == NULL || (SpsInfoHob->Flags & 1) == 0) {
return EFI_UNSUPPORTED;
}
//
// Store notify descriptor and PPI pointer in the HOB at known offsets
// Offset +35: low byte of NotifyDesc
// Offset +36: low byte of Ppi
//
*(volatile UINT8 *)((UINTN)SpsInfoHob + 35) = (UINT8)(UINTN)NotifyDesc;
*(volatile UINT8 *)((UINTN)SpsInfoHob + 36) = (UINT8)(UINTN)Ppi;
return EFI_SUCCESS;
}
// ---------------------------------------------------------------------------
// Pre-DID Reset
// ---------------------------------------------------------------------------
/**
* Execute the Pre-DID (Device ID) reset sequence.
*
* Sends a pre-DID reset message to ME, waits for reset counter change,
* then waits for ME to return to Normal or Recovery state.
*
* @retval EFI_SUCCESS Pre-DID reset successful
* @retval EFI_TIMEOUT Timed out waiting for ME
* @retval EFI_DEVICE_ERROR HECI PPI not available
* @retval EFI_PROTOCOL_ERROR Pre-DID reset not accepted by ME
*/
EFI_STATUS
ExecutePreDidReset (
VOID
)
{
HECI_PROTOCOL *HeciPpi;
UINT32 MeFs1Pre;
UINT32 Timeout;
UINT32 MkhiMsgBuf[3];
UINT32 ResponseSize;
UINT8 ResetAck;
EFI_STATUS Status;
Timeout = TIMEOUT_5000_MS;
DEBUG ((EFI_D_WARN, "[SPS] WARNING: Execute ME pre-DID reset\n"));
//
// Locate HECI PPI
//
Status = PeiServicesLocatePpi (&gHeci1PpiGuid, (VOID **)&HeciPpi);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Cannot locate HECI PPI (%r)\n", Status));
ASSERT_EFI_ERROR (Status);
return EFI_NOT_STARTED;
}
//
// Capture pre-reset ME state
//
MeFs1Pre = *(volatile UINT32 *)(GetMeFwHob (0) + 64);
//
// Build and send Pre-DID reset MKHI message
//
MkhiMsgBuf[0] = HECI_MSG_PRE_DID_RESET;
MkhiMsgBuf[1] = 0;
MkhiMsgBuf[2] = 0;
ResponseSize = HECI_MSG_PRE_DID_RESET_LENGTH;
Status = HeciPpi->SendMessage (
0, MkhiMsgBuf, HECI_MSG_PRE_DID_RESET_LENGTH,
&ResponseSize, 0, 7
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[SPS] Pre-DID reset, no ACK. Continue without reset (%r)\n", Status));
return EFI_SUCCESS;
}
//
// Check reset response
//
if ((MkhiMsgBuf[0] & 0x7F00) != 0 ||
(UINT8)MkhiMsgBuf[0] != 5 ||
(MkhiMsgBuf[0] & 0x8000) == 0 ||
(UINT8)MkhiMsgBuf[1] != 0) {
DEBUG ((EFI_D_ERROR,
"[SPS] Pre-DID reset is not accepted by ME. Continue without reset (%r)\n"));
return Status;
}
//
// Wait for reset counter to change
//
Status = WaitForMeResetCounterChange (
(MeFs1Pre >> 20) & 0xF,
&Timeout
);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Pre-DID reset, ME Reset Counter not changed (%r)\n", Status));
return Status;
}
//
// Wait for ME to return to Normal or Recovery state
//
Status = WaitForMeNormalOrRecovery (&Timeout);
if (EFI_ERROR (Status)) {
return EFI_TIMEOUT;
}
DEBUG ((EFI_D_INFO, "[SPS] Pre-DID reset has been executed successfully\n"));
return EFI_SUCCESS;
}
// ---------------------------------------------------------------------------
// ICC (Integrated Clock Control)
// ---------------------------------------------------------------------------
/**
* Set/Get current clocking mode via HECI ICC command.
*
* @param[in,out] Buffer ICC message buffer (48 bytes)
* @retval EFI_SUCCESS Clock settings applied
* @retval EFI_UNSUPPORTED Not SPS firmware
* @retval EFI_NOT_STARTED HECI protocol unavailable
* @retval EFI_PROTOCOL_ERROR Invalid response from ME
*/
EFI_STATUS
IccSetGetCurrentClockingMode (
IN OUT ICC_MESSAGE_BUFFER *Buffer OPTIONAL
)
{
HECI_PROTOCOL *HeciPpi;
UINT32 ResponseSize;
EFI_STATUS Status;
//
// ICC header: signature 0x40000, version, response fields
//
ICC_MESSAGE_BUFFER LocalBuf;
DEBUG ((EFI_D_INFO, "[ICC] SpsSetGetCurrenClockingMode\n"));
Status = EFI_UNSUPPORTED;
if (IsSpsFw () != 1 || IsSpsNormalModeS3 ()) {
return EFI_UNSUPPORTED;
}
if (Buffer == NULL) {
return EFI_INVALID_PARAMETER;
}
//
// Locate HECI PPI and send ICC command
//
Status = PeiServicesLocatePpi (&gHeci1PpiGuid, (VOID **)&HeciPpi);
if (EFI_ERROR (Status)) {
return EFI_NOT_STARTED;
}
ResponseSize = sizeof (ICC_MESSAGE_BUFFER);
Buffer->Header.Signature = ICC_HEADER_SIGNATURE;
Buffer->Header.Command = ICC_HEADER_COMMAND_SET_CURRENT;
Buffer->Header.Reserved = 0;
Buffer->Header.Response = ICC_HEADER_RESPONSE_SUCCESS;
Buffer->Header.Reserved2 = 0;
Buffer->Header.Version = 1;
Status = HeciPpi->SendMessage (
0, Buffer, sizeof (ICC_MESSAGE_BUFFER),
&ResponseSize, 0, 8
);
if (!EFI_ERROR (Status)) {
if (Buffer->Header.Command != ICC_HEADER_COMMAND_SET_CURRENT ||
Buffer->Header.Response != 0) {
DEBUG ((EFI_D_ERROR,
"(ICC) SpsSetGetCurrenClockingMode: Wrong response! "
"IccHeader.IccResponse = 0x%x\n",
Buffer->Header.Response));
Status = EFI_PROTOCOL_ERROR;
}
}
DEBUG ((EFI_D_INFO, "[ICC] SpsSetGetCurrenClockingMode exit status = %r \n", Status));
return Status;
}
/**
* Set SSC (Spread Spectrum Clocking) alternate mode via HECI.
*
* @param[in] SscAlternate TRUE to enable SSC alternate
* @return Status from IccSetGetCurrentClockingMode
*/
EFI_STATUS
PeiHeciSetSscAlternate (
IN BOOLEAN SscAlternate
)
{
ICC_MESSAGE_BUFFER Buffer;
DEBUG ((EFI_D_INFO, "[ME] PeiHeciSetSSCAlternate(%d)\n", SscAlternate));
ZeroMem (&Buffer, sizeof (Buffer));
Buffer.Data[43] = (UINT8)SscAlternate;
DEBUG ((EFI_D_INFO, "[ME] PeiHeciSetSSCAlternate sets SSC settings to %d\n", SscAlternate));
return IccSetGetCurrentClockingMode (&Buffer);
}
// ---------------------------------------------------------------------------
// ME Entry Point
// ---------------------------------------------------------------------------
/**
* Main entry point for SpsPei.
*
* Detects ME type, reads boot mode, performs pre-DID reset if needed,
* then branches to S3 or non-S3 path.
*
* @param[in] ImageHandle PEI image handle
* @param[in] SystemTable PEI system table
* @retval EFI_SUCCESS Module initialized successfully
* @retval EFI_UNSUPPORTED Non-SPS firmware detected
*/
EFI_STATUS
EFIAPI
SpsPeiEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
UINT32 BootMode;
SPS_POLICY_PPI *SpsPolicy;
UINT32 MeState;
UINT32 MeFs1;
DEBUG ((EFI_D_INFO, "[SPS] SpsPeiEntryPoint called.\n"));
//
// Step 1: Verify ME firmware is SPS
//
if (IsSpsFw () != 1) {
DEBUG ((EFI_D_INFO, "[SPS] Other ME FW detected.\n"));
goto NonSpsFwError;
}
//
// Step 2: Read boot mode
//
BootMode = 4;
Status = SystemTable->BootServices->GetBootMode (&BootMode);
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Cannot read boot mode (%r)\n"));
BootMode = 4;
}
//
// Step 3: Check pre-DID reset policy
//
SpsPolicy = (SPS_POLICY_PPI *)GetSpsPolicyPpi ();
DEBUG ((EFI_D_INFO, "[SPS] Pre-DID reset "));
if (BootMode != BOOT_MODE_S3_RESUME &&
SpsPolicy != NULL &&
(SpsPolicy->Flags & 1) != 0) {
//
// Pre-DID reset is enabled for this boot
//
DEBUG ((EFI_D_INFO, " execution\n"));
if (EFI_ERROR (ExecutePreDidReset ())) {
goto NonSpsFwError;
}
MeState = *(volatile UINT32 *)(GetMeFwHob (0) + 64) & MEFS1_ME_STATE_MASK;
MeFs1 = *(volatile UINT32 *)(GetMeFwHob (0) + 72) & 0x700;
if (MeState == 2 && MeFs1 == 0x400) {
//
// ME in recovery mode: re-initialize
//
DEBUG ((EFI_D_WARN,
"[SPS] WARNING: ME is in recovery mode (cause: %d)\n",
*(volatile UINT32 *)(GetMeFwHob (0) + 64)));
Status = WaitForMeFwInitComplete (NULL);
if (EFI_ERROR (Status)) {
if (Status == EFI_TIMEOUT) {
DEBUG ((EFI_D_ERROR,
"[SPS] ERROR: Pre-DID reset timeout failure causes ME non-functional flow\n"));
goto NonSpsFwError;
}
DEBUG ((EFI_D_ERROR,
"[SPS] ERROR: Pre-DID reset failure. Continue according to ME state\n"));
} else if ((MeState & MEFS1_ME_STATE_MASK) == MEFS1_ME_STATE_NORMAL) {
DEBUG ((EFI_D_INFO,
"[SPS] Pre-DID reset finished successfully, ME in Normal state\n"));
}
}
} else {
DEBUG ((EFI_D_INFO, " is disabled\n"));
}
//
// Step 4: Branch to S3 or non-S3 path
//
if (BootMode == BOOT_MODE_S3_RESUME) {
//
// S3 resume path
//
DEBUG ((EFI_D_INFO, "[SPS] S3 resume path\n"));
Status = PeiServicesLocatePpi (
&gHeci1PpiGuid,
&mS3NotifyDesc
);
} else {
//
// Non-S3 boot path
//
DEBUG ((EFI_D_INFO, "[SPS] Non S3 boot path\n"));
Status = PeiServicesLocatePpi (
&gHeci1PpiGuid,
&mNonS3NotifyDesc
);
}
if (EFI_ERROR (Status)) {
DEBUG ((EFI_D_ERROR, "[SPS] ERROR: Cannot register PPI notify handler (%r)\n"));
}
return EFI_SUCCESS;
NonSpsFwError:
//
// Non-SPS firmware detected: publish minimal HOBs and return error
//
FinalizeSpsInit (0, 0, 0, 0, 0);
DEBUG ((EFI_D_ERROR,
"[SPS] ERROR: Non SPS firmware running in ME\n"
" (MEFS1: 0x%08X, MEFS2: 0x%08X)\n",
*(volatile UINT32 *)(GetMeFwHob (0) + 64),
*(volatile UINT32 *)(GetMeFwHob (0) + 72)));
return EFI_UNSUPPORTED;
}
// ---------------------------------------------------------------------------
// Module Entry Point (PEI)
// ---------------------------------------------------------------------------
/**
* Standard UEFI PEIM entry point.
*
* Calls through to SpsPeiEntryPoint but performs a one-time initialization
* guard: checks if this is the first invocation by testing a global flag,
* setting the watchdog timer, and then delegating.
*
* @param[in] ImageHandle PEI image handle
* @param[in] SystemTable PEI system table
* @retval Status Return value from SpsPeiEntryPoint
*/
EFI_STATUS
EFIAPI
ModuleEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
//
// One-time initialization guard (0xFDA362C + 1024068 = 0xFDA362C + 0xFA000)
// Check whether bit 7 of the marker byte is set.
//
if (*(CHAR8 *)(GetBootMode () + 1024068) >= 0) {
//
// First call: set watchdog timer (1280ms)
//
SetWatchdogTimer ();
//
// Mark as initialized
//
*(_BYTE *)(GetBootMode () + 1024068) |= 0x80;
}
return SpsPeiEntryPoint (ImageHandle, SystemTable);
}
/**
* Get the current boot mode from PCD.
*
* @return Boot mode value (BOOT_MODE_S3_RESUME = 0x11, etc.)
*/
UINT32
GetBootMode (
VOID
)
{
return PcdGet32 (PCD_TOKEN_BOOT_MODE);
}
/**
* Set the watchdog timer count (1280 ticks = approx 357us @ 3.579545 MHz?).
*
* Reads current boot mode PCD and writes 1280 to the associated I/O register.
*/
VOID
SetWatchdogTimer (
VOID
)
{
IoWrite16 ((UINT16 *)(GetBootMode () + 1024064), 1280);
}
/**
* HECI PPI wrapper: returns the HECI protocol PPI.
*/
HECI_PROTOCOL *
GetHeciPpi (
VOID *This
)
{
EFI_STATUS Status;
HECI_PROTOCOL *HeciPpi = This;
Status = PeiServicesLocatePpi (&gHeci1PpiGuid, (VOID **)&HeciPpi);
if (EFI_ERROR (Status)) {
HeciPpi = NULL;
DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (Status);
}
return HeciPpi;
}
/**
* Get SPS Policy PPI.
*/
VOID *
GetSpsPolicyPpi (
VOID *This
)
{
VOID *PolicyPpi = This;
if (EFI_ERROR (PeiServicesLocatePpi (&gSpsPolicyPpiGuid, &PolicyPpi))) {
PolicyPpi = NULL;
DEBUG ((EFI_D_ERROR, "\nASSERT_EFI_ERROR (Status = %r)\n", Status));
ASSERT_EFI_ERROR (Status);
}
return PolicyPpi;
}
// ---------------------------------------------------------------------------
// PPI Notify Descriptors (in .data section)
// ---------------------------------------------------------------------------
//
// EFI_PEI_NOTIFY_DESCRIPTOR at 0xFFDA68B0 (for S3 path)
// Notify = SpsS3Path
//
EFI_PEI_NOTIFY_DESCRIPTOR mS3NotifyDesc = {
EFI_PEI_PPI_DESCRIPTOR_NOTIFY_DISPATCH | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST,
&gHeci1PpiGuid,
SpsS3Path
};
//
// EFI_PEI_NOTIFY_DESCRIPTOR at 0xFFDA68BC (for Non-S3 path)
// Notify = PpiNotifyHeciReady
//
EFI_PEI_NOTIFY_DESCRIPTOR mNonS3NotifyDesc = {
EFI_PEI_PPI_DESCRIPTOR_NOTIFY_DISPATCH | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST,
&gHeci1PpiGuid,
PpiNotifyHeciReady
};