/** @file
  Code which supports Software Guard Extensions DXE component

@copyright
 Copyright (c) 1999 - 2016 Intel Corporation. All rights reserved
 This software and associated documentation (if any) is furnished
 under a license and may only be used or copied in accordance
 with the terms of the license. Except as permitted by the
 license, no part of this software or documentation may be
 reproduced, stored in a retrieval system, or transmitted in any
 form or by any means without the express written consent of
 Intel Corporation.
 This file contains an 'Intel Peripheral Driver' and is uniquely
 identified as "Intel Reference Module" and is licensed for Intel
 CPUs and chipsets under the terms of your license agreement with
 Intel or your vendor. This file may be modified by the user, subject
 to additional terms of the license agreement.

@par Specification Reference:
**/

#include <Library/BaseLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <MpService.h>
#include <Protocol/CpuGlobalNvsArea.h>

#ifdef SGX_SUPPORT
#include <Private/Library/SoftwareGuardLib.h>
#include <Private/Library/CpuCommonLib.h>

extern CPU_INIT_DATA_HOB         *mCpuInitDataHob;

/**
 Code to update SGX Global NVS variable in EPC.ASL
**/
VOID
UpdateSgxNvs (
  VOID
  )
{
  EFI_STATUS                    Status;
  EFI_CPUID_REGISTER            CpuidSgxLeaf;
  EFI_CPUID_REGISTER            Cpuid;
  SOFTWARE_GUARD_CONFIG         *SoftwareGuardConfig;
  CPU_GLOBAL_NVS_AREA_PROTOCOL  *CpuGlobalNvsAreaProtocol;

  DEBUG ((DEBUG_INFO, "\n UpdateSgxNvs started \n"));

  SoftwareGuardConfig = (SOFTWARE_GUARD_CONFIG *)(UINTN)mCpuInitDataHob->SoftwareGuardConfig;

  ///
  /// Locate CPU Global NVS
  ///
  Status = gBS->LocateProtocol (&gCpuGlobalNvsAreaProtocolGuid, NULL, (VOID **) &CpuGlobalNvsAreaProtocol);
  if (Status != EFI_SUCCESS) {
    DEBUG ((DEBUG_ERROR, "\n UpdateSgxNvs: Unable to Locate CPU global NVS Protocol, EPC device will not be available\n"));
    return;
  }

  CpuGlobalNvsAreaProtocol->Area->SgxStatus      = 0;
  CpuGlobalNvsAreaProtocol->Area->EpcBaseAddress = 0;
  CpuGlobalNvsAreaProtocol->Area->EpcLength      = 1;

  /**
    Check if all of these conditions are met
    - 1: EnableSgx policy was set to enable
    - 2: SGX feature is supported by CPU
    - 3: PRM was successfully allocated and PRMRRs were set
    - 4: SGX IA32_FEATURE_CONTROL MSR(3Ah) [18] == 1
  **/
  if (IsSgxSupported() && IsPrmrrAlreadySet() && IsSgxFeatureCtrlSet()) {
    CpuidSgxLeaf.RegEax = CPUID_SGX_ENABLED;

    ///
    /// Check if SGX is present
    ///
    if (!IsSgxPresent()) {
      DEBUG ((DEBUG_WARN, "\n SGX is not present\n"));

    } else {

      ///
      /// Read Cpuid.Sgx_LEAF.0x2 to get EPC Base and Size
      ///
      AsmCpuidEx (CpuidSgxLeaf.RegEax, 0x2, &Cpuid.RegEax, &Cpuid.RegEbx, &Cpuid.RegEcx, &Cpuid.RegEdx);

      //
      // Check if the first sub-leaf is a valid EPC section
      //
      if ((Cpuid.RegEax & 0xF) != 0x1) {
        return;
      }
      DEBUG ((DEBUG_INFO, "\n SGX is ENABLED\n"));
      CpuGlobalNvsAreaProtocol->Area->EpcBaseAddress = LShiftU64 ((UINT64) (Cpuid.RegEbx & 0xFFFFF), 32) + (UINT64) (Cpuid.RegEax & 0xFFFFF000);
      CpuGlobalNvsAreaProtocol->Area->EpcLength      = LShiftU64 ((UINT64) (Cpuid.RegEdx & 0xFFFFF), 32) + (UINT64) (Cpuid.RegEcx & 0xFFFFF000);
      CpuGlobalNvsAreaProtocol->Area->SgxStatus      = TRUE;
    }
  } else {
    DEBUG ((DEBUG_ERROR, "\n SGX is not supported\n"));
  }
  DEBUG ((DEBUG_INFO, "CpuGlobalNvsAreaProtocol->Area->SgxStatus      = 0x%X\n",      CpuGlobalNvsAreaProtocol->Area->SgxStatus));
  DEBUG ((DEBUG_INFO, "CpuGlobalNvsAreaProtocol->Area->EpcBaseAddress = 0x%016llX\n", CpuGlobalNvsAreaProtocol->Area->EpcBaseAddress));
  DEBUG ((DEBUG_INFO, "CpuGlobalNvsAreaProtocol->Area->EpcLength      = 0x%016llX\n", CpuGlobalNvsAreaProtocol->Area->EpcLength));
  return;
}

#endif
