/** @file
    Firmware volume helper interfaces.

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

**/

#include "StandaloneMmCore.h"
#include <Library/FvLib.h>
#include <Library/ExtractGuidedSectionLib.h>

//
// List of file types supported by dispatcher
//
EFI_FV_FILETYPE  mMmFileTypes[] = {
  EFI_FV_FILETYPE_MM,
  0xE, // EFI_FV_FILETYPE_MM_STANDALONE,
       //
       // Note: DXE core will process the FV image file, so skip it in MM core
       // EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE
       //
};

EFI_STATUS
MmAddToDriverList (
  IN EFI_FIRMWARE_VOLUME_HEADER  *FwVolHeader,
  IN VOID                        *Pe32Data,
  IN UINTN                       Pe32DataSize,
  IN VOID                        *Depex,
  IN UINTN                       DepexSize,
  IN EFI_GUID                    *DriverName
  );

BOOLEAN
FvHasBeenProcessed (
  IN EFI_FIRMWARE_VOLUME_HEADER  *FwVolHeader
  );

VOID
FvIsBeingProcessed (
  IN EFI_FIRMWARE_VOLUME_HEADER  *FwVolHeader
  );

/**
  Given the pointer to the Firmware Volume Header find the
  MM driver and return its PE32 image.

  @param [in] FwVolHeader   Pointer to memory mapped FV

  @retval  EFI_SUCCESS            Success.
  @retval  EFI_INVALID_PARAMETER  Invalid parameter.
  @retval  EFI_NOT_FOUND          Could not find section data.
  @retval  EFI_OUT_OF_RESOURCES   Out of resources.
  @retval  EFI_VOLUME_CORRUPTED   Firmware volume is corrupted.
  @retval  EFI_UNSUPPORTED        Operation not supported.

**/
EFI_STATUS
MmCoreFfsFindMmDriver (
  IN  EFI_FIRMWARE_VOLUME_HEADER  *FwVolHeader
  )
{
  EFI_STATUS                  Status;
  EFI_STATUS                  DepexStatus;
  EFI_FFS_FILE_HEADER         *FileHeader;
  EFI_FV_FILETYPE             FileType;
  VOID                        *Pe32Data;
  UINTN                       Pe32DataSize;
  VOID                        *Depex;
  UINTN                       DepexSize;
  UINTN                       Index;
  EFI_COMMON_SECTION_HEADER   *Section;
  VOID                        *SectionData;
  UINTN                       SectionDataSize;
  UINT32                      DstBufferSize;
  VOID                        *ScratchBuffer;
  UINT32                      ScratchBufferSize;
  VOID                        *DstBuffer;
  UINT16                      SectionAttribute;
  UINT32                      AuthenticationStatus;
  EFI_FIRMWARE_VOLUME_HEADER  *InnerFvHeader;

  DEBUG ((DEBUG_INFO, "MmCoreFfsFindMmDriver - 0x%x\n", FwVolHeader));

  if (FvHasBeenProcessed (FwVolHeader)) {
    return EFI_SUCCESS;
  }

  FvIsBeingProcessed (FwVolHeader);

  //
  // First check for encapsulated compressed firmware volumes
  //
  FileHeader = NULL;
  do {
    Status = FfsFindNextFile (
               EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE,
               FwVolHeader,
               &FileHeader
               );
    if (EFI_ERROR (Status)) {
      break;
    }

    Status = FfsFindSectionData (
               EFI_SECTION_GUID_DEFINED,
               FileHeader,
               &SectionData,
               &SectionDataSize
               );
    if (EFI_ERROR (Status)) {
      break;
    }

    Section = (EFI_COMMON_SECTION_HEADER *)(FileHeader + 1);
    Status  = ExtractGuidedSectionGetInfo (
                Section,
                &DstBufferSize,
                &ScratchBufferSize,
                &SectionAttribute
                );
    if (EFI_ERROR (Status)) {
      break;
    }

    //
    // Allocate scratch buffer
    //
    ScratchBuffer = (VOID *)(UINTN)AllocatePages (EFI_SIZE_TO_PAGES (ScratchBufferSize));
    if (ScratchBuffer == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    //
    // Allocate destination buffer, extra one page for adjustment
    //
    DstBuffer = (VOID *)(UINTN)AllocatePages (EFI_SIZE_TO_PAGES (DstBufferSize));
    if (DstBuffer == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    //
    // Call decompress function
    //
    Status = ExtractGuidedSectionDecode (
               Section,
               &DstBuffer,
               ScratchBuffer,
               &AuthenticationStatus
               );
    FreePages (ScratchBuffer, EFI_SIZE_TO_PAGES (ScratchBufferSize));
    if (EFI_ERROR (Status)) {
      goto FreeDstBuffer;
    }

    DEBUG ((
      DEBUG_INFO,
      "Processing compressed firmware volume (AuthenticationStatus == %x)\n",
      AuthenticationStatus
      ));

    Status = FindFfsSectionInSections (
               DstBuffer,
               DstBufferSize,
               EFI_SECTION_FIRMWARE_VOLUME_IMAGE,
               &Section
               );
    if (EFI_ERROR (Status)) {
      goto FreeDstBuffer;
    }

    InnerFvHeader = (VOID *)(Section + 1);
    Status        = MmCoreFfsFindMmDriver (InnerFvHeader);
    if (EFI_ERROR (Status)) {
      goto FreeDstBuffer;
    }
  } while (TRUE);

  for (Index = 0; Index < sizeof (mMmFileTypes) / sizeof (mMmFileTypes[0]); Index++) {
    DEBUG ((DEBUG_INFO, "Check MmFileTypes - 0x%x\n", mMmFileTypes[Index]));
    FileType   = mMmFileTypes[Index];
    FileHeader = NULL;
    do {
      Status = FfsFindNextFile (FileType, FwVolHeader, &FileHeader);
      if (!EFI_ERROR (Status)) {
        Status = FfsFindSectionData (EFI_SECTION_PE32, FileHeader, &Pe32Data, &Pe32DataSize);
        DEBUG ((DEBUG_INFO, "Find PE data - 0x%x\n", Pe32Data));
        DepexStatus = FfsFindSectionData (EFI_SECTION_MM_DEPEX, FileHeader, &Depex, &DepexSize);
        if (!EFI_ERROR (DepexStatus)) {
          MmAddToDriverList (FwVolHeader, Pe32Data, Pe32DataSize, Depex, DepexSize, &FileHeader->Name);
        }
      }
    } while (!EFI_ERROR (Status));
  }

  return EFI_SUCCESS;

FreeDstBuffer:
  FreePages (DstBuffer, EFI_SIZE_TO_PAGES (DstBufferSize));

  return Status;
}