/** @file
  Var Check Hii generation from FV.

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

**/

#include "VarCheckHiiGen.h"

// {d0bc7cb4-6a47-495f-aa11-710746da06a2}
#define EFI_VFR_ATTRACT_GUID \
{ 0xd0bc7cb4, 0x6a47, 0x495f, { 0xaa, 0x11, 0x71, 0x7, 0x46, 0xda, 0x6, 0xa2 } }

EFI_GUID  gVfrArrayAttractGuid  = EFI_VFR_ATTRACT_GUID;

#define ALL_FF_GUID \
{ 0xFFFFFFFF, 0xFFFF, 0xFFFF, { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } }

EFI_GUID mAllFfGuid             = ALL_FF_GUID;

#define VAR_CHECK_VFR_DRIVER_INFO_SIGNATURE     SIGNATURE_32 ('V', 'D', 'R', 'I')

typedef struct {
  UINTN             Signature;
  LIST_ENTRY        Link;
  EFI_GUID          *DriverGuid;
} VAR_CHECK_VFR_DRIVER_INFO;

LIST_ENTRY                      mVfrDriverList = INITIALIZE_LIST_HEAD_VARIABLE (mVfrDriverList);

#define VAR_CHECK_VFR_DRIVER_INFO_FROM_LINK(a)  CR (a, VAR_CHECK_VFR_DRIVER_INFO, Link, VAR_CHECK_VFR_DRIVER_INFO_SIGNATURE)

#define MAX_MATCH_GUID_NUM      100

/**
  Get the address by Guid.

  Parse the FFS and find the GUID address.
  There may be multiple Guids matching the searched Guid.

  @param Ffs                Pointer to the FFS.
  @param Guid               Guid to find.
  @param Length             The length of FFS.
  @param Offset             Pointer to pointer to the offset.
  @param NumOfMatchingGuid  The number of matching Guid.

  @retval EFI_SUCCESS       One or multiple Guids matching the searched Guid.
  @retval EFI_NOT_FOUND     No Guid matching the searched Guid.

**/
EFI_STATUS
GetAddressByGuid (
  IN  VOID          *Ffs,
  IN  EFI_GUID      *Guid,
  IN  UINTN         Length,
  OUT UINTN         **Offset,
  OUT UINT8         *NumOfMatchingGuid
  )
{
  UINTN     LoopControl;
  BOOLEAN   Found;

  if((Ffs == NULL) || (Guid == NULL) || (Length == 0)){
    return EFI_NOT_FOUND;
  }

  if (NumOfMatchingGuid != NULL) {
    *NumOfMatchingGuid = 0;
  }

  Found = FALSE;
  for (LoopControl = 0; LoopControl < Length; LoopControl++) {
    if (CompareGuid (Guid, (EFI_GUID *) ((UINT8 *) Ffs + LoopControl))) {
      Found = TRUE;
      //
      // If NumOfMatchGuid or Offset are NULL, means user only want
      // to check whether current FFS includes this Guid or not.
      //
      if ((NumOfMatchingGuid != NULL) && (Offset != NULL)) {
        if (*NumOfMatchingGuid == 0) {
          *Offset = InternalVarCheckAllocateZeroPool (sizeof (UINTN) * MAX_MATCH_GUID_NUM);
          ASSERT (*Offset != NULL);
        }
        *(*Offset + *NumOfMatchingGuid) = LoopControl + sizeof (EFI_GUID);
        (*NumOfMatchingGuid)++;
      } else {
        break;
      }
    }
  }

  return (Found ? EFI_SUCCESS : EFI_NOT_FOUND);
}

/**
  Search the VfrBin Base address.

  According to the known GUID gVfrArrayAttractGuid to get the base address from FFS.

  @param Ffs                    Pointer to the FFS.
  @param EfiAddr                Pointer to the EFI in FFS
  @param Length                 The length of FFS.
  @param Offset                 Pointer to pointer to the Addr (Offset).
  @param NumOfMatchingOffset    The number of Addr (Offset).

  @retval EFI_SUCCESS           Get the address successfully.
  @retval EFI_NOT_FOUND         No VfrBin found.

**/
EFI_STATUS
SearchVfrBinInFfs (
   IN  VOID      *Ffs,
   IN  VOID      *EfiAddr,
   IN  UINTN     Length,
   OUT UINTN     **Offset,
   OUT UINT8     *NumOfMatchingOffset
  )
{
  UINTN        Index;
  EFI_STATUS   Status;
  UINTN        VirOffValue;

  if ((Ffs == NULL) || (Offset == NULL)) {
    return EFI_NOT_FOUND;
  }
  Status = GetAddressByGuid (
             Ffs,
             &gVfrArrayAttractGuid,
             Length,
             Offset,
             NumOfMatchingOffset
             );
  if (Status != EFI_SUCCESS) {
    return Status;
  }

  for (Index = 0; Index < *NumOfMatchingOffset; Index++) {
    //
    // Got the virOffset after the GUID
    //
    VirOffValue = *(UINTN *) ((UINTN) Ffs + *(*Offset + Index));
    //
    // Transfer the offset to the VA address. One modules may own multiple VfrBin address.
    //
    *(*Offset + Index) = (UINTN) EfiAddr + VirOffValue;
  }

  return Status;
}

/**
  Parse FFS.

  @param[in] Fv2            Pointer to Fv2 protocol.
  @param[in] DriverGuid     Pointer to driver GUID.

  @return Found the driver in the FV or not.

**/
BOOLEAN
ParseFfs (
  IN EFI_FIRMWARE_VOLUME2_PROTOCOL  *Fv2,
  IN EFI_GUID                       *DriverGuid
  )
{
  EFI_STATUS                    Status;
  EFI_FV_FILETYPE               FoundType;
  EFI_FV_FILE_ATTRIBUTES        FileAttributes;
  UINT32                        AuthenticationStatus;
  UINTN                         Size;
  VOID                          *Buffer;
  UINTN                         SectionSize;
  VOID                          *SectionBuffer;
  UINTN                         VfrBinIndex;
  UINT8                         NumberofMatchingVfrBin;
  UINTN                         *VfrBinBaseAddress;

  Status = Fv2->ReadFile (
                  Fv2,
                  DriverGuid,
                  NULL,
                  &Size,
                  &FoundType,
                  &FileAttributes,
                  &AuthenticationStatus
                  );
  if (EFI_ERROR (Status)) {
    return FALSE;
  }

  Buffer = NULL;
  Status = Fv2->ReadSection (
                  Fv2,
                  DriverGuid,
                  EFI_SECTION_RAW,
                  0, // Instance
                  &Buffer,
                  &Size,
                  &AuthenticationStatus
                  );
   if (!EFI_ERROR (Status)) {
     Status = SearchVfrBinInFfs (Buffer, 0, Size, &VfrBinBaseAddress, &NumberofMatchingVfrBin);
     if (!EFI_ERROR (Status)) {
        SectionBuffer = NULL;
        Status = Fv2->ReadSection (
                        Fv2,
                        DriverGuid,
                        EFI_SECTION_PE32,
                        0, // Instance
                        &SectionBuffer,
                        &SectionSize,
                        &AuthenticationStatus
                        );
        if (!EFI_ERROR (Status)) {
          DEBUG ((DEBUG_INFO , "FfsNameGuid - %g\n", DriverGuid));
          DEBUG ((DEBUG_INFO , "NumberofMatchingVfrBin - 0x%02x\n", NumberofMatchingVfrBin));

          for (VfrBinIndex = 0; VfrBinIndex < NumberofMatchingVfrBin; VfrBinIndex++) {
#ifdef DUMP_HII_DATA
            DEBUG_CODE (
              DumpHiiPackage ((UINT8 *) (UINTN) SectionBuffer + VfrBinBaseAddress[VfrBinIndex] + sizeof (UINT32));
              );
#endif
            VarCheckParseHiiPackage ((UINT8 *) (UINTN) SectionBuffer + VfrBinBaseAddress[VfrBinIndex] + sizeof (UINT32), TRUE);
          }

          FreePool (SectionBuffer);
        }

        InternalVarCheckFreePool (VfrBinBaseAddress);
      }

      FreePool (Buffer);
    }

   return TRUE;
}

/**
  Parse FVs.

  @param[in] ScanAll    Scan all modules in all FVs or not.

**/
VOID
ParseFv (
  IN BOOLEAN    ScanAll
  )
{
  EFI_STATUS                    Status;
  EFI_HANDLE                    *HandleBuffer;
  UINTN                         HandleCount;
  UINTN                         Index;
  EFI_FIRMWARE_VOLUME2_PROTOCOL *Fv2;
  VOID                          *Key;
  EFI_FV_FILETYPE               FileType;
  EFI_GUID                      NameGuid;
  EFI_FV_FILE_ATTRIBUTES        FileAttributes;
  UINTN                         Size;
  UINTN                         FfsIndex;
  VAR_CHECK_VFR_DRIVER_INFO     *VfrDriverInfo;
  LIST_ENTRY                    *VfrDriverLink;

  HandleBuffer = NULL;
  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  &gEfiFirmwareVolume2ProtocolGuid,
                  NULL,
                  &HandleCount,
                  &HandleBuffer
                  );
  if (EFI_ERROR (Status)) {
    return;
  }

  //
  // Search all FVs
  //
  for (Index = 0; Index < HandleCount; Index++) {
    DEBUG ((DEBUG_INFO , "FvIndex - %x\n", Index));
    Status = gBS->HandleProtocol (
                    HandleBuffer[Index],
                    &gEfiFirmwareVolume2ProtocolGuid,
                    (VOID **) &Fv2
                    );
    ASSERT_EFI_ERROR (Status);

    DEBUG_CODE (
      EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL   *Fvb2;
      EFI_PHYSICAL_ADDRESS                  FvAddress;
      UINT64                                FvSize;

      Status = gBS->HandleProtocol (
                      HandleBuffer[Index],
                      &gEfiFirmwareVolumeBlock2ProtocolGuid,
                      (VOID **) &Fvb2
                      );
      ASSERT_EFI_ERROR (Status);
      Status = Fvb2->GetPhysicalAddress (Fvb2, &FvAddress);
      if (!EFI_ERROR (Status)) {
        DEBUG ((DEBUG_INFO , "FvAddress - 0x%08x\n", FvAddress));
        FvSize = ((EFI_FIRMWARE_VOLUME_HEADER *) (UINTN) FvAddress)->FvLength;
        DEBUG ((DEBUG_INFO , "FvSize    - 0x%08x\n", FvSize));
      }
    );

    if (ScanAll) {
      //
      // Need to parse all modules in all FVs.
      //
      Key = InternalVarCheckAllocateZeroPool (Fv2->KeySize);
      ASSERT (Key != NULL);

      for (FfsIndex = 0; ; FfsIndex++) {
        FileType = EFI_FV_FILETYPE_ALL;
        Status = Fv2->GetNextFile (
                        Fv2,
                        Key,
                        &FileType,
                        &NameGuid,
                        &FileAttributes,
                        &Size
                      );
        if (EFI_ERROR (Status)) {
          break;
        }

        ParseFfs (Fv2, &NameGuid);
      }

      InternalVarCheckFreePool (Key);
    } else {
      //
      // Only parse drivers in the VFR drivers list.
      //
      VfrDriverLink = mVfrDriverList.ForwardLink;
      while (VfrDriverLink != &mVfrDriverList) {
        VfrDriverInfo = VAR_CHECK_VFR_DRIVER_INFO_FROM_LINK (VfrDriverLink);
        VfrDriverLink = VfrDriverLink->ForwardLink;
        if (ParseFfs (Fv2, VfrDriverInfo->DriverGuid)) {
          //
          // Found the driver in the FV.
          //
          RemoveEntryList (&VfrDriverInfo->Link);
          InternalVarCheckFreePool (VfrDriverInfo);
        }
      }
    }
  }

  FreePool (HandleBuffer);
}

/**
  Create Vfr Driver List.

  @param[in] DriverGuidArray    Driver Guid Array

**/
VOID
CreateVfrDriverList (
  IN EFI_GUID   *DriverGuidArray
  )
{
  UINTN                         Index;
  VAR_CHECK_VFR_DRIVER_INFO     *VfrDriverInfo;

  for (Index = 0; !IsZeroGuid (&DriverGuidArray[Index]); Index++) {
     DEBUG ((DEBUG_INFO , "CreateVfrDriverList: %g\n", &DriverGuidArray[Index]));
     VfrDriverInfo = InternalVarCheckAllocateZeroPool (sizeof (*VfrDriverInfo));
     ASSERT (VfrDriverInfo != NULL);
     VfrDriverInfo->Signature = VAR_CHECK_VFR_DRIVER_INFO_SIGNATURE;
     VfrDriverInfo->DriverGuid = &DriverGuidArray[Index];
     InsertTailList (&mVfrDriverList, &VfrDriverInfo->Link);
  }
}

/**
  Destroy Vfr Driver List.

**/
VOID
DestroyVfrDriverList (
  VOID
  )
{
  VAR_CHECK_VFR_DRIVER_INFO     *VfrDriverInfo;
  LIST_ENTRY                    *VfrDriverLink;

  while (mVfrDriverList.ForwardLink != &mVfrDriverList) {
    VfrDriverLink = mVfrDriverList.ForwardLink;
    VfrDriverInfo = VAR_CHECK_VFR_DRIVER_INFO_FROM_LINK (VfrDriverLink);
    RemoveEntryList (&VfrDriverInfo->Link);
    InternalVarCheckFreePool (VfrDriverInfo);
  }
}

/**
  Generate from FV.

**/
VOID
VarCheckHiiGenFromFv (
  VOID
  )
{
  EFI_GUID      *DriverGuidArray;
  BOOLEAN       ScanAll;

  DEBUG ((DEBUG_INFO , "VarCheckHiiGenDxeFromFv\n"));

  //
  // Get vfr driver guid array from PCD.
  //
  DriverGuidArray = (EFI_GUID *) PcdGetPtr (PcdVarCheckVfrDriverGuidArray);

  if (IsZeroGuid (&DriverGuidArray[0])) {
    //
    // No VFR driver will be parsed from FVs.
    //
    return;
  }

  if (CompareGuid (&DriverGuidArray[0], &mAllFfGuid)) {
    ScanAll = TRUE;
  } else {
    ScanAll = FALSE;
    CreateVfrDriverList (DriverGuidArray);
  }

  ParseFv (ScanAll);

  if (!ScanAll) {
    DestroyVfrDriverList ();
  }
}
