Newer
Older
AMI-Aptio-BIOS-Reversed / PeiFrb / PeiFrb.c
@Ajax Dong Ajax Dong 2 days ago 10 KB Init
/** @file PeiFrb.c - PEI Fault Resilient Boot (FRB) module This PEIM implements Fault Resilient Booting (FRB) for the AMI IPMI stack. It detects the system reset type by querying the CMOS RTC Status Register A and FRB timeout status memory, then configures the FRB watchdog timeout in the ServerSetup NVRAM variable and installs an FRB notification PPI.

 Source: e:\hs\AmiIpmiPkg\Ipmi\PeiFrb\PeiFrb.c Module: PeiFrb.efi (IA32, ID: 0413)

 GUIDs used:
 gEfiFirmwareVolumeBlock2ProtocolGuid -- Locate FVB to read ServerSetup var gEfiPeiDebug2PpiGuid -- Debug output (EFI_PEI_DEBUG2_PPI)
 gPlatformSetupPpiGuid (AMI) -- Platform setup PPI interface gHiiConfigPpiGuid (AMI) -- HII config PPI (referenced by PPI descriptor)
 gServerSetupVariableGuid (AMI) -- ServerSetup NVRAM variable GUID GUIDs produced:
 gPeiFrbPpiGuid (PPI descriptor) -- Installed via InstallPpi Copyright (c) American Megatrends Inc. All rights reserved.
 SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include "PeiFrb.h"

//
// CMOS RTC I/O ports and register indices
//
#define RTC_INDEX_PORT 0x70
#define RTC_DATA_PORT 0x71
#define RTC_STATUS_A_REG 0x4A

//
// FRB timeout status memory-mapped IO address
//
#define FRB_TIMEOUT_MMIO ((volatile UINT8 *)0xFDAF0490)

//
// ServerSetup variable byte offsets
//
#define FRB_VAR_ENABLE_OFFSET 6 // FRB enable flag at offset 6 in ServerSetup data
#define FRB_VAR_TIMEOUT_BYTE 7 // Timer multiplier byte at offset 7

//
// PEI_SERVICES function table offsets (IA32 flat model)
//
#define PEI_SERVICES_LOCATEPPI 0x20 // Offset: LocatePpi
#define PEI_SERVICES_INSTALLPPI 0x24 // Offset: InstallPpi

//
// FRB default timeout: 3600 *100ms = 360 seconds = 6 minutes
//
#define FRB_TIMEOUT_DEFAULT 3600
#define FRB_TIMEOUT_SCALE 10 // Each unit = 100ms

//
// Local FRB configuration structure (6 bytes)
//
typedef struct {
 UINT8 Flags; // [0] FRB flags & enable UINT8 ResetType; // [1] Reset type bits UINT16 Reserved; // [2-3] Reserved UINT16 FrbTimeout; // [4-5] FRB timeout in 100ms units
} FRB_CONFIG;

//
// GUID external declarations (defined in .data section of the PE32)
//
extern EFI_GUID gEfiFirmwareVolumeBlock2ProtocolGuid;
extern EFI_GUID gEfiPeiDebug2PpiGuid;
extern EFI_GUID gPlatformSetupPpiGuid;
extern EFI_GUID gHiiConfigPpiGuid;
extern EFI_GUID gServerSetupVariableGuid;

//
// FRB PPI descriptor (installed before exit)
//
extern EFI_PEI_PPI_DESCRIPTOR gPeiFrbPpiDescriptor;

//
// Function prototypes
//
EFI_STATUS EFIAPI ModuleEntryPoint (
 IN EFI_HANDLE ImageHandle,
 IN EFI_SYSTEM_TABLE *SystemTable
 );

INT32 GetFrbSetupData (
 IN INT32 SystemTable,
 OUT FRB_CONFIG *Config,
 OUT BOOLEAN *Enabled
 );

INT32 GetDebugOutputInterface (
 VOID
 );

INT32 AssertHandler (
 IN INT32 Status,
 IN CHAR8 *Format,
 ...
 );

VOID DebugPrint (
 IN CHAR8 *FileName,
 IN INT32 LineNumber,
 IN CHAR8 *Message
 );

INT32 DetectResetType (
 VOID
 );

INT32 GetPeiServicesFromIdt (
 VOID
 );

VOID *EFIAPI ReadIdtr (
 OUT IA32_DESCRIPTOR *Idtr
 );

VOID *EFIAPI Memset (
 OUT VOID *Buffer,
 IN UINTN Count,
 IN UINT8 Value
 );

VOID *EFIAPI FillTable8 (
 OUT VOID *BaseAddress,
 IN INT32 Count,
 IN UINT32 ValueLow,
 IN UINT32 ValueHigh
 );

VOID *EFIAPI Memset32 (
 OUT VOID *Buffer,
 IN UINTN Count,
 IN UINT32 Value
 );

VOID *EFIAPI Memmove (
 OUT VOID *Destination,
 IN CONST VOID *Source,
 IN UINTN Count
 );

EFI_STATUS EFIAPI ModuleEntryPoint (
 IN EFI_HANDLE ImageHandle,
 IN EFI_SYSTEM_TABLE *SystemTable
 )
{
 EFI_PEI_SERVICES **PeiServices;
 INT32 DebugIntf;
 FRB_CONFIG FrbCfg;
 BOOLEAN FrbEnabled;
 EFI_STATUS Status;
 UINT8 Dummy;
 VOID *SetupPpi;

 PeiServices = (EFI_PEI_SERVICES **)SystemTable;

 //
 // Validate the PEI Services revision
 //
 if ((*PeiServices)->Hdr.Revision < PEI_SERVICES_REVISION) {
 DebugIntf = GetDebugOutputInterface ();
 if (DebugIntf != 0) {
 ((DEBUG_OUTPUT_INTERFACE *)DebugIntf)->DebugPrint (
 "e:\\hs\\MdePkg\\Library\\PeimEntryPoint\\PeimEntryPoint.c",
 46,
 "(*PeiServices)->Hdr.Revision >= _gPeimRevision"
 );
 }
 }

 //
 // Initialize FRB config structure: enabled=TRUE, all zeros
 //
 FrbEnabled = TRUE;
 ZeroMem (&FrbCfg, sizeof (FrbCfg));

 //
 // Locate the AMI PlatformSetup PPI
 //
 Status = (*PeiServices)->LocatePpi (
 PeiServices,
 &gPlatformSetupPpiGuid,
 0,
 NULL,
 &SetupPpi
 );
 if (!EFI_ERROR (Status)) {
 //
 // Read FRB setup configuration from ServerSetup NVRAM variable
 //
 GetFrbSetupData ((INT32)SystemTable, &FrbCfg, &FrbEnabled);

 if (FrbEnabled) {
 //
 // Configure FRB: set reset type bits to cold reset,
 // preserve timeout value from ServerSetup
 //
 FrbCfg.ResetType &= 0x0F;
 FrbCfg.Flags = (FrbCfg.Flags & 0x38) | 0x01; // Enable FRB, preserve timeout

 //
 // Write FRB config to NVRAM via platform setup PPI (SetVariable #36)
 //
 Dummy = 0;
 Status = ((PLATFORM_SETUP_PPI *)SetupPpi)->SetVariable (
 (INTN)SetupPpi,
 6,
 0,
 sizeof (FRB_CONFIG),
 &FrbCfg,
 6,
 0,
 &Dummy
 );
 if (!EFI_ERROR (Status)) {
 //
 // Clear the FRB reset-pending variable (SetVariable #34)
 //
 Dummy = 0;
 Status = ((PLATFORM_SETUP_PPI *)SetupPpi)->SetVariable (
 (INTN)SetupPpi,
 34,
 0,
 0,
 NULL,
 0,
 0,
 &Dummy
 );
 if (!EFI_ERROR (Status)) {
 //
 // Install FRB PPI to notify subsequent PEIMs
 //
 return (*PeiServices)->InstallPpi (
 PeiServices,
 &gPeiFrbPpiDescriptor
 );
 }
 }
 } else {
 //
 // FRB not enabled in ServerSetup
 //
 return EFI_INVALID_PARAMETER;
 }
 }

 return Status;
}

INT32 GetFrbSetupData (
 IN INT32 SystemTable,
 OUT FRB_CONFIG *Config,
 OUT BOOLEAN *Enabled
 )
{
 EFI_PEI_SERVICES **PeiServices;
 EFI_FV_BLOCK2_PROTOCOL *Fvb;
 EFI_PEI_READ_ONLY_VARIABLE2_PPI *VarPpi;
 EFI_STATUS Status;
 UINT32 VarSize;
 UINT8 VarData[8];
 INT32 Timeout;

 PeiServices = (EFI_PEI_SERVICES **)SystemTable;

 //
 // Locate Firmware Volume Block 2 protocol to access NVRAM
 //
 Status = (*PeiServices)->LocateProtocol (
 PeiServices,
 &gEfiFirmwareVolumeBlock2ProtocolGuid,
 0,
 NULL,
 (VOID **)&Fvb
 );
 if (EFI_ERROR (Status)) {
 //
 // FVB not available: assert and report error
 //
 AssertHandler (0x80000000, "\nASSERT_EFI_ERROR (Status = %r)\n", Status);
 {
 INT32 Dbg;

 Dbg = GetDebugOutputInterface ();
 if (Dbg != 0) {
 ((DEBUG_OUTPUT_INTERFACE *)Dbg)->DebugPrint (
 "e:\\hs\\AmiIpmiPkg\\Ipmi\\PeiFrb\\PeiFrb.c",
 298,
 "!EFI_ERROR (Status)"
 );
 }
 }
 }

 if (!EFI_ERROR (Status)) {
 //
 // Read ServerSetup variable (expect max 1072 bytes)
 //
 VarSize = 1072;
 Status = ((EFI_PEI_READ_ONLY_VARIABLE2_PPI *)Fvb)->GetVariable (
 (EFI_PEI_READ_ONLY_VARIABLE2_PPI *)Fvb,
 L"ServerSetup",
 &gServerSetupVariableGuid,
 NULL,
 &VarSize,
 VarData
 );
 if (!EFI_ERROR (Status)) {
 //
 // Extract FRB enable flag (byte offset 6 in ServerSetup data)
 //
 *Enabled = VarData[FRB_VAR_ENABLE_OFFSET];

 //
 // Compute timeout: 10 *byte at offset 7
 //
 Timeout = FRB_TIMEOUT_SCALE * (INT32)*(UINT32 *)&VarData[FRB_VAR_TIMEOUT_BYTE];
 goto SetFrbTimeout;
 }
 } else {
 Timeout = FRB_TIMEOUT_DEFAULT;
 }

 Timeout = FRB_TIMEOUT_DEFAULT;

SetFrbTimeout:
 Config->FrbTimeout = (UINT16)Timeout;
 return Timeout;
}

INT32 GetDebugOutputInterface (
 VOID
 )
{
 EFI_PEI_SERVICES **PeiServices;
 INT32 PpiInterface;
 INT32 PpiList;

 PeiServices = (EFI_PEI_SERVICES **)GetPeiServicesFromIdt ();

 //
 // Locate EFI_PEI_DEBUG2_PPI
 //
 if ((*PeiServices)->LocatePpi (
 (INTN)PeiServices,
 &gEfiPeiDebug2PpiGuid,
 0,
 &PpiInterface,
 &PpiList
 ) >= 0) {
 return PpiList;
 }

 return 0;
}

INT32 AssertHandler (
 IN INT32 Status,
 IN CHAR8 *Format,
 ...
 )
{
 INT32 Result;
 VA_LIST Args;

 VA_START (Args, Format);

 Result = GetDebugOutputInterface ();
 if (Result != 0) {
 Result = DetectResetType ();
 if ((Result & Status) != 0) {
 //
 // Only print assert if reset type allows it
 //
 return
 ((INT32 ( *)(INT32, const CHAR8 *, CHAR8 *))Result)(
 Status, Format, (CHAR8 *)Args
 );
 }
 }

 return Result;
}

VOID DebugPrint (
 IN CHAR8 *FileName,
 IN INT32 LineNumber,
 IN CHAR8 *Message
 )
{
 INT32 Result;

 Result = GetDebugOutputInterface ();
 if (Result != 0) {
 ((INT32 ( *)(INT32, INT32, INT32))(Result + 4))(
 (INT32)FileName,
 LineNumber,
 (INT32)Message
 );
 }
}

INT32 DetectResetType (
 VOID
 )
{
 UINT8 RtcIdx;
 UINT8 ResetValue;

 //
 // Save bit 7 of CMOS index, set index to Status Register A (0x4A)
 //
 RtcIdx = IoRead8 (RTC_INDEX_PORT);
 IoWrite8 (RTC_INDEX_PORT, (RtcIdx & 0x80) | RTC_STATUS_A_REG);

 //
 // Read the reset type value from CMOS data port
 //
 ResetValue = IoRead8 (RTC_DATA_PORT);

 //
 // Interpret the reset type:
 // 0 = Power-on (cold start)
 // 1 = Warm reset via FRB timeout logic
 // 2-3 = Reserved/cold reset
 // >3, then 0 = Check MMIO FRB timeout status
 //
 if ((UINT8)ResetValue <= 3) {
 //
 // Direct interpretation for values 0-3
 //
 if (ResetValue == 0) {
 return 0; // Power-on reset
 }
 } else {
 //
 // Out-of-range value: check FRB timeout via memory-mapped IO
 //
 if (ResetValue == 0) {
 ResetValue = (*FRB_TIMEOUT_MMIO & 0x02) | 0x01;
 }
 }

 if (ResetValue != 0xFF) {
 if (ResetValue == 1) {
 //
 // 0x80000004 = EFI_WARN_RESET_COLD? No, actually this is EFI_WARN_RESET_WARM
 // Return value: if ResetValue != 1 -> cold reset
 //
 return 0x80000004; // FRB reset: warm
 }
 return 0x80000046; // EFI_WARN_RESET_COLD / FRB reset: cold
 }

 return 0; // Unknown: assume power-on
}

INT32 GetPeiServicesFromIdt (
 VOID
 )
{
 IA32_DESCRIPTOR Idtr;
 INT32 *PeiServices;

 ReadIdtr (&Idtr);
 PeiServices = *(INT32 **)(*(INT32 *)&Idtr.Base - 4);

 if (PeiServices == NULL) {
 DebugPrint (
 (INT32)"e:\\hs\\MdePkg\\Library\\PeiServicesTablePointerLibIdt\\PeiServicesTablePointer.c",
 48,
 (INT32)"PeiServices != ((void *) 0)"
 );
 }

 return (INT32)PeiServices;
}

VOID *EFIAPI ReadIdtr (
 OUT IA32_DESCRIPTOR *Idtr
 )
{
 if (Idtr == NULL) {
 DebugPrint (
 (INT32)"e:\\hs\\MdePkg\\Library\\BaseLib\\X86ReadIdtr.c",
 37,
 (INT32)"Idtr != ((void *) 0)"
 );
 }

 __sidt (Idtr);
 return Idtr;
}

VOID *EFIAPI Memset (
 OUT VOID *Buffer,
 IN UINTN Count,
 IN UINT8 Value
 )
{
 return memset (Buffer, Value, Count);
}

VOID *EFIAPI FillTable8 (
 OUT VOID *BaseAddress,
 IN INT32 Count,
 IN UINT32 ValueLow,
 IN UINT32 ValueHigh
 )
{
 INT32 Index;

 Index = Count;
 do {
 ((UINT32 *)BaseAddress)[8 *Index - 2] = ValueLow;
 ((UINT32 *)BaseAddress)[8 *Index-- - 1] = ValueHigh;
 } while (Index != 0);

 return BaseAddress;
}

VOID *EFIAPI Memset32 (
 OUT VOID *Buffer,
 IN UINTN Count,
 IN UINT32 Value
 )
{
 return memset32 (Buffer, Value, Count);
}

VOID *EFIAPI Memmove (
 OUT VOID *Destination,
 IN CONST VOID *Source,
 IN UINTN Count
 )
{
 UINTN Remaining;
 CHAR8 *Dst;
 CHAR8 *Src;

 Remaining = Count;

 if ((Source < Destination) &&
 (&((CONST CHAR8 *)Source)[Count - 1] >= (CONST CHAR8 *)Destination)) {
 //
 // Overlapping: source comes before destination, copy backwards
 //
 Src = &((CHAR8 *)Source)[Count - 1];
 Dst = &((CHAR8 *)Destination)[Count - 1];
 } else {
 //
 // Non-overlapping: copy dword-aligned then tail bytes
 //
 Remaining = Count & 3;
 CopyMem (Destination, Source, 4 * (Count >> 2));
 Src = &((CHAR8 *)Source)[4 * (Count >> 2)];
 Dst = &((CHAR8 *)Destination)[4 * (Count >> 2)];
 }

 CopyMem (Dst, Src, Remaining);
 return Destination;
}