/** @file
  CPU Common Lib implementation.

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

**/

#include <Uefi.h>
#include <Library/BaseLib.h>
#include <Library/CpuLib.h>
#include <Library/DebugLib.h>
#include <Library/IoLib.h>
#include <Library/PcdLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/PciLib.h>
#include <Library/TimerLib.h>
#include <Library/CpuPlatformLib.h>
#include <Library/CpuMailboxLib.h>
#include <Register/Cpuid.h>
#include <Register/Msr.h>
#include <CpuAccess.h>

#include <Library/CpuCommonLib.h>

#define INTERRUPT_VECTOR_NUMBER 256
#define END_OF_TIMETABLE        0x3FF


/**
  Set up flags in CR4 for XMM instruction enabling
**/
VOID
EFIAPI
XmmInit (
  VOID
  )
{
  EFI_CPUID_REGISTER Cpuid;
  UINTN              Cr0;
  UINTN              Cr4;

  ///
  /// Read the CPUID information
  ///
  AsmCpuid (CPUID_VERSION_INFO, &Cpuid.RegEax, &Cpuid.RegEbx, &Cpuid.RegEcx, &Cpuid.RegEdx);

  ///
  /// Check whether SSE2 is supported
  ///
  if (Cpuid.RegEdx & BIT26) {
    ///
    /// Enable XMM
    ///
    Cr0 = AsmReadCr0 ();
    Cr0 |= BIT1;
    AsmWriteCr0 (Cr0);

    Cr4 = AsmReadCr4 ();
    Cr4 |= (UINTN) (BIT9 | BIT10);
    AsmWriteCr4 (Cr4);
  }
}

/**
  Enable "Machine Check Enable" bit in Cr4
**/
VOID
EFIAPI
EnableMce (
  VOID
  )
{
  UINTN Cr4;

  ///
  /// Enable MCE
  ///
  Cr4 = AsmReadCr4 ();
  Cr4 |= BIT6;
  AsmWriteCr4 (Cr4);
}

/**
  Mtrr Synch Up Entry
**/
UINTN
EFIAPI
MpMtrrSynchUpEntry (
  VOID
  )
{
  EFI_CPUID_REGISTER Cpuid;
  UINT64 MsrData;
  UINTN  Cr0;
  UINTN  Cr4;

  ///
  /// Read the CPUID and MSR 1Bh information
  ///
  AsmCpuid (CPUID_VERSION_INFO, &Cpuid.RegEax, &Cpuid.RegEbx, &Cpuid.RegEcx, &Cpuid.RegEdx);
  MsrData = AsmReadMsr64 (MSR_IA32_APIC_BASE);

  ///
  /// Set CD(Bit30) bit and clear NW(Bit29) bit of CR0 followed by a WBINVD.
  ///
  if (!(Cpuid.RegEdx & BIT24) || (MsrData & BIT8)) {
    AsmDisableCache ();
  } else {
    ///
    /// We could bypass the wbinvd by
    /// checking MSR 1Bh(MSR_IA32_APIC_BASE) Bit8 (1 = BSP, 0 = AP) to see if we're the BSP?
    /// and checking CPUID if the processor support self-snooping.
    ///
    Cr0 = AsmReadCr0 ();
    Cr0 &= (UINTN) ~BIT29;
    Cr0 |= BIT30;
    AsmWriteCr0 (Cr0);
  }

  ///
  /// Clear PGE flag Bit 7
  ///
  Cr4 = AsmReadCr4 ();
  Cr4 &= (UINTN) ~BIT7;
  AsmWriteCr4 (Cr4);

  ///
  /// Flush all TLBs
  ///
  CpuFlushTlb ();

  return Cr4;
}

/**
  Mtrr Synch Up Exit
**/
VOID
EFIAPI
MpMtrrSynchUpExit (
  UINTN Cr4
  )
{
  UINTN  Cr0;

  ///
  /// Flush all TLBs the second time
  ///
  CpuFlushTlb ();

  ///
  /// Clear both the CD and NW bits of CR0.
  ///
  Cr0 = AsmReadCr0 ();
  Cr0 &= (UINTN) ~(BIT29 | BIT30);
  AsmWriteCr0 (Cr0);

  ///
  /// Set PGE Flag in CR4 if set
  ///
  AsmWriteCr4 (Cr4);
}

/**
  This procedure sends an IPI to the designated processor in
  the requested delivery mode with the requested vector.

  @param[in] ApicID          - APIC ID of processor.
  @param[in] VectorNumber    - Vector number.
  @param[in] DeliveryMode    - I/O APIC Interrupt Deliver Modes

  @retval EFI_INVALID_PARAMETER - Input paramters were not correct.
  @retval EFI_NOT_READY         - There was a pending interrupt
  @retval EFI_SUCCESS           - Interrupt sent successfully
**/
EFI_STATUS
EFIAPI
CpuSendIpi (
  IN UINT32            ApicID,
  IN UINTN             VectorNumber,
  IN UINTN             DeliveryMode
  )
{
  MSR_IA32_APIC_BASE_REGISTER Msr;
  EFI_PHYSICAL_ADDRESS        ApicBase;
  UINT32                      IcrLow;
  UINT32                      IcrHigh;
  BOOLEAN                     XapicEnabled;
  UINT32                      TriggerMode;

  ///
  /// Check for valid input parameters.
  ///
  if (VectorNumber >= INTERRUPT_VECTOR_NUMBER) {
    return EFI_INVALID_PARAMETER;
  }

  if (DeliveryMode >= DELIVERY_MODE_MAX) {
    return EFI_INVALID_PARAMETER;
  }

  ///
  /// Fix the vector number for special interrupts like SMI and INIT.
  ///
  if (DeliveryMode == DELIVERY_MODE_SMI || DeliveryMode == DELIVERY_MODE_INIT) {
    VectorNumber = 0x0;
  }

  ///
  /// Initialze ICR high dword, since P6 family processor needs
  /// the destination field to be 0x0F when it is a broadcast
  ///
  IcrHigh = 0x0f000000;
  IcrLow  = (UINT32) (VectorNumber | (LShiftU64 (DeliveryMode, 8)));

  TriggerMode = TRIGGER_MODE_EDGE;

  ///
  /// Interrupt trigger mode
  ///
  if (TriggerMode == TRIGGER_MODE_LEVEL) {
    IcrLow |= 0x8000;
  }

  ///
  /// Interrupt pin polarity
  ///
  IcrLow |= 0x4000;

  ///
  /// xAPIC Enabled
  ///
  Msr.Uint64 = AsmReadMsr64 (MSR_IA32_APIC_BASE);
  XapicEnabled = (BOOLEAN) ((Msr.Bits.EXTD == 1) && (Msr.Bits.EN == 1));

  if (XapicEnabled) {
    IcrHigh = (UINT32) ApicID;
  } else {
    IcrHigh = (UINT32) LShiftU64 (ApicID, 24);
  }

  ApicBase    = Msr.Uint64 & 0xffffff000;

  /* If Extended XAPIC Mode is enabled,
     legacy xAPIC is no longer working.
     So, previous MMIO offset must be transferred to MSR offset R/W.
     ----------------------------------------------------------------
     MMIO Offset     MSR Offset     Register Name
     ----------------------------------------------------------------
     300h-310h        830h         Interrupt Command Register [63:0]
                      831h         [Reserved]
     ----------------------------------------------------------------
  */
  ///
  /// To write APIC register by MSR or MMIO
  ///
  if (XapicEnabled) {
    AsmWriteMsr64 (MSR_IA32_X2APIC_ICR, (((UINT64) LShiftU64 (IcrHigh, 32)) | (UINT64) IcrLow));
  } else {
    *(volatile UINT32 *) (UINTN) (ApicBase  + APIC_REGISTER_ICR_HIGH_OFFSET) = (UINT32) IcrHigh;
    *(volatile UINT32 *) (UINTN) (ApicBase  + APIC_REGISTER_ICR_LOW_OFFSET) = (UINT32) IcrLow;
  }

  MicroSecondDelay (10);

  ///
  /// To get APIC register from MSR or MMIO
  ///
  if (XapicEnabled) {
    IcrLow = (UINT32) AsmReadMsr64 (MSR_IA32_X2APIC_ICR);
  } else {
    IcrLow = (UINT32) *(volatile UINT32 *) (UINTN) (ApicBase + APIC_REGISTER_ICR_LOW_OFFSET);
  }

  if (IcrLow & BIT12) {
    return EFI_NOT_READY;
  }

  MicroSecondDelay (100);

  return EFI_SUCCESS;
}


/**
  Get APIC ID of processor

  @retval APIC ID of processor
**/
UINT32
GetCpuApicId (
  VOID
  )
{
  EFI_CPUID_REGISTER CpuidRegisters;

  AsmCpuid (
    CPUID_VERSION_INFO,
    &CpuidRegisters.RegEax,
    &CpuidRegisters.RegEbx,
    &CpuidRegisters.RegEcx,
    &CpuidRegisters.RegEdx
    );
  return (UINT32) (CpuidRegisters.RegEbx >> 24);
}

/**
  Programs XAPIC registers.

  @param[in] Bsp             - Is this BSP?
**/
VOID
ProgramXApic (
  BOOLEAN Bsp
  )
{
  UINT64               ApicBaseReg;
  EFI_PHYSICAL_ADDRESS ApicBase;
  volatile UINT32      *EntryAddress;
  UINT32               EntryValue;

  ApicBaseReg = AsmReadMsr64 (MSR_IA32_APIC_BASE);
  ApicBase    = ApicBaseReg & 0xffffff000ULL;

  ///
  /// Program the spurious vector entry
  ///
  EntryAddress  = (UINT32 *) (UINTN) (ApicBase + APIC_REGISTER_SPURIOUS_VECTOR_OFFSET);
  EntryValue    = *EntryAddress;
  EntryValue &= 0xFFFFFD0F;
  EntryValue |= 0x10F;
  *EntryAddress = EntryValue;

  ///
  /// Program the LINT1 vector entry as extINT
  ///
  EntryAddress  = (UINT32 *) (UINTN) (ApicBase + APIC_REGISTER_LINT0_VECTOR_OFFSET);
  EntryValue    = *EntryAddress;

  if (Bsp) {
    EntryValue &= 0xFFFE00FF;
    EntryValue |= 0x700;
  } else {
    EntryValue |= 0x10000;
  }

  *EntryAddress = EntryValue;

  ///
  /// Program the LINT1 vector entry as NMI
  ///
  EntryAddress  = (UINT32 *) (UINTN) (ApicBase + APIC_REGISTER_LINT1_VECTOR_OFFSET);
  EntryValue    = *EntryAddress;
  EntryValue &= 0xFFFE00FF;
  if (Bsp) {
    EntryValue |= 0x400;
  } else {
    EntryValue |= 0x10400;
  }

  *EntryAddress = EntryValue;
}

/**
  This function is to disable BIOS Write Protect in SMM phase.
**/
VOID
EFIAPI
CpuSmmDisableBiosWriteProtect (
  VOID
  )
{
  UINT32      Data32;

  ///
  /// Read memory location FED30880h OR with 00000001h, place the result in EAX,
  /// and write data to lower 32 bits of MSR 1FEh (sample code available)
  ///
  Data32 = MmioRead32 ((UINTN) (0xFED30880)) | (UINT32) (BIT0);
  AsmWriteMsr32 (0x000001FE, Data32);
}

/**
  This function is to enable BIOS Write Protect in SMM phase.
**/
VOID
EFIAPI
CpuSmmEnableBiosWriteProtect (
  VOID
  )
{
  UINT32      Data32;

  ///
  /// Read memory location FED30880h AND with FFFFFFFEh, place the result in EAX,
  /// and write data to lower 32 bits of MSR 1FEh (sample code available)
  ///
  Data32 = MmioRead32 ((UINTN) (0xFED30880)) & (UINT32) (~BIT0);
  AsmWriteMsr32 (0x000001FE, Data32);
}

/**
  This function returns the maximum number of cores supported in this physical processor package.

  @retval Maximum number of supported cores in the package.
**/
UINT8
GetMaxSupportedCoreCount (
  VOID
  )
{
  EFI_CPUID_REGISTER Cpuid;
  AsmCpuidEx (
    4,
    0,
    &Cpuid.RegEax,
    NULL,
    NULL,
    NULL
    );
  return (UINT8) (RShiftU64 (Cpuid.RegEax, 26) & 0x3f) + 1;
}

/**
  This function returns the actual factory-configured number of threads per core,
  and actual factory-configured number of cores in this physical processor package.

  @param[out] *ThreadsPerCore    - variable that will store Maximum enabled threads per core
  @param[out] *NumberOfCores     - variable that will store Maximum enabled cores per die
**/
VOID
GetSupportedCount (
  OUT UINT16 *ThreadsPerCore,  OPTIONAL
  OUT UINT16 *NumberOfCores    OPTIONAL
  )
{
  EFI_CPUID_REGISTER    CpuidRegs;
  UINT16                Threads;

  AsmCpuidEx (CPUID_EXTENDED_TOPOLOGY, 0, NULL, &CpuidRegs.RegEbx, NULL, NULL);
  Threads = (UINT16) CpuidRegs.RegEbx;

  if (ThreadsPerCore != NULL) {
    *ThreadsPerCore = Threads;
  }

  if (NumberOfCores != NULL) {
    AsmCpuidEx (CPUID_EXTENDED_TOPOLOGY, 1, NULL, &CpuidRegs.RegEbx, NULL, NULL);
    *NumberOfCores = (UINT16) (CpuidRegs.RegEbx / Threads);
  }
}


/**
  Check to see if the executing thread is BSP

  @retval TRUE   Executing thread is BSP
  @retval FALSE  Executing thread is AP
**/
BOOLEAN
IsBsp (
  VOID
  )
{
  MSR_IA32_APIC_BASE_REGISTER Msr;

  Msr.Uint64  = AsmReadMsr64 (MSR_IA32_APIC_BASE);
  return (BOOLEAN) (Msr.Bits.BSP == 1);
}


BOOLEAN
IsPrmrrAlreadySet (
  VOID
  )
{
  return FALSE;
}

/**
  Check if this is non-core processor - HT AP thread

  @retval TRUE if this is HT AP thread
  @retval FALSE if this is core thread
**/
BOOLEAN
IsSecondaryThread (
  VOID
  )
{
  UINT32             ApicID;
  EFI_CPUID_REGISTER CpuidRegisters;
  UINT8              CpuCount;
  UINT8              CoreCount;
  UINT8              CpuPerCore;
  UINT32             Mask;

  ApicID = GetCpuApicId ();

  AsmCpuid (
    CPUID_VERSION_INFO,
    &CpuidRegisters.RegEax,
    &CpuidRegisters.RegEbx,
    &CpuidRegisters.RegEcx,
    &CpuidRegisters.RegEdx
    );
  if ((CpuidRegisters.RegEdx & 0x10000000) == 0) {
    return FALSE;
  }

  CpuCount = (UINT8) ((CpuidRegisters.RegEbx >> 16) & 0xff);
  if (CpuCount == 1) {
    return FALSE;
  }

  AsmCpuid (
    CPUID_SIGNATURE,
    &CpuidRegisters.RegEax,
    &CpuidRegisters.RegEbx,
    &CpuidRegisters.RegEcx,
    &CpuidRegisters.RegEdx
    );
  if (CpuidRegisters.RegEax > 3) {

    CoreCount = GetMaxSupportedCoreCount ();
  } else {
    CoreCount = 1;
  }
  ///
  /// Assumes there is symmetry across core boundary, i.e. each core within a package has the same number of logical processors
  ///
  if (CpuCount == CoreCount) {
    return FALSE;
  }

  CpuPerCore = CpuCount / CoreCount;

  ///
  /// Assume 1 Core  has no more than 8 threads
  ///
  if (CpuPerCore == 2) {
    Mask = 0x1;
  } else if (CpuPerCore <= 4) {
    Mask = 0x3;
  } else {
    Mask = 0x7;
  }

  if ((ApicID & Mask) == 0) {
    return FALSE;
  } else {
    return TRUE;
  }
}

