Newer
Older
AMI-Aptio-BIOS-Reversed / DxeIpmiBmcInitialize / DxeIpmiBmcInitialize.c
@Ajax Dong Ajax Dong 2 days ago 41 KB Init
/**
 * 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
// ===========================================================================