/** @file
  SMM CPU misc functions for Ia32 arch specific.

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

**/

#include "PiSmmCpuDxeSmm.h"

extern UINT64  gTaskGateDescriptor;

EFI_PHYSICAL_ADDRESS  mGdtBuffer;
UINTN                 mGdtBufferSize;

extern BOOLEAN  mCetSupported;

X86_ASSEMBLY_PATCH_LABEL  mPatchCetPl0Ssp;
X86_ASSEMBLY_PATCH_LABEL  mPatchCetInterruptSsp;
UINT32                    mCetPl0Ssp;
UINT32                    mCetInterruptSsp;

/**
  Initialize IDT for SMM Stack Guard.

**/
VOID
EFIAPI
InitializeIDTSmmStackGuard (
  VOID
  )
{
  IA32_IDT_GATE_DESCRIPTOR  *IdtGate;

  //
  // If SMM Stack Guard feature is enabled, the Page Fault Exception entry in IDT
  // is a Task Gate Descriptor so that when a Page Fault Exception occurs,
  // the processors can use a known good stack in case stack is ran out.
  //
  IdtGate         = (IA32_IDT_GATE_DESCRIPTOR *)gcSmiIdtr.Base;
  IdtGate        += EXCEPT_IA32_PAGE_FAULT;
  IdtGate->Uint64 = gTaskGateDescriptor;
}

/**
  Initialize Gdt for all processors.

  @param[in]   Cr3          CR3 value.
  @param[out]  GdtStepSize  The step size for GDT table.

  @return GdtBase for processor 0.
          GdtBase for processor X is: GdtBase + (GdtStepSize * X)
**/
VOID *
InitGdt (
  IN  UINTN  Cr3,
  OUT UINTN  *GdtStepSize
  )
{
  UINTN                    Index;
  IA32_SEGMENT_DESCRIPTOR  *GdtDescriptor;
  UINTN                    TssBase;
  UINTN                    GdtTssTableSize;
  UINT8                    *GdtTssTables;
  UINTN                    GdtTableStepSize;
  UINTN                    InterruptShadowStack;

  if (FeaturePcdGet (PcdCpuSmmStackGuard)) {
    //
    // For IA32 SMM, if SMM Stack Guard feature is enabled, we use 2 TSS.
    // in this case, we allocate separate GDT/TSS for each CPUs to avoid TSS load contention
    // on each SMI entry.
    //

    //
    // Enlarge GDT to contain 2 TSS descriptors
    //
    gcSmiGdtr.Limit += (UINT16)(2 * sizeof (IA32_SEGMENT_DESCRIPTOR));

    GdtTssTableSize = (gcSmiGdtr.Limit + 1 + TSS_SIZE + EXCEPTION_TSS_SIZE + 7) & ~7; // 8 bytes aligned
    mGdtBufferSize  = GdtTssTableSize * gSmmCpuPrivate->SmmCoreEntryContext.NumberOfCpus;
    //
    // IA32 Stack Guard need use task switch to switch stack that need
    // write GDT and TSS, so AllocateCodePages() could not be used here
    // as code pages will be set to RO.
    //
    GdtTssTables = (UINT8 *)AllocatePages (EFI_SIZE_TO_PAGES (mGdtBufferSize));
    ASSERT (GdtTssTables != NULL);
    mGdtBuffer       = (UINTN)GdtTssTables;
    GdtTableStepSize = GdtTssTableSize;

    for (Index = 0; Index < gSmmCpuPrivate->SmmCoreEntryContext.NumberOfCpus; Index++) {
      CopyMem (GdtTssTables + GdtTableStepSize * Index, (VOID *)(UINTN)gcSmiGdtr.Base, gcSmiGdtr.Limit + 1 + TSS_SIZE + EXCEPTION_TSS_SIZE);
      //
      // Fixup TSS descriptors
      //
      TssBase                      = (UINTN)(GdtTssTables + GdtTableStepSize * Index + gcSmiGdtr.Limit + 1);
      GdtDescriptor                = (IA32_SEGMENT_DESCRIPTOR *)(TssBase) - 2;
      GdtDescriptor->Bits.BaseLow  = (UINT16)TssBase;
      GdtDescriptor->Bits.BaseMid  = (UINT8)(TssBase >> 16);
      GdtDescriptor->Bits.BaseHigh = (UINT8)(TssBase >> 24);

      TssBase += TSS_SIZE;
      GdtDescriptor++;
      GdtDescriptor->Bits.BaseLow  = (UINT16)TssBase;
      GdtDescriptor->Bits.BaseMid  = (UINT8)(TssBase >> 16);
      GdtDescriptor->Bits.BaseHigh = (UINT8)(TssBase >> 24);
      //
      // Fixup TSS segments
      //
      // ESP as known good stack
      //
      *(UINTN *)(TssBase + TSS_IA32_ESP_OFFSET)  =  mSmmStackArrayBase + EFI_PAGE_SIZE + Index * mSmmStackSize;
      *(UINT32 *)(TssBase + TSS_IA32_CR3_OFFSET) = Cr3;

      //
      // Setup ShadowStack for stack switch
      //
      if ((PcdGet32 (PcdControlFlowEnforcementPropertyMask) != 0) && mCetSupported) {
        InterruptShadowStack                       = (UINTN)(mSmmStackArrayBase + mSmmStackSize + EFI_PAGES_TO_SIZE (1) - sizeof (UINT64) + (mSmmStackSize + mSmmShadowStackSize) * Index);
        *(UINT32 *)(TssBase + TSS_IA32_SSP_OFFSET) = (UINT32)InterruptShadowStack;
      }
    }
  } else {
    //
    // Just use original table, AllocatePage and copy them here to make sure GDTs are covered in page memory.
    //
    GdtTssTableSize = gcSmiGdtr.Limit + 1;
    mGdtBufferSize  = GdtTssTableSize * gSmmCpuPrivate->SmmCoreEntryContext.NumberOfCpus;
    GdtTssTables    = (UINT8 *)AllocateCodePages (EFI_SIZE_TO_PAGES (mGdtBufferSize));
    ASSERT (GdtTssTables != NULL);
    mGdtBuffer       = (UINTN)GdtTssTables;
    GdtTableStepSize = GdtTssTableSize;

    for (Index = 0; Index < gSmmCpuPrivate->SmmCoreEntryContext.NumberOfCpus; Index++) {
      CopyMem (GdtTssTables + GdtTableStepSize * Index, (VOID *)(UINTN)gcSmiGdtr.Base, gcSmiGdtr.Limit + 1);
    }
  }

  *GdtStepSize = GdtTableStepSize;
  return GdtTssTables;
}

/**
  Initialize the shadow stack related data structure.

  @param CpuIndex     The index of CPU.
  @param ShadowStack  The bottom of the shadow stack for this CPU.
**/
VOID
InitShadowStack (
  IN UINTN  CpuIndex,
  IN VOID   *ShadowStack
  )
{
  UINTN  SmmShadowStackSize;

  if ((PcdGet32 (PcdControlFlowEnforcementPropertyMask) != 0) && mCetSupported) {
    SmmShadowStackSize = EFI_PAGES_TO_SIZE (EFI_SIZE_TO_PAGES (PcdGet32 (PcdCpuSmmShadowStackSize)));
    if (FeaturePcdGet (PcdCpuSmmStackGuard)) {
      SmmShadowStackSize += EFI_PAGES_TO_SIZE (2);
    }

    mCetPl0Ssp = (UINT32)((UINTN)ShadowStack + SmmShadowStackSize - sizeof (UINT64));
    PatchInstructionX86 (mPatchCetPl0Ssp, mCetPl0Ssp, 4);
    DEBUG ((DEBUG_INFO, "mCetPl0Ssp - 0x%x\n", mCetPl0Ssp));
    DEBUG ((DEBUG_INFO, "ShadowStack - 0x%x\n", ShadowStack));
    DEBUG ((DEBUG_INFO, "  SmmShadowStackSize - 0x%x\n", SmmShadowStackSize));

    if (FeaturePcdGet (PcdCpuSmmStackGuard)) {
      mCetInterruptSsp = (UINT32)((UINTN)ShadowStack + EFI_PAGES_TO_SIZE (1) - sizeof (UINT64));
      PatchInstructionX86 (mPatchCetInterruptSsp, mCetInterruptSsp, 4);
      DEBUG ((DEBUG_INFO, "mCetInterruptSsp - 0x%x\n", mCetInterruptSsp));
    }
  }
}
