/** @file
Implementation of helper routines for DXE environment.

Copyright (c) 2013 - 2016 Intel Corporation.

SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <PiDxe.h>

#include <Library/UefiBootServicesTableLib.h>
#include <Library/S3BootScriptLib.h>
#include <Library/DxeServicesLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Protocol/SmmBase2.h>
#include <Protocol/Spi.h>
#include <Protocol/VariableLock.h>

#include <Guid/MemoryConfigData.h>
#include <Guid/QuarkVariableLock.h>

#include "CommonHeader.h"

#define FLASH_BLOCK_SIZE            SIZE_4KB

//
// Global variables.
//
EFI_SPI_PROTOCOL                    *mPlatHelpSpiProtocolRef = NULL;

//
// Routines defined in other source modules of this component.
//

//
// Routines local to this component.
//

//
// Routines shared with other souce modules in this component.
//

EFI_SPI_PROTOCOL *
LocateSpiProtocol (
  IN  EFI_SMM_SYSTEM_TABLE2             *Smst
  )
{
  if (mPlatHelpSpiProtocolRef == NULL) {
    if (Smst != NULL) {
      Smst->SmmLocateProtocol (
              &gEfiSmmSpiProtocolGuid,
              NULL,
              (VOID **) &mPlatHelpSpiProtocolRef
              );
    } else {
      gBS->LocateProtocol (
             &gEfiSpiProtocolGuid,
             NULL,
             (VOID **) &mPlatHelpSpiProtocolRef
             );
    }
    ASSERT (mPlatHelpSpiProtocolRef != NULL);
  }
  return mPlatHelpSpiProtocolRef;
}

//
// Routines exported by this source module.
//

/**
  Find pointer to RAW data in Firmware volume file.

  @param   FvNameGuid       Firmware volume to search. If == NULL search all.
  @param   FileNameGuid     Firmware volume file to search for.
  @param   SectionData      Pointer to RAW data section of found file.
  @param   SectionDataSize  Pointer to UNITN to get size of RAW data.

  @retval  EFI_SUCCESS            Raw Data found.
  @retval  EFI_INVALID_PARAMETER  FileNameGuid == NULL.
  @retval  EFI_NOT_FOUND          Firmware volume file not found.
  @retval  EFI_UNSUPPORTED        Unsupported in current enviroment (PEI or DXE).

**/
EFI_STATUS
EFIAPI
PlatformFindFvFileRawDataSection (
  IN CONST EFI_GUID                 *FvNameGuid OPTIONAL,
  IN CONST EFI_GUID                 *FileNameGuid,
  OUT VOID                          **SectionData,
  OUT UINTN                         *SectionDataSize
  )
{
  if (FileNameGuid == NULL || SectionData == NULL || SectionDataSize == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  if (FvNameGuid != NULL) {
    return EFI_UNSUPPORTED;  // Searching in specific FV unsupported in DXE.
  }

  return GetSectionFromAnyFv (FileNameGuid, EFI_SECTION_RAW, 0, SectionData, SectionDataSize);
}

/**
  Find free spi protect register and write to it to protect a flash region.

  @param   DirectValue      Value to directly write to register.
                            if DirectValue == 0 the use Base & Length below.
  @param   BaseAddress      Base address of region in Flash Memory Map.
  @param   Length           Length of region to protect.

  @retval  EFI_SUCCESS      Free spi protect register found & written.
  @retval  EFI_NOT_FOUND    Free Spi protect register not found.
  @retval  EFI_DEVICE_ERROR Unable to write to spi protect register.
**/
EFI_STATUS
EFIAPI
PlatformWriteFirstFreeSpiProtect (
  IN CONST UINT32                         DirectValue,
  IN CONST UINT32                         BaseAddress,
  IN CONST UINT32                         Length
  )
{
  UINT32                            FreeOffset;
  UINT32                            PchRootComplexBar;
  EFI_STATUS                        Status;

  PchRootComplexBar = QNC_RCRB_BASE;

  Status = WriteFirstFreeSpiProtect (
             PchRootComplexBar,
             DirectValue,
             BaseAddress,
             Length,
             &FreeOffset
             );

  if (!EFI_ERROR (Status)) {
    S3BootScriptSaveMemWrite (
      S3BootScriptWidthUint32,
        (UINTN) (PchRootComplexBar + FreeOffset),
        1,
        (VOID *) (UINTN) (PchRootComplexBar + FreeOffset)
        );
  }

  return Status;
}

/**
  Lock legacy SPI static configuration information.

  Function will assert if unable to lock config.

**/
VOID
EFIAPI
PlatformFlashLockConfig (
  VOID
  )
{
  EFI_STATUS        Status;
  EFI_SPI_PROTOCOL  *SpiProtocol;

  //
  // Enable lock of legacy SPI static configuration information.
  //

  SpiProtocol = LocateSpiProtocol (NULL);  // This routine will not be called in SMM.
  ASSERT (SpiProtocol != NULL);
  if (SpiProtocol != NULL) {
    Status = SpiProtocol->Lock (SpiProtocol);

    if (!EFI_ERROR (Status)) {
      DEBUG ((EFI_D_INFO, "Platform: Spi Config Locked Down\n"));
    } else if (Status == EFI_ACCESS_DENIED) {
      DEBUG ((EFI_D_INFO, "Platform: Spi Config already locked down\n"));
    } else {
      ASSERT_EFI_ERROR (Status);
    }
  }
}

/**
  Platform Variable Lock.

  @retval EFI_SUCCESS           Platform Variable Lock successful.
  @retval EFI_NOT_FOUND         No protocol instances were found that match Protocol and
                                Registration.

**/
VOID
EFIAPI
PlatformVariableLock (
  )
{
  EFI_STATUS                        Status;
  EDKII_VARIABLE_LOCK_PROTOCOL      *VariableLockProtocol;

  Status = gBS->LocateProtocol (&gEdkiiVariableLockProtocolGuid, NULL, (VOID **)&VariableLockProtocol);
  ASSERT_EFI_ERROR (Status);

  Status = VariableLockProtocol->RequestToLock (
                                   VariableLockProtocol,
                                   QUARK_VARIABLE_LOCK_NAME,
                                   &gQuarkVariableLockGuid
                                   );
  ASSERT_EFI_ERROR (Status);

  // Memory Config Data shouldn't be writable when Quark Variable Lock is enabled.
  Status = VariableLockProtocol->RequestToLock (
                                   VariableLockProtocol,
                                   EFI_MEMORY_CONFIG_DATA_NAME,
                                   &gEfiMemoryConfigDataGuid
                                   );
  ASSERT_EFI_ERROR (Status);
}

/**
  Lock regions and config of SPI flash given the policy for this platform.

  Function will assert if unable to lock regions or config.

  @param   PreBootPolicy    If TRUE do Pre Boot Flash Lock Policy.

**/
VOID
EFIAPI
PlatformFlashLockPolicy (
  IN CONST BOOLEAN                        PreBootPolicy
  )
{
  EFI_STATUS                        Status;
  UINT64                            CpuAddressNvStorage;
  UINT64                            CpuAddressFlashDevice;
  UINT64                            SpiAddress;
  EFI_BOOT_MODE                     BootMode;
  UINTN                             SpiFlashDeviceSize;

  BootMode = GetBootModeHob ();

  SpiFlashDeviceSize = (UINTN) PcdGet32 (PcdSpiFlashDeviceSize);
  CpuAddressFlashDevice = SIZE_4GB - SpiFlashDeviceSize;
  DEBUG (
      (EFI_D_INFO,
      "Platform:FlashDeviceSize = 0x%08x Bytes\n",
      SpiFlashDeviceSize)
      );

  //
  // If not in update or recovery mode, lock stuff down
  //
  if ((BootMode != BOOT_IN_RECOVERY_MODE) && (BootMode != BOOT_ON_FLASH_UPDATE)) {

    //
    // Lock regions
    //
    CpuAddressNvStorage = (UINT64) PcdGet32 (PcdFlashNvStorageVariableBase);

    //
    // Lock from start of flash device up to Smi writable flash storage areas.
    //
    SpiAddress = 0;
    if (!PlatformIsSpiRangeProtected ((UINT32) SpiAddress, (UINT32) (CpuAddressNvStorage - CpuAddressFlashDevice))) {
      DEBUG (
        (EFI_D_INFO,
        "Platform: Protect Region Base:Len 0x%08x:0x%08x\n",
        (UINTN) SpiAddress, (UINTN)(CpuAddressNvStorage - CpuAddressFlashDevice))
        );
      Status = PlatformWriteFirstFreeSpiProtect (
                  0,
                  (UINT32) SpiAddress,
                  (UINT32) (CpuAddressNvStorage - CpuAddressFlashDevice)
                  );

      ASSERT_EFI_ERROR (Status);
    }
    //
    // Move Spi Address to after Smi writable flash storage areas.
    //
    SpiAddress = CpuAddressNvStorage - CpuAddressFlashDevice;
    SpiAddress += ((UINT64) PcdGet32 (PcdFlashNvStorageVariableSize));

    //
    // Lock from end of OEM area to end of flash part.
    //
    if (!PlatformIsSpiRangeProtected ((UINT32) SpiAddress, SpiFlashDeviceSize - ((UINT32) SpiAddress))) {
      DEBUG (
        (EFI_D_INFO,
        "Platform: Protect Region Base:Len 0x%08x:0x%08x\n",
        (UINTN) SpiAddress,
        (UINTN) (SpiFlashDeviceSize - ((UINT32) SpiAddress)))
        );
      ASSERT (SpiAddress < ((UINT64) SpiFlashDeviceSize));
      Status = PlatformWriteFirstFreeSpiProtect (
                  0,
                  (UINT32) SpiAddress,
                  SpiFlashDeviceSize - ((UINT32) SpiAddress)
                  );

      ASSERT_EFI_ERROR (Status);
    }
  }

  //
  // Always Lock flash config registers if about to boot a boot option
  // else lock depending on boot mode.
  //
  if (PreBootPolicy || (BootMode != BOOT_ON_FLASH_UPDATE)) {
    PlatformFlashLockConfig ();
  }

  //
  // Enable Quark Variable lock if PreBootPolicy.
  //
  if (PreBootPolicy) {
    PlatformVariableLock ();
  }
}

/** Check if System booted with recovery Boot Stage1 image.

  @retval  TRUE    If system booted with recovery Boot Stage1 image.
  @retval  FALSE   If system booted with normal stage1 image.

**/
BOOLEAN
EFIAPI
PlatformIsBootWithRecoveryStage1 (
  VOID
  )
{
  ASSERT_EFI_ERROR (EFI_UNSUPPORTED);
  return FALSE;
}

