/** @file
  Machine Check features.

  Copyright (c) 2017 - 2019, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "CpuCommonFeatures.h"

/**
  Detects if Machine Check Exception feature supported on current processor.

  @param[in]  ProcessorNumber  The index of the CPU executing this function.
  @param[in]  CpuInfo          A pointer to the REGISTER_CPU_FEATURE_INFORMATION
                               structure for the CPU executing this function.
  @param[in]  ConfigData       A pointer to the configuration buffer returned
                               by CPU_FEATURE_GET_CONFIG_DATA.  NULL if
                               CPU_FEATURE_GET_CONFIG_DATA was not provided in
                               RegisterCpuFeature().

  @retval TRUE     Machine Check Exception feature is supported.
  @retval FALSE    Machine Check Exception feature is not supported.

  @note This service could be called by BSP/APs.
**/
BOOLEAN
EFIAPI
MceSupport (
  IN UINTN                             ProcessorNumber,
  IN REGISTER_CPU_FEATURE_INFORMATION  *CpuInfo,
  IN VOID                              *ConfigData  OPTIONAL
  )
{
  return (CpuInfo->CpuIdVersionInfoEdx.Bits.MCE == 1);
}

/**
  Initializes Machine Check Exception feature to specific state.

  @param[in]  ProcessorNumber  The index of the CPU executing this function.
  @param[in]  CpuInfo          A pointer to the REGISTER_CPU_FEATURE_INFORMATION
                               structure for the CPU executing this function.
  @param[in]  ConfigData       A pointer to the configuration buffer returned
                               by CPU_FEATURE_GET_CONFIG_DATA.  NULL if
                               CPU_FEATURE_GET_CONFIG_DATA was not provided in
                               RegisterCpuFeature().
  @param[in]  State            If TRUE, then the Machine Check Exception feature must be enabled.
                               If FALSE, then the Machine Check Exception feature must be disabled.

  @retval RETURN_SUCCESS       Machine Check Exception feature is initialized.

  @note This service could be called by BSP only.
**/
RETURN_STATUS
EFIAPI
MceInitialize (
  IN UINTN                             ProcessorNumber,
  IN REGISTER_CPU_FEATURE_INFORMATION  *CpuInfo,
  IN VOID                              *ConfigData   OPTIONAL,
  IN BOOLEAN                           State
  )
{
  //
  // Set MCE bit in CR4
  //
  CPU_REGISTER_TABLE_WRITE_FIELD (
    ProcessorNumber,
    ControlRegister,
    4,
    IA32_CR4,
    Bits.MCE,
    (State) ? 1 : 0
    );
  return RETURN_SUCCESS;
}

/**
  Detects if Machine Check Architecture feature supported on current processor.

  @param[in]  ProcessorNumber  The index of the CPU executing this function.
  @param[in]  CpuInfo          A pointer to the REGISTER_CPU_FEATURE_INFORMATION
                               structure for the CPU executing this function.
  @param[in]  ConfigData       A pointer to the configuration buffer returned
                               by CPU_FEATURE_GET_CONFIG_DATA.  NULL if
                               CPU_FEATURE_GET_CONFIG_DATA was not provided in
                               RegisterCpuFeature().

  @retval TRUE     Machine Check Architecture feature is supported.
  @retval FALSE    Machine Check Architecture feature is not supported.

  @note This service could be called by BSP/APs.
**/
BOOLEAN
EFIAPI
McaSupport (
  IN UINTN                             ProcessorNumber,
  IN REGISTER_CPU_FEATURE_INFORMATION  *CpuInfo,
  IN VOID                              *ConfigData  OPTIONAL
  )
{
  if (!MceSupport (ProcessorNumber, CpuInfo, ConfigData)) {
    return FALSE;
  }

  return (CpuInfo->CpuIdVersionInfoEdx.Bits.MCA == 1);
}

/**
  Initializes Machine Check Architecture feature to specific state.

  @param[in]  ProcessorNumber  The index of the CPU executing this function.
  @param[in]  CpuInfo          A pointer to the REGISTER_CPU_FEATURE_INFORMATION
                               structure for the CPU executing this function.
  @param[in]  ConfigData       A pointer to the configuration buffer returned
                               by CPU_FEATURE_GET_CONFIG_DATA.  NULL if
                               CPU_FEATURE_GET_CONFIG_DATA was not provided in
                               RegisterCpuFeature().
  @param[in]  State            If TRUE, then the Machine Check Architecture feature must be enabled.
                               If FALSE, then the Machine Check Architecture feature must be disabled.

  @retval RETURN_SUCCESS       Machine Check Architecture feature is initialized.

  @note This service could be called by BSP only.
**/
RETURN_STATUS
EFIAPI
McaInitialize (
  IN UINTN                             ProcessorNumber,
  IN REGISTER_CPU_FEATURE_INFORMATION  *CpuInfo,
  IN VOID                              *ConfigData   OPTIONAL,
  IN BOOLEAN                           State
  )
{
  MSR_IA32_MCG_CAP_REGISTER  McgCap;
  UINT32                     BankIndex;

  //
  // The scope of MSR_IA32_MC*_CTL/MSR_IA32_MC*_STATUS is core for below processor type, only program
  // MSR_IA32_MC*_CTL/MSR_IA32_MC*_STATUS for thread 0 in each core.
  //
  if (IS_ATOM_PROCESSOR (CpuInfo->DisplayFamily, CpuInfo->DisplayModel) ||
      IS_SILVERMONT_PROCESSOR (CpuInfo->DisplayFamily, CpuInfo->DisplayModel) ||
      IS_SANDY_BRIDGE_PROCESSOR (CpuInfo->DisplayFamily, CpuInfo->DisplayModel) ||
      IS_SKYLAKE_PROCESSOR (CpuInfo->DisplayFamily, CpuInfo->DisplayModel) ||
      IS_XEON_PHI_PROCESSOR (CpuInfo->DisplayFamily, CpuInfo->DisplayModel) ||
      IS_PENTIUM_4_PROCESSOR (CpuInfo->DisplayFamily, CpuInfo->DisplayModel) ||
      IS_CORE_PROCESSOR (CpuInfo->DisplayFamily, CpuInfo->DisplayModel))
  {
    if (CpuInfo->ProcessorInfo.Location.Thread != 0) {
      return RETURN_SUCCESS;
    }
  }

  //
  // The scope of MSR_IA32_MC*_CTL/MSR_IA32_MC*_STATUS is package for below processor type, only program
  // MSR_IA32_MC*_CTL/MSR_IA32_MC*_STATUS once for each package.
  //
  if (IS_NEHALEM_PROCESSOR (CpuInfo->DisplayFamily, CpuInfo->DisplayModel)) {
    if ((CpuInfo->First.Thread == 0) || (CpuInfo->First.Core == 0)) {
      return RETURN_SUCCESS;
    }
  }

  if (State) {
    McgCap.Uint64 = AsmReadMsr64 (MSR_IA32_MCG_CAP);
    for (BankIndex = 0; BankIndex < (UINT32)McgCap.Bits.Count; BankIndex++) {
      CPU_REGISTER_TABLE_WRITE64 (
        ProcessorNumber,
        Msr,
        MSR_IA32_MC0_CTL + BankIndex * 4,
        MAX_UINT64
        );
    }

    if (PcdGetBool (PcdIsPowerOnReset)) {
      for (BankIndex = 0; BankIndex < (UINTN)McgCap.Bits.Count; BankIndex++) {
        CPU_REGISTER_TABLE_WRITE64 (
          ProcessorNumber,
          Msr,
          MSR_IA32_MC0_STATUS + BankIndex * 4,
          0
          );
      }
    }
  }

  return RETURN_SUCCESS;
}

/**
  Detects if IA32_MCG_CTL feature supported on current processor.

  @param[in]  ProcessorNumber  The index of the CPU executing this function.
  @param[in]  CpuInfo          A pointer to the REGISTER_CPU_FEATURE_INFORMATION
                               structure for the CPU executing this function.
  @param[in]  ConfigData       A pointer to the configuration buffer returned
                               by CPU_FEATURE_GET_CONFIG_DATA.  NULL if
                               CPU_FEATURE_GET_CONFIG_DATA was not provided in
                               RegisterCpuFeature().

  @retval TRUE     IA32_MCG_CTL feature is supported.
  @retval FALSE    IA32_MCG_CTL feature is not supported.

  @note This service could be called by BSP/APs.
**/
BOOLEAN
EFIAPI
McgCtlSupport (
  IN UINTN                             ProcessorNumber,
  IN REGISTER_CPU_FEATURE_INFORMATION  *CpuInfo,
  IN VOID                              *ConfigData  OPTIONAL
  )
{
  MSR_IA32_MCG_CAP_REGISTER  McgCap;

  if (!McaSupport (ProcessorNumber, CpuInfo, ConfigData)) {
    return FALSE;
  }

  McgCap.Uint64 = AsmReadMsr64 (MSR_IA32_MCG_CAP);
  return (McgCap.Bits.MCG_CTL_P == 1);
}

/**
  Initializes IA32_MCG_CTL feature to specific state.

  @param[in]  ProcessorNumber  The index of the CPU executing this function.
  @param[in]  CpuInfo          A pointer to the REGISTER_CPU_FEATURE_INFORMATION
                               structure for the CPU executing this function.
  @param[in]  ConfigData       A pointer to the configuration buffer returned
                               by CPU_FEATURE_GET_CONFIG_DATA.  NULL if
                               CPU_FEATURE_GET_CONFIG_DATA was not provided in
                               RegisterCpuFeature().
  @param[in]  State            If TRUE, then the IA32_MCG_CTL feature must be enabled.
                               If FALSE, then the IA32_MCG_CTL feature must be disabled.

  @retval RETURN_SUCCESS       IA32_MCG_CTL feature is initialized.

  @note This service could be called by BSP only.
**/
RETURN_STATUS
EFIAPI
McgCtlInitialize (
  IN UINTN                             ProcessorNumber,
  IN REGISTER_CPU_FEATURE_INFORMATION  *CpuInfo,
  IN VOID                              *ConfigData   OPTIONAL,
  IN BOOLEAN                           State
  )
{
  CPU_REGISTER_TABLE_WRITE64 (
    ProcessorNumber,
    Msr,
    MSR_IA32_MCG_CTL,
    (State) ? MAX_UINT64 : 0
    );
  return RETURN_SUCCESS;
}

/**
  Detects if Local machine check exception feature supported on current
  processor.

  @param[in]  ProcessorNumber  The index of the CPU executing this function.
  @param[in]  CpuInfo          A pointer to the REGISTER_CPU_FEATURE_INFORMATION
                               structure for the CPU executing this function.
  @param[in]  ConfigData       A pointer to the configuration buffer returned
                               by CPU_FEATURE_GET_CONFIG_DATA.  NULL if
                               CPU_FEATURE_GET_CONFIG_DATA was not provided in
                               RegisterCpuFeature().

  @retval TRUE     Local machine check exception feature is supported.
  @retval FALSE    Local machine check exception feature is not supported.

  @note This service could be called by BSP/APs.
**/
BOOLEAN
EFIAPI
LmceSupport (
  IN UINTN                             ProcessorNumber,
  IN REGISTER_CPU_FEATURE_INFORMATION  *CpuInfo,
  IN VOID                              *ConfigData  OPTIONAL
  )
{
  MSR_IA32_MCG_CAP_REGISTER  McgCap;

  if (!McaSupport (ProcessorNumber, CpuInfo, ConfigData)) {
    return FALSE;
  }

  McgCap.Uint64 = AsmReadMsr64 (MSR_IA32_MCG_CAP);
  if (ProcessorNumber == 0) {
    DEBUG ((DEBUG_INFO, "LMCE enable = %x\n", (BOOLEAN)(McgCap.Bits.MCG_LMCE_P != 0)));
  }

  return (BOOLEAN)(McgCap.Bits.MCG_LMCE_P != 0);
}

/**
  Initializes Local machine check exception feature to specific state.

  @param[in]  ProcessorNumber  The index of the CPU executing this function.
  @param[in]  CpuInfo          A pointer to the REGISTER_CPU_FEATURE_INFORMATION
                               structure for the CPU executing this function.
  @param[in]  ConfigData       A pointer to the configuration buffer returned
                               by CPU_FEATURE_GET_CONFIG_DATA.  NULL if
                               CPU_FEATURE_GET_CONFIG_DATA was not provided in
                               RegisterCpuFeature().
  @param[in]  State            If TRUE, then the Local machine check exception
                               feature must be enabled.
                               If FALSE, then the Local machine check exception
                               feature must be disabled.

  @retval RETURN_SUCCESS       Local machine check exception feature is initialized.

**/
RETURN_STATUS
EFIAPI
LmceInitialize (
  IN UINTN                             ProcessorNumber,
  IN REGISTER_CPU_FEATURE_INFORMATION  *CpuInfo,
  IN VOID                              *ConfigData   OPTIONAL,
  IN BOOLEAN                           State
  )
{
  //
  // The scope of LcmeOn bit in the MSR_IA32_MISC_ENABLE is core for below processor type, only program
  // MSR_IA32_MISC_ENABLE for thread 0 in each core.
  //
  if (IS_SILVERMONT_PROCESSOR (CpuInfo->DisplayFamily, CpuInfo->DisplayModel) ||
      IS_GOLDMONT_PROCESSOR (CpuInfo->DisplayFamily, CpuInfo->DisplayModel) ||
      IS_PENTIUM_4_PROCESSOR (CpuInfo->DisplayFamily, CpuInfo->DisplayModel))
  {
    if (CpuInfo->ProcessorInfo.Location.Thread != 0) {
      return RETURN_SUCCESS;
    }
  }

  CPU_REGISTER_TABLE_TEST_THEN_WRITE_FIELD (
    ProcessorNumber,
    Msr,
    MSR_IA32_FEATURE_CONTROL,
    MSR_IA32_FEATURE_CONTROL_REGISTER,
    Bits.LmceOn,
    (State) ? 1 : 0
    );

  return RETURN_SUCCESS;
}
