/** @file
  X64 Instruction function.

  Copyright (C) 2020, Advanced Micro Devices, Inc. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Base.h>
#include <Uefi.h>
#include <Library/BaseMemoryLib.h>
#include <Register/Intel/Cpuid.h>
#include <IndustryStandard/InstructionParsing.h>
#include "CcInstruction.h"

#define MAX_INSTRUCTION_LENGTH  15

/**
  Return a pointer to the contents of the specified register.

  Based upon the input register, return a pointer to the registers contents
  in the x86 processor context.

  @param[in] Regs      x64 processor context
  @param[in] Register  Register to obtain pointer for

  @return              Pointer to the contents of the requested register

**/
UINT64 *
CcGetRegisterPointer (
  IN EFI_SYSTEM_CONTEXT_X64  *Regs,
  IN UINT8                   Register
  )
{
  UINT64  *Reg;

  switch (Register) {
    case 0:
      Reg = &Regs->Rax;
      break;
    case 1:
      Reg = &Regs->Rcx;
      break;
    case 2:
      Reg = &Regs->Rdx;
      break;
    case 3:
      Reg = &Regs->Rbx;
      break;
    case 4:
      Reg = &Regs->Rsp;
      break;
    case 5:
      Reg = &Regs->Rbp;
      break;
    case 6:
      Reg = &Regs->Rsi;
      break;
    case 7:
      Reg = &Regs->Rdi;
      break;
    case 8:
      Reg = &Regs->R8;
      break;
    case 9:
      Reg = &Regs->R9;
      break;
    case 10:
      Reg = &Regs->R10;
      break;
    case 11:
      Reg = &Regs->R11;
      break;
    case 12:
      Reg = &Regs->R12;
      break;
    case 13:
      Reg = &Regs->R13;
      break;
    case 14:
      Reg = &Regs->R14;
      break;
    case 15:
      Reg = &Regs->R15;
      break;
    default:
      Reg = NULL;
  }

  ASSERT (Reg != NULL);

  return Reg;
}

/**
  Update the instruction parsing context for displacement bytes.

  @param[in, out] InstructionData  Instruction parsing context
  @param[in]      Size             The instruction displacement size

**/
STATIC
VOID
UpdateForDisplacement (
  IN OUT CC_INSTRUCTION_DATA  *InstructionData,
  IN     UINTN                Size
  )
{
  InstructionData->DisplacementSize = Size;
  InstructionData->Immediate       += Size;
  InstructionData->End             += Size;
}

/**
  Determine if an instruction address if RIP relative.

  Examine the instruction parsing context to determine if the address offset
  is relative to the instruction pointer.

  @param[in] InstructionData  Instruction parsing context

  @retval TRUE                Instruction addressing is RIP relative
  @retval FALSE               Instruction addressing is not RIP relative

**/
STATIC
BOOLEAN
IsRipRelative (
  IN CC_INSTRUCTION_DATA  *InstructionData
  )
{
  CC_INSTRUCTION_OPCODE_EXT  *Ext;

  Ext = &InstructionData->Ext;

  return ((InstructionData->Mode == LongMode64Bit) &&
          (Ext->ModRm.Mod == 0) &&
          (Ext->ModRm.Rm == 5)  &&
          (InstructionData->SibPresent == FALSE));
}

/**
  Return the effective address of a memory operand.

  Examine the instruction parsing context to obtain the effective memory
  address of a memory operand.

  @param[in] Regs             x64 processor context
  @param[in] InstructionData  Instruction parsing context

  @return                     The memory operand effective address

**/
STATIC
UINT64
GetEffectiveMemoryAddress (
  IN EFI_SYSTEM_CONTEXT_X64  *Regs,
  IN CC_INSTRUCTION_DATA     *InstructionData
  )
{
  CC_INSTRUCTION_OPCODE_EXT  *Ext;
  UINT64                     EffectiveAddress;

  Ext              = &InstructionData->Ext;
  EffectiveAddress = 0;

  if (IsRipRelative (InstructionData)) {
    //
    // RIP-relative displacement is a 32-bit signed value
    //
    INT32  RipRelative;

    RipRelative = *(INT32 *)InstructionData->Displacement;

    UpdateForDisplacement (InstructionData, 4);

    //
    // Negative displacement is handled by standard UINT64 wrap-around.
    //
    return Regs->Rip + (UINT64)RipRelative;
  }

  switch (Ext->ModRm.Mod) {
    case 1:
      UpdateForDisplacement (InstructionData, 1);
      EffectiveAddress += (UINT64)(*(INT8 *)(InstructionData->Displacement));
      break;
    case 2:
      switch (InstructionData->AddrSize) {
        case Size16Bits:
          UpdateForDisplacement (InstructionData, 2);
          EffectiveAddress += (UINT64)(*(INT16 *)(InstructionData->Displacement));
          break;
        default:
          UpdateForDisplacement (InstructionData, 4);
          EffectiveAddress += (UINT64)(*(INT32 *)(InstructionData->Displacement));
          break;
      }

      break;
  }

  if (InstructionData->SibPresent) {
    INT64  Displacement;

    if (Ext->Sib.Index != 4) {
      CopyMem (
        &Displacement,
        CcGetRegisterPointer (Regs, Ext->Sib.Index),
        sizeof (Displacement)
        );
      Displacement *= (INT64)(1 << Ext->Sib.Scale);

      //
      // Negative displacement is handled by standard UINT64 wrap-around.
      //
      EffectiveAddress += (UINT64)Displacement;
    }

    if ((Ext->Sib.Base != 5) || Ext->ModRm.Mod) {
      EffectiveAddress += *CcGetRegisterPointer (Regs, Ext->Sib.Base);
    } else {
      UpdateForDisplacement (InstructionData, 4);
      EffectiveAddress += (UINT64)(*(INT32 *)(InstructionData->Displacement));
    }
  } else {
    EffectiveAddress += *CcGetRegisterPointer (Regs, Ext->ModRm.Rm);
  }

  return EffectiveAddress;
}

/**
  Decode a ModRM byte.

  Examine the instruction parsing context to decode a ModRM byte and the SIB
  byte, if present.

  @param[in]      Regs             x64 processor context
  @param[in, out] InstructionData  Instruction parsing context

**/
VOID
CcDecodeModRm (
  IN     EFI_SYSTEM_CONTEXT_X64  *Regs,
  IN OUT CC_INSTRUCTION_DATA     *InstructionData
  )
{
  CC_INSTRUCTION_OPCODE_EXT  *Ext;
  INSTRUCTION_REX_PREFIX     *RexPrefix;
  INSTRUCTION_MODRM          *ModRm;
  INSTRUCTION_SIB            *Sib;

  RexPrefix = &InstructionData->RexPrefix;
  Ext       = &InstructionData->Ext;
  ModRm     = &InstructionData->ModRm;
  Sib       = &InstructionData->Sib;

  InstructionData->ModRmPresent = TRUE;
  ModRm->Uint8                  = *(InstructionData->End);

  InstructionData->Displacement++;
  InstructionData->Immediate++;
  InstructionData->End++;

  Ext->ModRm.Mod = ModRm->Bits.Mod;
  Ext->ModRm.Reg = (RexPrefix->Bits.BitR << 3) | ModRm->Bits.Reg;
  Ext->ModRm.Rm  = (RexPrefix->Bits.BitB << 3) | ModRm->Bits.Rm;

  Ext->RegData = *CcGetRegisterPointer (Regs, Ext->ModRm.Reg);

  if (Ext->ModRm.Mod == 3) {
    Ext->RmData = *CcGetRegisterPointer (Regs, Ext->ModRm.Rm);
  } else {
    if (ModRm->Bits.Rm == 4) {
      InstructionData->SibPresent = TRUE;
      Sib->Uint8                  = *(InstructionData->End);

      InstructionData->Displacement++;
      InstructionData->Immediate++;
      InstructionData->End++;

      Ext->Sib.Scale = Sib->Bits.Scale;
      Ext->Sib.Index = (RexPrefix->Bits.BitX << 3) | Sib->Bits.Index;
      Ext->Sib.Base  = (RexPrefix->Bits.BitB << 3) | Sib->Bits.Base;
    }

    Ext->RmData = GetEffectiveMemoryAddress (Regs, InstructionData);
  }
}

/**
  Decode instruction prefixes.

  Parse the instruction data to track the instruction prefixes that have
  been used.

  @param[in]      Regs             x64 processor context
  @param[in, out] InstructionData  Instruction parsing context

  @retval         EFI_SUCCESS      Successfully decode Prefixes
  @retval         Others           Other error as indicated
**/
STATIC
EFI_STATUS
DecodePrefixes (
  IN     EFI_SYSTEM_CONTEXT_X64  *Regs,
  IN OUT CC_INSTRUCTION_DATA     *InstructionData
  )
{
  CC_INSTRUCTION_MODE  Mode;
  CC_INSTRUCTION_SIZE  ModeDataSize;
  CC_INSTRUCTION_SIZE  ModeAddrSize;
  UINT8                *Byte;
  UINT8                ParsedLength;

  ParsedLength = 0;

  //
  // Always in 64-bit mode
  //
  Mode         = LongMode64Bit;
  ModeDataSize = Size32Bits;
  ModeAddrSize = Size64Bits;

  InstructionData->Mode     = Mode;
  InstructionData->DataSize = ModeDataSize;
  InstructionData->AddrSize = ModeAddrSize;

  InstructionData->Prefixes = InstructionData->Begin;

  Byte = InstructionData->Prefixes;
  for ( ; ParsedLength <= MAX_INSTRUCTION_LENGTH; Byte++, InstructionData->PrefixSize++, ParsedLength++) {
    //
    // Check the 0x40 to 0x4F range using an if statement here since some
    // compilers don't like the "case 0x40 ... 0x4F:" syntax. This avoids
    // 16 case statements below.
    //
    if ((*Byte >= REX_PREFIX_START) && (*Byte <= REX_PREFIX_STOP)) {
      InstructionData->RexPrefix.Uint8 = *Byte;
      if ((*Byte & REX_64BIT_OPERAND_SIZE_MASK) != 0) {
        InstructionData->DataSize = Size64Bits;
      }

      continue;
    }

    switch (*Byte) {
      case OVERRIDE_SEGMENT_CS:
      case OVERRIDE_SEGMENT_DS:
      case OVERRIDE_SEGMENT_ES:
      case OVERRIDE_SEGMENT_SS:
        if (Mode != LongMode64Bit) {
          InstructionData->SegmentSpecified = TRUE;
          InstructionData->Segment          = (*Byte >> 3) & 3;
        }

        break;

      case OVERRIDE_SEGMENT_FS:
      case OVERRIDE_SEGMENT_GS:
        InstructionData->SegmentSpecified = TRUE;
        InstructionData->Segment          = *Byte & 7;
        break;

      case OVERRIDE_OPERAND_SIZE:
        if (InstructionData->RexPrefix.Uint8 == 0) {
          InstructionData->DataSize =
            (Mode == LongMode64Bit)       ? Size16Bits :
            (Mode == LongModeCompat32Bit) ? Size16Bits :
            (Mode == LongModeCompat16Bit) ? Size32Bits : 0;
        }

        break;

      case OVERRIDE_ADDRESS_SIZE:
        InstructionData->AddrSize =
          (Mode == LongMode64Bit)       ? Size32Bits :
          (Mode == LongModeCompat32Bit) ? Size16Bits :
          (Mode == LongModeCompat16Bit) ? Size32Bits : 0;
        break;

      case LOCK_PREFIX:
        break;

      case REPZ_PREFIX:
        InstructionData->RepMode = RepZ;
        break;

      case REPNZ_PREFIX:
        InstructionData->RepMode = RepNZ;
        break;

      default:
        InstructionData->OpCodes    = Byte;
        InstructionData->OpCodeSize = (*Byte == TWO_BYTE_OPCODE_ESCAPE) ? 2 : 1;

        InstructionData->End          = Byte + InstructionData->OpCodeSize;
        InstructionData->Displacement = InstructionData->End;
        InstructionData->Immediate    = InstructionData->End;
        return EFI_SUCCESS;
    }
  }

  return EFI_ABORTED;
}

/**
  Determine instruction length

  Return the total length of the parsed instruction.

  @param[in] InstructionData  Instruction parsing context

  @return                     Length of parsed instruction

**/
UINT64
CcInstructionLength (
  IN CC_INSTRUCTION_DATA  *InstructionData
  )
{
  return (UINT64)(InstructionData->End - InstructionData->Begin);
}

/**
  Initialize the instruction parsing context.

  Initialize the instruction parsing context, which includes decoding the
  instruction prefixes.

  @param[in, out] InstructionData  Instruction parsing context
  @param[in]      Ghcb             Pointer to the Guest-Hypervisor Communication
                                   Block
  @param[in]      Regs             x64 processor context

  @retval         EFI_SUCCESS      Successfully initialize InstructionData
  @retval         Others           Other error as indicated
**/
EFI_STATUS
CcInitInstructionData (
  IN OUT CC_INSTRUCTION_DATA     *InstructionData,
  IN     GHCB                    *Ghcb,
  IN     EFI_SYSTEM_CONTEXT_X64  *Regs
  )
{
  SetMem (InstructionData, sizeof (*InstructionData), 0);
  InstructionData->Ghcb  = Ghcb;
  InstructionData->Begin = (UINT8 *)Regs->Rip;
  InstructionData->End   = (UINT8 *)Regs->Rip;

  return DecodePrefixes (Regs, InstructionData);
}
