/** @file
PeiCore - CPU Information PEI Module
This PEIM provides CPU information services during the PEI phase.
It displays POST progress, detects CPU presence via CMOS, and
provides CPUID-based topology information.
Source: e:\hs\MdeModulePkg\Core\Pei\PeiMain\PeiMain.c
e:\hs\MdeModulePkg\Core\Pei\Dispatcher\Dispatcher.c
e:\hs\MdeModulePkg\Core\Pei\FwVol\FwVol.c
e:\hs\MdeModulePkg\Core\Pei\Ppi\Ppi.c
e:\hs\MdeModulePkg\Core\Pei\Image\Image.c
e:\hs\MdeModulePkg\Core\Pei\Memory\MemoryServices.c
**/
#include <PiPei.h>
#include <Library/PeiServicesTablePointerLib.h>
#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/PeiServicesLib.h>
#include <Library/HobLib.h>
#include <Library/BaseMemoryLib.h>
//
// EFI_STATUS codes used in GetCpuPresenceStatus
//
#define CPU_PRESENT_SINGLE 0xFFFFFFFF // EFI_SUCCESS? or all bits set
#define CPU_PRESENT_MULTI 0xFFFFFFFF ^ 0x40 // approximate
#define CPU_NOT_PRESENT 0
#define CPU_ERROR_STATUS 0x80000000
//
// VGA constants
//
#define VGA_TEXT_MEMORY ((UINT16*)0xB8000)
#define VGA_CRTC_ADDR_PORT 0x3D4
#define VGA_CRTC_DATA_PORT 0x3D5
#define VGA_CURSOR_START 0x0A
#define VGA_CURSOR_SCANLINE 0x20
#define VGA_COLS 80
#define VGA_ROWS 25
#define VGA_ATTRIBUTE 7
//
// CMOS constants
//
#define RTC_ADDRESS_PORT 0x70
#define RTC_DATA_PORT 0x71
#define CMOS_CPU_PRESENCE 0x4A
#define NMI_DISABLE_BIT 0x80
//
// CPUID constants
//
#define CPUID_EXTENDED_TOPOLOGY 0x0B
//
// Progress display table entry: { x, y }
// Terminated with x == 0xFF / -1
//
#pragma pack(1)
typedef struct {
UINT8 X;
UINT8 Y;
} PROGRESS_POSITION;
#pragma pack()
//
// Forward declarations
//
EFI_STATUS
EFIAPI
CpuInfoEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
);
//
// Helper function prototypes
//
EFI_STATUS
GetPeiServices (
VOID
);
INTN
CpuInfoDebugPrint (
IN UINTN ErrorLevel,
IN CHAR8 *Format,
...
);
CHAR8
VgaPrintStringAt (
IN UINT8 X,
IN UINT8 Y,
IN CHAR8 *String,
...
);
UINT32
AsmCpuid (
IN UINT32 Index,
OUT UINT32 *Eax OPTIONAL,
OUT UINT32 *Ebx OPTIONAL,
OUT UINT32 *Ecx OPTIONAL,
OUT UINT32 *Edx OPTIONAL
);
UINT32
CpuId (
OUT UINT32 *Result OPTIONAL
);
UINTN
AsciiStrLen (
IN CHAR8 *String
);
VOID
ReadIdtr (
OUT IA32_DESCRIPTOR *Idtr
);
VOID
CpuInfoReportAssertion (
IN CHAR8 *FileName,
IN UINTN LineNumber,
IN CHAR8 *Description
);
VOID
CpuDeadLoop (
VOID
);
//========================================================================
// Module Entry Point
//========================================================================
/**
Main entry point for CPU Information PEIM.
@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.
@retval EFI_SUCCESS The entry point executed successfully.
**/
EFI_STATUS
EFIAPI
CpuInfoEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
UINT8 X;
UINT8 Y;
UINTN Index;
UINT8 *TablePos;
PROGRESS_POSITION *ProgressTable;
//
// Display initial POST progress: 0%
//
VgaPrintStringAt (0, 9, "== Progress : 0% ==");
//
// Iterate the progress-position table and display
// "System initializing, please wait..." messages
// at each position until terminator (-1) is found.
//
// The table is an array of PROGRESS_POSITION {X,Y}
// entries at byte_FFE83F24.
//
ProgressTable = (PROGRESS_POSITION*)(UINTN)0xFFE83F24;
if (ProgressTable[0].X != 0xFF) {
Index = 0;
TablePos = (UINT8*)&ProgressTable[0];
do {
X = ProgressTable[Index].X;
Y = ProgressTable[Index].Y;
//
// Skip entry (50,y) when y is in [11..21] — these are
// the "already displayed" slots.
//
if (X != 50 || Y < 11 || Y > 21) {
VgaPrintStringAt (X, Y, &gSystemInitializing[Index * 82]);
}
Index++;
TablePos = (UINT8*)&ProgressTable[Index];
} while (ProgressTable[Index].X != 0xFF);
}
//
// Install PPI or propagate descriptor to next PEIM
//
// NOTE: The original code calls through the PEI Services
// table at offset +36 (sizeof(EFI_PEI_SERVICES) + 36)
// with unk_FFE83F14 as the parameter. This could be
// InstallPpi, or a callback via SystemTable-> ?
// In the PEIM case, this likely dispatches a notification
// or installs a PPI.
//
return EFI_SUCCESS;
}
//========================================================================
// VGA Print Support
//========================================================================
/**
Writes an ASCII string to the VGA text buffer at the specified position
(row X, column Y) with attribute 7 (white on black).
@param[in] X VGA column (0-based)
@param[in] Y VGA row (0-based)
@param[in] String The ASCII string to print
@param[in] ... Additional arguments (ignored in this implementation)
@return The last character written.
**/
CHAR8
VgaPrintStringAt (
IN UINT8 X,
IN UINT8 Y,
IN CHAR8 *String,
...
)
{
UINTN Length;
UINT16 *VgaPtr;
UINTN Index;
Length = AsciiStrLen (String);
//
// Calculate VGA buffer offset: 2 bytes per cell
// VGA memory starts at 0xB8000 (753664 in decimal)
//
VgaPtr = VGA_TEXT_MEMORY + (X + Y * 80);
//
// Hide cursor by setting cursor position to scanline 32
//
IoWrite8 (VGA_CRTC_ADDR_PORT, VGA_CURSOR_START);
IoWrite8 (VGA_CRTC_DATA_PORT, VGA_CURSOR_SCANLINE);
//
// Write string characters with attribute 7
//
for (Index = 0; Index < Length; Index++) {
*VgaPtr = (UINT16)String[Index];
VgaPtr++;
}
//
// Set attribute of the last character (foreground)
//
*(VgaPtr - 1) = (UINT16)((String[Length - 1]) | (VGA_ATTRIBUTE << 8));
return String[Length - 1];
}
//========================================================================
// CPU Presence Detection
//========================================================================
/**
Detects CPU presence by reading CMOS register 0x4A.
This function probes CMOS NVRAM location 0x4A (CPU presence
register) to determine:
- 0 = No CPU present (or single socket unpopulated)
- 1 = Single CPU present
- Other = Multi-CPU or error
@retval 0 No CPU present / socket empty
@retval 0xFFFFFFFF Single CPU present (EFI_INVALID_PARAMETER-like status)
@retval 0x80000000+ Multiple CPUs present
**/
INTN
GetCpuPresenceStatus (
VOID
)
{
UINT8 OriginalReg;
UINT8 CmosValue;
//
// Read current RTC address register, preserve NMI mask (bit 7),
// select CMOS index 0x4A (CPU presence)
//
OriginalReg = IoRead8 (RTC_ADDRESS_PORT);
IoWrite8 (RTC_ADDRESS_PORT, (OriginalReg & NMI_DISABLE_BIT) | CMOS_CPU_PRESENCE);
//
// Read CPU presence value from CMOS data register
//
CmosValue = IoRead8 (RTC_DATA_PORT);
//
// Interpret the value:
// <= 3 : direct presence value
// == 0 : no CPU (or fall through to MMIO probe)
// == 1 : single CPU
// == -1 / 0xFF : invalid/empty
// > 3 : check if zero (possible memory-mapped IO at 0xFDAF0490)
//
if (CmosValue > 3) {
if (CmosValue == 0) {
//
// Read GPIO/MMIO register at fixed address for CPU presence
// bit 1 set = CPU present
//
CmosValue = (*(volatile UINT8*)0xFDAF0490 & 2) | 1;
}
}
if (CmosValue == 0) {
return 0; // No CPU
}
//
// 0xFF means not valid/empty
//
if (CmosValue == 0xFF) {
return 0; // Empty socket
}
if (CmosValue == 1) {
return CPU_PRESENT_SINGLE; // 0xFFFFFFFF
}
return CPU_ERROR_STATUS; // 0x80000000 (-2147483648)
}
//========================================================================
// PEI Services Access
//========================================================================
/**
Retrieves PEI Services pointer via the IDTR-based PEI Services
Table Pointer mechanism.
@return Pointer to EFI_PEI_SERVICES.
**/
EFI_PEI_SERVICES**
GetPeiServicesFromIdtr (
VOID
)
{
IA32_DESCRIPTOR Idtr;
UINTN PeiServices;
ReadIdtr (&Idtr);
//
// The PEI Services pointer is stored at the DWORD immediately
// before the IDT base address.
//
PeiServices = *(UINTN*)(*(UINTN*)&Idtr.Base - sizeof (UINTN));
if (PeiServices == 0) {
CpuInfoReportAssertion (
__FILE__,
__LINE__,
"PeiServices != ((void *) 0)"
);
}
return (EFI_PEI_SERVICES**)PeiServices;
}
/**
Returns PEI Services table pointer from the IDTR-based mechanism,
then calls the LocatePpi (or similar) service to find a PPI GUID.
@return The PPI instance pointer (or NULL if not found).
**/
EFI_STATUS
GetPeiServices (
VOID
)
{
EFI_PEI_SERVICES **PeiServices;
UINTN PpiInstance;
UINTN SectionInstance;
PeiServices = GetPeiServicesFromIdtr ();
//
// Call PEI Service at offset 32 (likely LocatePpi or similar)
// looking for the GUID at unk_FFE83EF4.
//
if ((*PeiServices)->LocatePpi (
&gUnknownPpiGuid,
0,
&PpiInstance,
&SectionInstance
) >= 0) {
return SectionInstance;
}
return 0;
}
//========================================================================
// Debug Print (Conditional)
//========================================================================
/**
Conditionally prints a debug message if the CPU presence status
matches the requested error level.
@param[in] ErrorLevel The error level mask
@param[in] Format Format string
@param[in] ... Additional arguments
**/
INTN
CpuInfoDebugPrint (
IN UINTN ErrorLevel,
IN CHAR8 *Format,
...
)
{
EFI_PEI_SERVICES **PeiServices;
VA_LIST Va;
INTN PresenceStatus;
VA_START (Va, Format);
PeiServices = GetPeiServicesFromIdtr ();
if (PeiServices == NULL) {
return 0;
}
PresenceStatus = GetCpuPresenceStatus ();
if ((PresenceStatus & ErrorLevel) != 0) {
//
// Call PEI Services debug print at offset 0
// (PEI_SERVICES pointer + 0 is the first function pointer)
// This is typically DebugPrint or similar.
//
return (*(PeiServices + 0))(ErrorLevel, Format, (CHAR8*)Va);
}
return PresenceStatus;
}
//========================================================================
// CPUID Wrappers
//========================================================================
/**
Executes the CPUID instruction with the given input EAX.
@param[in] _EAX CPUID leaf
@param[out] a2 Returns EAX (optional)
@param[out] a3 Returns EBX (optional)
@param[out] a4 Returns ECX (optional)
@param[out] a5 Returns EDX (optional)
@return The value of EAX after CPUID.
**/
UINT32
AsmCpuid (
IN UINT32 _EAX,
OUT UINT32 *a2 OPTIONAL,
OUT UINT32 *a3 OPTIONAL,
OUT UINT32 *a4 OPTIONAL,
OUT UINT32 *a5 OPTIONAL
)
{
UINT32 _EBX, _ECX, _EDX;
AsmCpuidEx (_EAX, 0, a2, &_EBX, &_ECX, &_EDX);
if (a2 != NULL) *a2 = _EAX;
if (a3 != NULL) *a3 = _EBX;
if (a4 != NULL) *a4 = _ECX;
if (a5 != NULL) *a5 = _EDX;
return _EAX;
}
/**
Executes CPUID leaf 0x0B (Extended Topology Enumeration).
This leaf provides processor topology information such as
the number of logical processors per core, per package, etc.
@param[out] a1 Optional pointer to receive EAX value
@return Always CPUID_EXTENDED_TOPOLOGY (11 = 0x0B).
**/
UINT32
CpuId (
OUT UINT32 *a1 OPTIONAL
)
{
UINT32 _EBX, _ECX, _EDX;
AsmCpuidEx (CPUID_EXTENDED_TOPOLOGY, 0, &_EBX, &_ECX, &_EDX);
//
// Check if leaf 0x0B is supported (EBX non-zero means valid)
//
if (a1 != NULL) {
*a1 = _EBX;
}
return CPUID_EXTENDED_TOPOLOGY;
}
//========================================================================
// ASSERT / Debug Assert Support
//========================================================================
/**
Reports an assertion failure via the PEI Services assertion handler.
@param[in] FileName Source file name string
@param[in] LineNumber Line number
@param[in] Description Assertion expression text
**/
VOID
CpuInfoReportAssertion (
IN CHAR8 *FileName,
IN UINTN LineNumber,
IN CHAR8 *Description
)
{
EFI_PEI_SERVICES **PeiServices;
PeiServices = GetPeiServicesFromIdtr ();
if (PeiServices != NULL) {
//
// PEI_SERVICES + 4 is ReportStatusCode or assertion handler
//
(*PeiServices)->ReportStatusCode (
(EFI_STATUS_CODE_TYPE)FileName,
(EFI_STATUS_CODE_VALUE)LineNumber,
(EFI_STATUS_CODE_DATA*)Description
);
}
}
//========================================================================
// String Helpers
//========================================================================
/**
Returns the length of an ASCII string.
This implementation also validates the string does not exceed
PcdMaximumAsciiStringLength (0xF4240 = 1,000,000).
@param[in] String Pointer to null-terminated ASCII string.
@return Length of the string in characters.
**/
UINTN
AsciiStrLen (
IN CHAR8 *String
)
{
UINTN Length;
if (String == NULL) {
CpuInfoReportAssertion (
"e:\\hs\\MdePkg\\Library\\BaseLib\\String.c",
1082,
"String != ((void *) 0)"
);
}
Length = 0;
while (String[Length] != '\0') {
if (Length >= 0xF4240) {
CpuInfoReportAssertion (
"e:\\hs\\MdePkg\\Library\\BaseLib\\String.c",
1090,
"Length < _gPcd_FixedAtBuild_PcdMaximumAsciiStringLength"
);
}
Length++;
}
return Length;
}
//========================================================================
// IDTR Access
//========================================================================
/**
Reads the Interrupt Descriptor Table Register (IDTR).
@param[out] Idtr Pointer to IA32_DESCRIPTOR structure to receive IDTR value.
**/
VOID
ReadIdtr (
OUT IA32_DESCRIPTOR *Idtr
)
{
if (Idtr == NULL) {
CpuInfoReportAssertion (
"e:\\hs\\MdePkg\\Library\\BaseLib\\X86ReadIdtr.c",
37,
"Idtr != ((void *) 0)"
);
return;
}
AsmReadIdtr (Idtr);
}
//========================================================================
// Memory Functions
//========================================================================
/**
Fills a memory buffer with a specified value.
@param[in] buf Pointer to buffer
@param[in] count Number of bytes to fill
@param[in] value Fill value
@return Pointer to buffer.
**/
VOID *
EFIAPI
Memset (
VOID *buf,
UINTN count,
CHAR8 value
)
{
return SetMem (buf, count, (UINT8)value);
}
/**
Fills a buffer with a 32-bit pattern.
@param[in] buf Pointer to buffer
@param[in] cnt Number of pairs (8 bytes per pair)
@param[in] val1 First DWORD value
@param[in] val2 Second DWORD value
@return Pointer to buffer.
**/
VOID *
EFIAPI
SetMem32 (
OUT VOID *buf,
IN UINTN cnt,
IN UINT32 val1,
IN UINT32 val2
)
{
UINTN Index;
UINT32 *Ptr;
Ptr = (UINT32*)buf;
for (Index = 0; Index < cnt; Index++) {
Ptr[Index * 2] = val1;
Ptr[Index * 2 + 1] = val2;
}
return buf;
}
/**
Copies a memory buffer, handling overlapping regions.
@param[out] dst Destination buffer
@param[in] src Source buffer
@param[in] count Number of bytes to copy
@return Pointer to destination buffer.
**/
VOID *
EFIAPI
CopyMem (
OUT VOID *dst,
IN CONST VOID *src,
IN UINTN count
)
{
UINT8 *Dst8;
UINT8 *Src8;
UINTN Count;
Dst8 = (UINT8*)dst;
Src8 = (UINT8*)src;
Count = count;
//
// Check for backwards overlap: if src < dst and src+count-1 >= dst
// copy from end to start.
//
if ((UINTN)Src8 < (UINTN)Dst8 && ((UINTN)Src8 + Count - 1) >= (UINTN)Dst8) {
//
// Overlapping, copy backward
//
Src8 = &Src8[Count - 1];
Dst8 = &Dst8[Count - 1];
while (Count--) {
*Dst8-- = *Src8--;
}
return dst;
}
//
// Non-overlapping: copy aligned DWORDs first, then remainder bytes
//
for (; Count >= 4; Count -= 4) {
*(UINT32*)Dst8 = *(UINT32*)Src8;
Dst8 += 4;
Src8 += 4;
}
while (Count--) {
*Dst8++ = *Src8++;
}
return dst;
}