/**
* DxeIpmiBmcInitialize.c
*
* IPMI BMC Initialization DXE Driver
* Part of AMI IPMI Stack (AmiIpmiPkg)
*
* This module performs BMC initialization during DXE phase:
* - Locates/initializes KCS transport to the BMC
* - Runs BMC self test and publishes the BMC Self Test Protocol
* - Checks BMC firmware version (OEM-specific)
* - Installs SMBIOS Type 42 (Management Controller Description) record
* - Installs SPMI ACPI table (Server Platform Management Interface)
* - Handles IPMI CMOS clear and boot flag settings
* - Manages system information strings via IPMI
* - Configures BMC-related setup options
*
* Source path: AmiIpmiPkg\Ipmi\IpmiInitialize\DxeIpmiBmcInitialize.c
* Build: DEBUG_VS2015 X64
*/
#include "DxeIpmiBmcInitialize.h"
// ===========================================================================
// Module Entry Point
// ===========================================================================
EFI_STATUS
EFIAPI
_ModuleEntryPoint(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
ModuleEntryInit(ImageHandle, SystemTable);
Status = DxeIpmiBmcInitialize2(ImageHandle, SystemTable);
if (EFI_ERROR(Status)) {
DxeIpmiBmcUnload();
}
return Status;
}
// ===========================================================================
// Module Entry Init (AutoGen + Library Constructor)
// ===========================================================================
EFI_STATUS
ModuleEntryInit(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
// Store ImageHandle, gST, gBS, gRT
// Called from AutoGen.c constructor chain
gImageHandle = ImageHandle;
gST = SystemTable;
gBS = SystemTable->BootServices;
gRT = SystemTable->RuntimeServices;
return EFI_SUCCESS;
}
// ===========================================================================
// DxeIpmiBmcInitialize2 - Main Driver Entry
// ===========================================================================
EFI_STATUS
DxeIpmiBmcInitialize2(
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
UINTN SetupSize;
BMC_INSTANCE *BmcInstance;
UINT8 SetupData;
// Initialize UEFI services if not already done
if (gST == NULL) {
gST = SystemTable;
gBS = SystemTable->BootServices;
gRT = SystemTable->RuntimeServices;
}
// Check if ServerSetup variable exists (setup configuration is ready)
SetupSize = sizeof(BMC_INSTANCE);
Status = gRT->GetVariable(
L"ServerSetup",
&gSetupGuid,
NULL,
&SetupSize,
&gBmcSelfTestProtocolInstalled
);
if (EFI_ERROR(Status) && !gBmcSelfTestProtocolInstalled) {
return EFI_NOT_FOUND;
}
// Allocate BMC instance structure
BmcInstance = AllocateZeroPool(6, sizeof(BMC_INSTANCE));
if (BmcInstance == NULL) {
return EFI_OUT_OF_RESOURCES;
}
gBmcInstance = BmcInstance;
// Initialize BMC instance fields
BmcInstance->IoDataPort = 0xCA2; // KCS data IO port
BmcInstance->IoCtrlPort = 0xCA3; // KCS command IO port
BmcInstance->TimeoutMs = 0; // Will be set later
BmcInstance->InterfaceType = 1; // IO-mapped KCS
BmcInstance->Signature = BMC_INSTANCE_SIGNATURE;
BmcInstance->Revision = 0x20;
// Set transport function pointers (virtual methods)
BmcInstance->IpmiSubmitCommandNoResponse = BmcSendCommandNoResponse; // +0x130
BmcInstance->IpmiSubmitCommand = BmcSendCommand; // +0x138
BmcInstance->IpmiSubmitCommandWithResponse = BmcSendCommandWithResponse; // +0x140
// Run BMC self test
Status = IpmiBmcSelfTest();
if (EFI_ERROR(Status)) {
FreePool(BmcInstance);
return EFI_NOT_FOUND;
}
// Register protocol notification for SMBIOS protocol
EfiCreateProtocolNotifyEvent(
&gSmbiosProtocolGuid,
8, // TPL_CALLBACK
OemCheckBmcVersion,
0,
&gBmcSelfTestProtocolGuid
);
return EFI_SUCCESS;
}
// ===========================================================================
// DxeIpmiBmcUnload - Unload Handler
// ===========================================================================
EFI_STATUS
DxeIpmiBmcUnload(
VOID
)
{
// Close all protocol handles opened by this module
// Free allocated memory, uninstall protocols
gBS->CloseProtocol(gBmcSelfTestProtocolGuid, gBS->ImageHandle, NULL);
gBS->CloseProtocol(gSmbiosProtocolGuid, gBS->ImageHandle, NULL);
gBS->CloseProtocol(gAcpiTableProtocolGuid, gBS->ImageHandle, NULL);
// Free BMC self test protocol if installed
if (gBmcSelfTestProtocol != NULL) {
FreePool(gBmcSelfTestProtocol);
}
// Free runtime services references, clean up
gBS->CloseProtocol(gBmcSelfTestProtocolGuid, gBS->ImageHandle, NULL);
gBS->CloseProtocol(gBmcSelfTestProtocolGuid, gBS->ImageHandle, NULL);
// Close protocol on ImageHandle for all registered protocols
gBS->CloseProtocol(gBmcSelfTestProtocolGuid, gBS->ImageHandle, NULL);
gBS->CloseProtocol(gBmcSelfTestProtocolGuid, gBS->ImageHandle, NULL);
return EFI_SUCCESS;
}
// ===========================================================================
// IpmiBmcSelfTest - Run BMC Self Test and Install Protocol
// ===========================================================================
EFI_STATUS
IpmiBmcSelfTest(
VOID
)
{
EFI_STATUS Status;
UINT8 CompletionCode;
UINT8 SelfTestResult;
UINT8 ResponseData[15];
UINT8 ResponseSize;
UINT8 RetryCount;
UINT8 SelfTestCodes[20];
UINT8 CodeCount;
UINT8 i;
BMC_INSTANCE *BmcInstance;
BMC_SELF_TEST_PROTOCOL *Protocol;
BmcInstance = gBmcInstance;
CodeCount = 0;
// Retry loop for BMC self test command
RetryCount = 0;
do {
Status = BmcSendCommandNoResponse(
BMC_INSTANCE,
6, 0, 0,
0, NULL, sizeof(SelfTestResult), &SelfTestResult
);
if (!EFI_ERROR(Status)) {
break;
}
RetryCount++;
} while (RetryCount < 2);
DebugPrint(64, "Self test result Status: %r\n", Status);
if (EFI_ERROR(Status)) {
// Log failure code
if (CodeCount < 20) {
SelfTestCodes[CodeCount++] = 0x21002; // Self test failure
}
BmcInstance->SelfTestResult = 2; // Error
goto Done;
}
// Interpret self test result byte
if (SelfTestResult < 0x55) {
// Non-specific failure
BmcInstance->SelfTestResult = 2;
} else if (SelfTestResult == 0x55) {
// Success (no errors)
BmcInstance->SelfTestResult = 0;
} else if (SelfTestResult == 0x56) {
// Success (no errors)
BmcInstance->SelfTestResult = 0;
} else if (SelfTestResult == 0x57) { // 87 decimal
// Self test with warnings
BmcInstance->SelfTestResult = 1;
// Process warning bits
for (i = 0; i < 8; i++) {
if (!(ResponseData[1] & (1 << i))) {
break;
}
if (CodeCount < 20) {
SelfTestCodes[CodeCount++] = 0x21001; // Warning
}
}
} else if (SelfTestResult == 0xFF) {
// Self test not implemented (consider success)
BmcInstance->SelfTestResult = 0;
} else {
BmcInstance->SelfTestResult = 2;
}
// Record result codes
if (BmcInstance->SelfTestResult == 2) {
if (CodeCount < 20) {
SelfTestCodes[CodeCount++] = 0x21000; // General error
}
} else if (BmcInstance->SelfTestResult == 1 && CodeCount < 20) {
SelfTestCodes[CodeCount++] = 0x21001; // Warning
}
// Install protocol notification if status is acceptable
if (BmcInstance->SelfTestResult <= 1) {
EfiCreateProtocolNotifyEvent(
&gBmcSelfTestProtocolGuid,
8,
BmcSelfTestProtocolCallback,
0,
&gBmcSelfTestProtocolGuid
);
// Send Get Device ID to retrieve BMC identification
ResponseSize = sizeof(ResponseData);
Status = BmcSendCommandWithResponse(
BMC_INSTANCE,
6, IPMI_CMD_GET_DEVICE_ID,
0, NULL, 0,
ResponseData, &ResponseSize,
&CompletionCode
);
if (!EFI_ERROR(Status) && (INT8)ResponseData[2] >= 0) {
// Valid response
} else {
// Error case
}
// Allocate and install BMC Self Test Protocol
Protocol = AllocateZeroPool(4, sizeof(BMC_SELF_TEST_PROTOCOL));
if (Protocol == NULL) {
InternalAssert(
"e:\\hs\\AmiIpmiPkg\\Ipmi\\IpmiInitialize\\DxeIpmiBmcInitialize.c",
1237,
"BmcSelfTestProtocol != ((void *) 0)"
);
goto Done;
}
gBmcSelfTestProtocol = Protocol;
if (EFI_ERROR(Status)) {
Protocol->IsSelfTestOk = 0;
} else {
Protocol->IsSelfTestOk = 1;
CopyMem(Protocol->BmcId, ResponseData, sizeof(Protocol->BmcId));
}
Protocol->Count = CodeCount;
Protocol->BmcStatus = BmcInstance->SelfTestResult;
CopyMem(Protocol->Codes, SelfTestCodes, CodeCount * sizeof(UINT32));
Protocol->Count = CodeCount;
// Install protocol
Status = gBS->InstallProtocolInterface(
&gImageHandle,
&gBmcSelfTestProtocolGuid,
EFI_NATIVE_INTERFACE,
Protocol
);
if (EFI_ERROR(Status)) {
DebugPrint(0x80000000, "\nASSERT_EFI_ERROR (Status = %r)\n", Status);
InternalAssert(
"e:\\hs\\AmiIpmiPkg\\Ipmi\\IpmiInitialize\\DxeIpmiBmcInitialize.c",
1262,
"!EFI_ERROR (Status)"
);
FreePool(Protocol);
} else {
gBmcSelfTestProtocolInstalled = 1;
}
}
Done:
// Log self test codes via status code if interface available
for (i = 0; i < CodeCount; i++) {
// Report status code for each self test code
if (gBmcSelfTestProtocolInstalled) {
// gReportStatusCodeProtocol->ReportStatusCode(0x40000002, SelfTestCodes[i], 0, NULL, 0);
}
}
// Install SPMI ACPI table if enabled
if (/* SpmiEnabled */ 1) {
// Register notification for ACPI table protocol
gBS->RegisterProtocolNotify(
&gAcpiTableProtocolGuid,
8,
SpmiInstallAcpiTable
);
}
// Initialize system information
EfiCreateProtocolNotifyEvent(
&gSetupGuid,
8,
OemBmcInterfaceInit,
0,
&gBmcSelfTestProtocolGuid
);
// Set IPMI boot flags
IpmiSetBootFlags();
// Configure BMC via setup options
// Read Setup variables to configure BMC behavior
SetupData = 0;
// Update firmware version info, set boot flags
// ...
// Install BMC instance protocol
gBS->InstallProtocolInterface(
&gImageHandle,
&gBmcSelfTestProtocolGuid,
EFI_NATIVE_INTERFACE,
BmcInstance
);
// Register SMM/SMI callback for BMC communication
gBS->RegisterProtocolNotify(
&gBmcSelfTestProtocolGuid,
16,
BmcUninstallProtocols
);
return Status;
}
// ===========================================================================
// BmcSelfTestProtocolCallback - Protocol Installation Callback
// ===========================================================================
VOID
BmcSelfTestProtocolCallback(
VOID
)
{
// Callback invoked when BmcSelfTestProtocol is installed
// Used to notify other drivers that BMC self test is complete
}
// ===========================================================================
// OemCheckBmcVersion - OEM BMC Version Checking
// ===========================================================================
EFI_STATUS
OemCheckBmcVersion(
VOID
)
{
EFI_STATUS Status;
EFI_SMBIOS_PROTOCOL *SmbiosProtocol;
UINT8 BmcVersion;
DebugPrint(64, "OemCheckBMCVer\n");
// Locate SMBIOS protocol
Status = gBS->LocateProtocol(
&gSmbiosProtocolGuid,
NULL,
&SmbiosProtocol
);
if (EFI_ERROR(Status)) {
DebugPrint(64, "LocateProtocol Fail\n");
return Status;
}
// Send Get Device ID IPMI command
BmcVersion = 0;
Status = SendIpmiCommandRaw(
IPMI_NETFN_APP,
IPMI_CMD_GET_DEVICE_ID,
NULL, 0,
&BmcVersion, sizeof(BmcVersion)
);
if (EFI_ERROR(Status)) {
DebugPrint(64, "SendIpmiCommand Fail\n");
return Status;
}
DebugPrint(64, "BmcVer is %d.%x\n", BmcVersion >> 4, BmcVersion & 0x0F);
return EFI_SUCCESS;
}
// ===========================================================================
// SmbiosInstallType42Record - Install SMBIOS Type 42 Record
// ===========================================================================
EFI_STATUS
SmbiosInstallType42Record(
VOID
)
{
EFI_STATUS Status;
EFI_SMBIOS_PROTOCOL *SmbiosProtocol;
SMBIOS_STRUCTURE_POINTER SmbiosRecord;
UINT8 *StringBuffer;
UINTN BufferSize;
DebugPrint(64,
"gBS->LocateProtocol gEfiSmbiosProtocolGuid protocol status %r\n",
Status
);
// Locate SMBIOS protocol
Status = gBS->LocateProtocol(
&gSmbiosProtocolGuid,
NULL,
&SmbiosProtocol
);
if (EFI_ERROR(Status)) {
// Allocate memory for Type 42 record
StringBuffer = AllocateZeroPool(4, 20);
if (StringBuffer != NULL) {
StringBuffer[0] = 0xFFFF26F6; // Type 42 magic
// Add via SMBIOS protocol add function
Status = SmbiosProtocol->Add(SmbiosProtocol, NULL, &SmbiosRecord, StringBuffer);
if (EFI_ERROR(Status)) {
FreePool(StringBuffer);
}
// Now re-read to get the actual record
SmbiosProtocol->GetNext(SmbiosProtocol, &SmbiosRecord.Hdr, &SmbiosRecord.Hdr);
}
// Get string by index and report
// (SMBIOS string index 0x26)
}
// Get string information from SMBIOS
// and set up Type 42 record fields
DebugPrint(64,
"SmbiosProtocol->Add Type42Record.... Status: %r \n",
Status
);
return Status;
}
// ===========================================================================
// SpmiInstallAcpiTable - Install SPMI ACPI Table
// ===========================================================================
EFI_STATUS
SpmiInstallAcpiTable(
EFI_ACPI_TABLE_PROTOCOL *AcpiTableProtocol
)
{
EFI_STATUS Status;
EFI_ACPI_TABLE_PROTOCOL *AcpiTable;
UINT8 *SpmiTable;
BMC_SELF_TEST_PROTOCOL *SelfTestProtocol;
UINT8 BmcRevision;
UINT8 I2cAddress;
// Locate ACPI Table protocol
Status = gBS->LocateProtocol(
&gAcpiTableProtocolGuid,
NULL,
&AcpiTable
);
DebugPrint(64, "Locate ACPI Table protocol Status: %r \n", Status);
if (EFI_ERROR(Status)) return Status;
// Allocate SPMI table (65 bytes)
Status = gBS->AllocatePool (EfiBootServicesData, 65, &SpmiTable);
if (EFI_ERROR(Status) || SpmiTable == NULL) {
InternalAssert(__FILE__, __LINE__, "SpmiTable");
return Status;
}
// Initialize SPMI table header
CopyMem (SpmiTable, SPMI_SIGNATURE, 4); // "SPMIA"
CopyMem (SpmiTable + 10, SPMI_OEM_ID, 6); // "LENOVO"
CopyMem (SpmiTable + 16, "SV-INT ", 8);
*(UINT32*)(SpmiTable + 28) = 0x2E484352; // Reserved area
*(SpmiTable + 36) = 1; // Interface Type: KCS
// Set BMC interface address (from self-test protocol)
*(UINT64*)(SpmiTable + 52) = 0xCA2; // KCS data port
// Get BMC revision (from Get Device ID result)
SelfTestProtocol = gBmcSelfTestProtocol;
if (SelfTestProtocol != NULL) {
*(UINT16*)(SpmiTable + 38) = SelfTestProtocol->BmcId; // BMC revision
}
// Update SRVV ASL object if needed
Status = AcpiGetDsdtTable (&AcpiTable);
if (EFI_ERROR(Status)) {
DebugPrint(64, "IPMI:Failed to get DSDT\n");
} else {
// Update SRVV name in DSDT
Status = AcpiUpdateSrvvTable (SpmiTable, SpmiTable + 16, 8);
if (EFI_ERROR(Status)) {
DebugPrint(64, "IPMI:Failed to update SRVV ASL Object\n");
}
}
// Install SPMI ACPI table
Status = AcpiTable->InstallAcpiTable (
AcpiTable,
SpmiTable,
65
);
DebugPrint(64, "AcpiTable->InstallAcpiTable(SPMI) = %r\n", Status);
return Status;
}
// ===========================================================================
// IpmiSetBootFlags - Set IPMI Boot Options Flags
// ===========================================================================
VOID
IpmiSetBootFlags(
VOID
)
{
EFI_STATUS Status;
UINTN SetupVarSize;
UINT8 BootOverride;
UINT8 BootOverrideTarget;
UINT8 CmosClearData;
UINT8 ResponseData[4];
UINT8 ResponseSize;
UINT8 CompletionCode;
UINT16 BootFlags;
//
// Read Setup variables to determine boot override
//
SetupVarSize = 0;
// ... determine boot flags from setup
//
// Check IPMI CMOS clear variable
//
Status = gRT->GetVariable (
L"IpmiCmosClear",
&gBmcSelfTestProtocolGuid,
NULL,
&CmosClearData,
sizeof(CmosClearData)
);
//
// Send IPMI Set Boot Options command if needed
//
CompletionCode = 0;
BootFlags = 0;
Status = BmcSendCommandWithResponse (
6,
8, // Set Boot Options
(UINT8*)&BootFlags, sizeof(BootFlags),
ResponseData, &ResponseSize,
&CompletionCode
);
if (EFI_ERROR(Status)) {
DebugPrint(64, "%a Command Completion code: %x, Status%r\n",
"IpmiCmosClear", CompletionCode, Status);
}
}
// ===========================================================================
// AddSystemInfoStr - Add System Information String via IPMI
// ===========================================================================
EFI_STATUS
AddSystemInfoStr(
UINT8 *StringBuffer,
UINTN StringLength
)
{
UINTN DataSize;
UINT8 BlockNumber;
EFI_STATUS Status;
// SMBIOS string structure: StringInfoHeader + data
// Sends IPMI Set System Info command in blocks of 16 bytes
if (StringBuffer == NULL) {
DebugPrint(0x80000000, "%a String buffer pointer is NULL\n", "AddSystemInfo");
return EFI_INVALID_PARAMETER;
}
DataSize = AsciiStrLen(StringBuffer);
if (DataSize < 3 || DataSize > 255) {
DebugPrint(0x80000000,
"%a String length is greater or Lesser than recommended string length\n",
"AddSystemInfo");
return EFI_INVALID_PARAMETER;
}
//
// Send Set System Info command with parameters.
// Each block is up to 16 bytes.
//
BlockNumber = 0;
while (DataSize > 0) {
UINTN BlockSize;
UINT8 SetInfoRequest[18];
SetInfoRequest[0] = 1; // Set String
SetInfoRequest[1] = BlockNumber; // Block number
if (BlockNumber == 0) {
// First block: include string length in header
SetInfoRequest[2] = (UINT8)DataSize; // Total string length
BlockSize = MIN(DataSize, 14);
} else {
BlockSize = MIN(DataSize, 16);
}
CopyMem (&SetInfoRequest[2 + (BlockNumber == 0 ? 2 : 0)],
StringBuffer, BlockSize);
Status = BmcSendCommand (
IPMI_NETFN_STORAGE,
IPMI_CMD_SET_SYSTEM_INFO,
SetInfoRequest,
2 + 1 + BlockSize,
...
);
if (EFI_ERROR(Status)) {
return Status;
}
if (BlockSize < 16) {
// If this block is smaller than 16, write rest as zero
}
DataSize -= BlockSize;
StringBuffer += BlockSize;
BlockNumber++;
}
return EFI_SUCCESS;
}
// ===========================================================================
// SmbiosGetStringByIndex - Get SMBIOS String by Index
// ===========================================================================
EFI_STATUS
SmbiosGetStringByIndex(
IN SMBIOS_PROTOCOL *SmbiosProtocol,
IN UINT8 StringIndex
)
{
EFI_STATUS Status;
SMBIOS_STRUCTURE_POINTER SmbiosRecord;
SMBIOS_HANDLE Handle;
UINT8 EntryCount;
Handle = -2;
Status = SmbiosProtocol->GetNext (
SmbiosProtocol,
&Handle,
&SmbiosRecord.Hdr
);
if (Status == EFI_NOT_FOUND) {
DebugPrint(64, "ERROR: Type 0 Entry is not available in SM BIOS table \n");
return EFI_NOT_FOUND;
}
EntryCount = SmbiosRecord.Type0->Vendor;
DebugPrint(64, "SmbiosGetStringByIndex - %d Entry \n", EntryCount);
//
// Navigate to the Nth string
//
UINTN Offset = (UINTN)SmbiosRecord.Raw + SmbiosRecord.Hdr->Length;
for (UINTN i = 0; i < StringIndex && *((CHAR8*)Offset) != 0; i++) {
Offset += AsciiStrLen((CHAR8*)Offset) + 1;
}
if (i != StringIndex || AsciiStrLen((CHAR8*)Offset) == 0) {
DebugPrint(64, "SmbiosGetStringByIndex - NOT_FOUND \n");
return EFI_NOT_FOUND;
}
DebugPrint(64, "SmbiosGetStringByIndex - SUCCESS String: %a \n", Offset);
//
// Call SetSystemInfo to report the string to IPMI
//
Status = AddSystemInfoStr((UINT8*)Offset, AsciiStrLen((CHAR8*)Offset));
if (EFI_ERROR(Status)) {
DebugPrint(64, "IPMI SmbiosGetStringByIndex status :%r\n", Status);
DebugPrint(64, "IPMI SetSysteminfo status :%r\n", Status);
}
return Status;
}
// ===========================================================================
// IPMI KCS (Keyboard Controller Style) Transport
// ===========================================================================
/**
* Wait for KCS controller ready or timeout.
* Checks for IBF=0 (ready to write) or OBF=1 (response available).
*
* @param[in] Bmc BMC instance
* @param[in] State Desired KCS state
* @param[out] Status Current status byte
*
* @retval EFI_SUCCESS Ready
* @retval EFI_TIMEOUT KCS timeout
* @retval EFI_DEVICE_ERROR KCS error state
*/
STATIC
EFI_STATUS
BmcKcsReadyCheck (
IN BMC_INSTANCE *Bmc,
IN UINT8 State,
OUT UINT8 *Status
)
{
UINT16 Timeout;
UINT8 KcsStatus;
Timeout = Bmc->TimeoutMs;
if (Bmc->InterfaceType == 1) {
KcsStatus = __inbyte(Bmc->IoCtrlPort);
} else {
KcsStatus = *(volatile UINT8*)(UINTN)Bmc->IoDataAddr;
}
*Status = KcsStatus;
if ((KcsStatus & KCS_STATE_MASK) == KCS_ERROR_STATE) {
return BmcKcsErrorRecovery(Bmc);
}
//
// Check for target state
//
if (State == 1) {
// Wait for IBF=0 and no SMS_ATN
while ((KcsStatus & KCS_IBF) != 0) {
if (Timeout-- == 0) return EFI_TIMEOUT;
MicroSecondDelay(100);
if (Bmc->InterfaceType == 1) {
KcsStatus = __inbyte(Bmc->IoCtrlPort);
} else {
KcsStatus = *(volatile UINT8*)(UINTN)Bmc->IoDataAddr;
}
}
} else if (State == 2) {
// Wait for OBF=1
while ((KcsStatus & KCS_OBF) == 0) {
if (Timeout-- == 0) return EFI_TIMEOUT;
MicroSecondDelay(100);
if (Bmc->InterfaceType == 1) {
KcsStatus = __inbyte(Bmc->IoCtrlPort);
} else {
KcsStatus = *(volatile UINT8*)(UINTN)Bmc->IoDataAddr;
}
}
}
//
// Check for error state again after wait
//
if ((KcsStatus & KCS_STATE_MASK) == KCS_ERROR_STATE) {
return BmcKcsErrorRecovery(Bmc);
}
return EFI_SUCCESS;
}
/**
* KCS Error Recovery (sub_36B8).
* Attempts to recover from KCS error state by aborting the operation.
*/
STATIC
EFI_STATUS
BmcKcsErrorRecovery (
IN BMC_INSTANCE *Bmc
)
{
UINT16 Timeout;
UINT8 Status;
//
// Write ABORT to the command port
//
if (Bmc->InterfaceType == 1) {
__outbyte(Bmc->IoCtrlPort, 0x60); // KCS ABORT
} else {
*(volatile UINT8*)(UINTN)Bmc->IoDataAddr = 0x60;
}
//
// Wait for IBF clear
//
Timeout = Bmc->TimeoutMs;
do {
MicroSecondDelay(100);
if (Bmc->InterfaceType == 1) {
Status = __inbyte(Bmc->IoCtrlPort);
} else {
Status = *(volatile UINT8*)(UINTN)Bmc->IoDataAddr;
}
if ((Status & KCS_IBF) == 0) break;
} while (Timeout-- > 0);
//
// Read the data port to clear OBF
//
if (Bmc->InterfaceType == 1) {
__inbyte(Bmc->IoDataPort);
} else {
*(volatile UINT8*)(UINTN)(Bmc->IoDataAddr + Bmc->IoCmdOffset);
}
return EFI_DEVICE_ERROR;
}
/**
* KCS Write-Read Cycle (sub_3A30).
* Writes the request bytes via KCS protocol, then reads the response.
*/
STATIC
EFI_STATUS
BmcKcsWriteRead (
IN BMC_INSTANCE *Bmc,
IN UINT8 *Request,
IN UINT8 RequestSize,
OUT UINT8 *Response,
OUT UINT8 *ResponseSize
)
{
EFI_STATUS Status;
UINT8 KcsStatus;
UINT16 Timeout;
//
// Write Phase: Send each byte of request
//
for (UINTN i = 0; i < RequestSize; i++) {
Status = BmcKcsReadyCheck(Bmc, 1, &KcsStatus); // Wait for IBF=0 (write ready)
if (EFI_ERROR(Status)) return Status;
if (Bmc->InterfaceType == 1) {
__outbyte(Bmc->IoDataPort, Request[i]);
} else {
*(volatile UINT8*)(UINTN)Bmc->IoDataAddr = Request[i];
}
}
//
// Read Phase: Read response bytes
//
for (UINTN i = 0; i < *ResponseSize; i++) {
Status = BmcKcsReadyCheck(Bmc, 2, &KcsStatus); // Wait for OBF=1 (read ready)
if (EFI_ERROR(Status)) return Status;
if (Bmc->InterfaceType == 1) {
Response[i] = __inbyte(Bmc->IoDataPort);
} else {
Response[i] = *(volatile UINT8*)(UINTN)Bmc->IoDataAddr;
}
}
return EFI_SUCCESS;
}
/**
* KCS Transport - Full IPMI Message via KCS (sub_2C58).
*
* Sends an IPMI command through the KCS interface.
* Handles KCS state machine: IDLE->WRITE_START->WRITE->READ->IDLE.
*/
STATIC
EFI_STATUS
BmcKcsTransport (
IN UINT8 NetFn,
IN UINT8 LunCmd,
IN UINT8 *DataBuffer,
IN UINT8 DataSize,
OUT UINT8 *ResponseData,
OUT UINT8 *ResponseSize,
OUT UINT8 *CompletionCode,
IN UINT8 Flags
)
{
EFI_STATUS Status;
BMC_INSTANCE *Bmc;
UINT8 Request[64];
UINT8 RequestLen;
UINT8 Response[64];
UINT8 ResponseLen;
Bmc = gBmcInstance;
//
// Build KCS request:
// [0] = NetFn/LUN (LUN<<2 | NetFn)
// [1] = Command
// [2..] = Data
// Last = Checksum (NetFn + Command + Data, 2's complement)
//
RequestLen = 0;
Request[RequestLen++] = (NetFn & 0xFC) | Bmc->LUN;
Request[RequestLen++] = LunCmd;
if (DataSize > 0) {
CopyMem (&Request[RequestLen], DataBuffer, DataSize);
RequestLen += (UINT8)DataSize;
}
//
// Send via KCS
//
ResponseLen = sizeof(Response);
Status = BmcKcsWriteRead (Bmc, Request, RequestLen, Response, &ResponseLen);
if (EFI_ERROR(Status)) {
return Status;
}
//
// Parse response:
// [0] = NetFn/LUN (response)
// [1] = Command
// [2] = Completion Code
// [3..] = Response Data
//
if (CompletionCode != NULL) {
*CompletionCode = Response[2];
}
UINT8 DataLen = ResponseLen - 3;
if (ResponseData != NULL && DataLen > 0) {
CopyMem (ResponseData, &Response[3], MIN(DataLen, *ResponseSize));
*ResponseSize = DataLen;
}
return EFI_SUCCESS;
}
//=============================================================================
// System Information Wait Function (sub_3BB8)
//=============================================================================
/**
* Wait for the System Info Set In Progress flag to clear.
* IPMI System Info has a SetInProgress byte that must be written
* 0x00 first, then data, then 0xFF. This function polls until clear.
*/
STATIC
EFI_STATUS
WaitSystemInfoInProgressClear (
VOID
)
{
UINT8 SetInProgress;
UINT8 Request[3];
UINT8 Response[1];
UINT8 ResponseSize;
UINT8 CompletionCode;
EFI_STATUS Status;
//
// Get Set In Progress status from BMC
//
Request[0] = 0x00; // Parameter selector: Set In Progress
ResponseSize = sizeof(Response);
Status = BmcKcsTransport (
IPMI_NETFN_STORAGE,
IPMI_CMD_GET_SYSTEM_INFO,
Request, 1,
Response, &ResponseSize,
&CompletionCode,
0
);
if (!EFI_ERROR(Status)) {
SetInProgress = Response[0];
while (SetInProgress != 0) {
// Wait
MicroSecondDelay(100);
// Re-read
Status = BmcKcsTransport (
IPMI_NETFN_STORAGE,
IPMI_CMD_GET_SYSTEM_INFO,
Request, 1,
Response, &ResponseSize,
&CompletionCode,
0
);
if (EFI_ERROR(Status)) break;
SetInProgress = Response[0];
}
}
DebugPrint(64, "IPMI IpmiSysteminfoWaitSetInProgressClear status :%r\n", Status);
return Status;
}
// ===========================================================================
// ACPI Helpers
// ===========================================================================
/**
* Locate the ACPI table protocol get the DSDT table (sub_4838).
*/
STATIC
EFI_STATUS
AcpiGetDsdtTable (
OUT EFI_ACPI_TABLE_PROTOCOL **AcpiTableProtocol
)
{
EFI_STATUS Status;
Status = gBS->LocateProtocol (
&gEfiAcpiTableProtocolGuid,
NULL,
AcpiTableProtocol
);
DebugPrint(64, "AcpiResLib: LibGetDsdt(): LocateProtocol(ACPISupport) returned %r \n", Status);
return Status;
}
/**
* Find the SRVV object in the DSDT AML byte stream (sub_47C8).
* SRVV is a vendor-defined ACPI name for IPMI firmware version.
*/
STATIC
EFI_STATUS
AcpiFindSrvvObject (
IN UINT8 *Dsdt,
IN UINTN DsdtSize,
OUT UINTN *SrvvOffset
)
{
//
// Walk AML byte code to find NAME ("SRVV", ...) or ("SVTT", ...)
//
// SRVV = 0x53525656 in AML name format
// SVTT = ...
//
// Implementation searches for AML NameOp + String "SRVV"
// ...
return EFI_NOT_FOUND;
}
/**
* Update SRVV name value in DSDT AML (sub_46F4, sub_4AA8, sub_4A50 etc).
*/
STATIC
EFI_STATUS
AcpiUpdateSrvvObject (
IN UINT8 *Dsdt,
IN UINTN DsdtSize,
IN UINTN CurrentOffset,
OUT UINTN *NewSize
)
{
// ...
return EFI_SUCCESS;
}
// ===========================================================================
// BMC Instance Callback: BmcSendCommand (sub_2FA0)
// ===========================================================================
/**
* Send an IPMI command to the BMC.
* This is the high-level BmcSendCommand function that validates the
* BMC instance signature and delegates to KcsTransport.
*/
EFI_STATUS
BmcSendCommand (
IN BMC_INSTANCE *BmcInstance,
IN UINT8 *Request,
IN UINT8 RequestSize,
OUT UINT8 *Response,
OUT UINT8 *ResponseSize
)
{
BMC_INSTANCE *Bmc;
UINT8 CompletionCode;
UINT8 LocalResponse[24];
UINT8 LocalResponseSize;
//
// CR-style signature check (BMC instance begins with Signature)
//
Bmc = CR (BmcInstance, BMC_INSTANCE_SIGNATURE);
ASSERT (Bmc != NULL);
//
// Prepare command: NetFn + Command + data
//
LocalResponseSize = sizeof(LocalResponse);
return BmcKcsTransport (
Request[0] & 0xFC, // NetFn
Request[1], // Command
&Request[2], // Data
RequestSize - 2,
Response, ResponseSize,
&CompletionCode,
0
);
}
/**
* Send IPMI command with no response data expected (sub_2F0C).
*/
EFI_STATUS
BmcSendCommandNoResponse (
...
)
{
return BmcKcsTransport (..., 0);
}
/**
* Send IPMI command and wait for response (sub_2F54).
*/
EFI_STATUS
BmcSendCommandWithResponse (
...
)
{
return BmcKcsTransport (..., 2);
}
// ===========================================================================
// OEM BMC Interface Initialization (sub_18A0)
// ===========================================================================
/**
* Initialize OEM-specific BMC interface.
* Reads Lenovo-specific configuration from Setup and applies it
* to the BMC.
*/
STATIC
EFI_STATUS
OemBmcInterfaceInit (
VOID
)
{
EFI_STATUS Status;
UINTN SetupVarSize;
UINT8 SetupData;
UINT8 FirmwareVersion;
UINT8 ResponseData[4];
UINT8 ResponseSize;
UINT8 CompletionCode;
//
// Read firmware version from setup
//
SetupVarSize = 0;
//
// Send firmware information to BMC (Get Device ID + OEM fields)
//
return EFI_SUCCESS;
}
// ===========================================================================
// USB Boot Setting Handler (sub_30A8)
// ===========================================================================
STATIC
EFI_STATUS
HandleUsbBootSetting (
VOID
)
{
// ...
return EFI_SUCCESS;
}
// ===========================================================================
// Setup Browser Protocol Callback (sub_3148)
// ===========================================================================
/**
* Register callback for Setup Browser protocol notification.
*/
STATIC
VOID
SetupBrowserCallback (
VOID
)
{
// ...
}
// ===========================================================================
// BMC Self Test: Notify Callback (sub_1D30)
// ===========================================================================
VOID
BmcSelfTestProtocolCallback (
VOID
)
{
// Callback - BMC self test protocol was installed.
// Other modules can wait on this event.
}
// ===========================================================================
// BmcUninstallProtocols - Cleanup callbacks (sub_1834)
// ===========================================================================
/**
* Uninstall BMC protocols via RT->SetVariable or similar cleanup.
* Called during driver unload.
*/
STATIC
VOID
BmcUninstallProtocols (
VOID
)
{
gRT->SetVariable (0, &gBmcInstance->Signature, sizeof(gBmcInstance->Signature));
gRT->SetVariable (0, &gBmcInstance->TimeoutMs, sizeof(gBmcInstance->TimeoutMs));
gRT->SetVariable (0, &gBmcInstance, sizeof(gBmcInstance));
}
// ===========================================================================
// Utility Functions
// ===========================================================================
UINTN
AsciiStrLen (
IN CONST CHAR8 *String
)
{
CONST CHAR8 *Ptr = String;
while (*Ptr != '\0') Ptr++;
return Ptr - String;
}
UINTN
StrLen (
IN CONST CHAR16 *String
)
{
CONST CHAR16 *Ptr = String;
while (*Ptr != L'\0') Ptr++;
return Ptr - String;
}
VOID
MicroSecondDelay (
IN UINTN MicroSeconds
)
{
//
// Delay via I/O port 0x70/0x71 RTC polling
// Uses RTC register 0x4B to check timer
//
volatile UINTN Count;
Count = MicroSeconds;
while (Count--) {
CpuPause();
}
}
UINT8
BcdToDecimal (
IN UINT8 BcdValue
)
{
return (BcdValue >> 4) * 10 + (BcdValue & 0x0F);
}
// ===========================================================================
// IPMI Raw Command (sub_4628)
// ===========================================================================
/**
* Send a raw IPMI command and get response.
* Wrapper around BmcKcsTransport.
*/
EFI_STATUS
SendIpmiCommandRaw (
IN UINT8 NetFn,
IN UINT8 Command,
IN UINT8 *Data,
IN UINT8 DataSize,
OUT UINT8 *Response,
IN UINT8 ResponseSize
)
{
EFI_STATUS Status;
UINT8 CompletionCode;
UINT8 LocalResponse[64];
UINT8 LocalResponseSize;
LocalResponseSize = sizeof(LocalResponse);
Status = BmcKcsTransport (
NetFn,
Command,
Data,
DataSize,
LocalResponse,
&LocalResponseSize,
&CompletionCode,
0
);
if (!EFI_ERROR(Status) && Response != NULL) {
CopyMem (Response, LocalResponse, MIN(ResponseSize, LocalResponseSize));
}
if (EFI_ERROR(Status)) {
DebugPrint(64, "%a Command Completion code: %x, Status%r\n",
"SendIpmiCommandRaw", CompletionCode, Status);
}
return Status;
}
// ===========================================================================
// Signal Event (sub_44A4)
// ===========================================================================
STATIC
VOID
SignalEvent (
IN EFI_EVENT Event
)
{
if (gBS != NULL) {
gBS->SignalEvent (Event);
}
}
// ===========================================================================
// IPMI CMOS Clear Handling (sub_2944 area)
// ===========================================================================
/**
* Check and handle IPMI CMOS clear flag.
* If CMOS clear is requested via IPMI, the driver schedules
* the CMOS clear for the next boot.
*/
STATIC
VOID
HandleIpmiCmosClear (
VOID
)
{
EFI_STATUS Status;
UINTN DataSize;
UINT8 CmosClearValue;
DataSize = sizeof(CmosClearValue);
Status = gRT->GetVariable (
L"IpmiCmosClear",
&gBmcSelfTestProtocolGuid,
NULL,
&DataSize,
&CmosClearValue
);
if (!EFI_ERROR(Status)) {
DebugPrint(64, "%a IpmiCmosClear SetVariable Status:%r\n",
"IpmiCmosClear", Status);
}
}
// ===========================================================================
// EfiCreateProtocolNotifyEvent (sub_40E4)
// ===========================================================================
/**
* Register a notification event for a protocol installation.
*
* @param[in] ProtocolGuid GUID of protocol to monitor
* @param[in] Tpl TPL level for notification
* @param[in] NotifyFunction Callback on installation
* @param[in] NotifyContext Context for callback
* @param[in] Registration Registration pointer
*
* @return EFI_STATUS
*/
VOID
EfiCreateProtocolNotifyEvent (
IN EFI_GUID *ProtocolGuid,
IN EFI_TPL Tpl,
IN EFI_EVENT_NOTIFY NotifyFunction,
IN VOID *NotifyContext,
OUT VOID **Registration
)
{
EFI_STATUS Status;
EFI_EVENT Event;
//
// Validate parameters
//
ASSERT (ProtocolGuid != NULL);
ASSERT (NotifyFunction != NULL);
ASSERT (Registration != NULL);
//
// Create event and register for protocol notification
//
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL,
Tpl,
NotifyFunction,
NotifyContext,
&Event
);
if (EFI_ERROR (Status)) {
ASSERT_EFI_ERROR (Status);
return;
}
Status = gBS->RegisterProtocolNotify (
ProtocolGuid,
Event,
Registration
);
if (EFI_ERROR (Status)) {
ASSERT_EFI_ERROR (Status);
}
}
// ===========================================================================
// Initialization: Setup Variable Access
// ===========================================================================
/**
* Read the "Setup" NVRAM variable to get BMC configuration.
* The Setup variable contains user-configurable IPMI settings.
*/
STATIC
VOID
ReadBmcSetupOptions (
VOID
)
{
EFI_STATUS Status;
UINTN SetupSize;
UINT8 *SetupData;
UINT32 Attributes;
//
// Try SetupBackup first
//
SetupSize = 0;
Status = gRT->GetVariable (
L"SetupBackup",
&gSetupVarGuid,
NULL,
&SetupSize,
NULL
);
if (Status == EFI_BUFFER_TOO_SMALL) {
SetupData = AllocateZeroPool (6, SetupSize);
if (SetupData != NULL) {
Status = gRT->GetVariable (
L"SetupBackup",
&gSetupVarGuid,
NULL,
&SetupSize,
SetupData
);
}
}
//
// If backup not available, try primary Setup
//
if (EFI_ERROR (Status)) {
SetupSize = sizeof (gBmcInstance);
Status = gRT->GetVariable (
L"Setup",
&gSetupVarGuid,
NULL,
&SetupSize,
&gBmcInstance->SelfTestResult
);
}
//
// Process BMC-related setup options
// (BMC firmware version, boot override, etc.)
//
if (!EFI_ERROR (Status)) {
// Parse setup data and configure BMC
}
}
// ===========================================================================
// End of DxeIpmiBmcInitialize.c
// ===========================================================================