/** @file
  Firmware File System driver that produce Firmware Volume protocol.
  Layers on top of Firmware Block protocol to produce a file abstraction
  of FV based files.

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

**/

#include "DxeMain.h"
#include "FwVolDriver.h"

//
// Protocol notify related globals
//
VOID       *gEfiFwVolBlockNotifyReg;
EFI_EVENT  gEfiFwVolBlockEvent;

FV_DEVICE  mFvDevice = {
  FV2_DEVICE_SIGNATURE,
  NULL,
  NULL,
  {
    FvGetVolumeAttributes,
    FvSetVolumeAttributes,
    FvReadFile,
    FvReadFileSection,
    FvWriteFile,
    FvGetNextFile,
    sizeof (UINTN),
    NULL,
    FvGetVolumeInfo,
    FvSetVolumeInfo
  },
  NULL,
  NULL,
  NULL,
  NULL,
  { NULL,                 NULL},
  0,
  0,
  FALSE,
  FALSE
};

//
// FFS helper functions
//

/**
  Read data from Firmware Block by FVB protocol Read.
  The data may cross the multi block ranges.

  @param  Fvb                   The FW_VOL_BLOCK_PROTOCOL instance from which to read data.
  @param  StartLba              Pointer to StartLba.
                                On input, the start logical block index from which to read.
                                On output,the end logical block index after reading.
  @param  Offset                Pointer to Offset
                                On input, offset into the block at which to begin reading.
                                On output, offset into the end block after reading.
  @param  DataSize              Size of data to be read.
  @param  Data                  Pointer to Buffer that the data will be read into.

  @retval EFI_SUCCESS           Successfully read data from firmware block.
  @retval others
**/
EFI_STATUS
ReadFvbData (
  IN     EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL  *Fvb,
  IN OUT EFI_LBA                             *StartLba,
  IN OUT UINTN                               *Offset,
  IN     UINTN                               DataSize,
  OUT    UINT8                               *Data
  )
{
  UINTN       BlockSize;
  UINTN       NumberOfBlocks;
  UINTN       BlockIndex;
  UINTN       ReadDataSize;
  EFI_STATUS  Status;

  //
  // Try read data in current block
  //
  BlockIndex   = 0;
  ReadDataSize = DataSize;
  Status       = Fvb->Read (Fvb, *StartLba, *Offset, &ReadDataSize, Data);
  if (Status == EFI_SUCCESS) {
    *Offset += DataSize;
    return EFI_SUCCESS;
  } else if (Status != EFI_BAD_BUFFER_SIZE) {
    //
    // other error will direct return
    //
    return Status;
  }

  //
  // Data crosses the blocks, read data from next block
  //
  DataSize -= ReadDataSize;
  Data     += ReadDataSize;
  *StartLba = *StartLba + 1;
  while (DataSize > 0) {
    Status = Fvb->GetBlockSize (Fvb, *StartLba, &BlockSize, &NumberOfBlocks);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // Read data from the crossing blocks
    //
    BlockIndex = 0;
    while (BlockIndex < NumberOfBlocks && DataSize >= BlockSize) {
      Status = Fvb->Read (Fvb, *StartLba + BlockIndex, 0, &BlockSize, Data);
      if (EFI_ERROR (Status)) {
        return Status;
      }

      Data     += BlockSize;
      DataSize -= BlockSize;
      BlockIndex++;
    }

    //
    // Data doesn't exceed the current block range.
    //
    if (DataSize < BlockSize) {
      break;
    }

    //
    // Data must be got from the next block range.
    //
    *StartLba += NumberOfBlocks;
  }

  //
  // read the remaining data
  //
  if (DataSize > 0) {
    Status = Fvb->Read (Fvb, *StartLba + BlockIndex, 0, &DataSize, Data);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  }

  //
  // Update Lba and Offset used by the following read.
  //
  *StartLba += BlockIndex;
  *Offset    = DataSize;

  return EFI_SUCCESS;
}

/**
  Given the supplied FW_VOL_BLOCK_PROTOCOL, allocate a buffer for output and
  copy the real length volume header into it.

  @param  Fvb                   The FW_VOL_BLOCK_PROTOCOL instance from which to
                                read the volume header
  @param  FwVolHeader           Pointer to pointer to allocated buffer in which
                                the volume header is returned.

  @retval EFI_OUT_OF_RESOURCES  No enough buffer could be allocated.
  @retval EFI_SUCCESS           Successfully read volume header to the allocated
                                buffer.
  @retval EFI_INVALID_PARAMETER The FV Header signature is not as expected or
                                the file system could not be understood.

**/
EFI_STATUS
GetFwVolHeader (
  IN     EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL  *Fvb,
  OUT    EFI_FIRMWARE_VOLUME_HEADER          **FwVolHeader
  )
{
  EFI_STATUS                  Status;
  EFI_FIRMWARE_VOLUME_HEADER  TempFvh;
  UINTN                       FvhLength;
  EFI_LBA                     StartLba;
  UINTN                       Offset;
  UINT8                       *Buffer;

  //
  // Read the standard FV header
  //
  StartLba  = 0;
  Offset    = 0;
  FvhLength = sizeof (EFI_FIRMWARE_VOLUME_HEADER);
  Status    = ReadFvbData (Fvb, &StartLba, &Offset, FvhLength, (UINT8 *)&TempFvh);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Validate FV Header signature, if not as expected, continue.
  //
  if (TempFvh.Signature != EFI_FVH_SIGNATURE) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Check to see that the file system is indeed formatted in a way we can
  // understand it...
  //
  if ((!CompareGuid (&TempFvh.FileSystemGuid, &gEfiFirmwareFileSystem2Guid)) &&
      (!CompareGuid (&TempFvh.FileSystemGuid, &gEfiFirmwareFileSystem3Guid)))
  {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Allocate a buffer for the caller
  //
  *FwVolHeader = AllocatePool (TempFvh.HeaderLength);
  if (*FwVolHeader == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Copy the standard header into the buffer
  //
  CopyMem (*FwVolHeader, &TempFvh, sizeof (EFI_FIRMWARE_VOLUME_HEADER));

  //
  // Read the rest of the header
  //
  FvhLength = TempFvh.HeaderLength - sizeof (EFI_FIRMWARE_VOLUME_HEADER);
  Buffer    = (UINT8 *)*FwVolHeader + sizeof (EFI_FIRMWARE_VOLUME_HEADER);
  Status    = ReadFvbData (Fvb, &StartLba, &Offset, FvhLength, Buffer);
  if (EFI_ERROR (Status)) {
    //
    // Read failed so free buffer
    //
    CoreFreePool (*FwVolHeader);
  }

  return Status;
}

/**
  Free FvDevice resource when error happens

  @param  FvDevice              pointer to the FvDevice to be freed.

**/
VOID
FreeFvDeviceResource (
  IN FV_DEVICE  *FvDevice
  )
{
  FFS_FILE_LIST_ENTRY  *FfsFileEntry;
  LIST_ENTRY           *NextEntry;

  //
  // Free File List Entry
  //
  FfsFileEntry = (FFS_FILE_LIST_ENTRY *)FvDevice->FfsFileListHeader.ForwardLink;
  while (&FfsFileEntry->Link != &FvDevice->FfsFileListHeader) {
    NextEntry = (&FfsFileEntry->Link)->ForwardLink;

    if (FfsFileEntry->StreamHandle != 0) {
      //
      // Close stream and free resources from SEP
      //
      CloseSectionStream (FfsFileEntry->StreamHandle, FALSE);
    }

    if (FfsFileEntry->FileCached) {
      //
      // Free the cached file buffer.
      //
      CoreFreePool (FfsFileEntry->FfsHeader);
    }

    CoreFreePool (FfsFileEntry);

    FfsFileEntry = (FFS_FILE_LIST_ENTRY *)NextEntry;
  }

  if (!FvDevice->IsMemoryMapped) {
    //
    // Free the cached FV buffer.
    //
    CoreFreePool (FvDevice->CachedFv);
  }

  //
  // Free Volume Header
  //
  CoreFreePool (FvDevice->FwVolHeader);

  return;
}

/**
  Check if an FV is consistent and allocate cache for it.

  @param  FvDevice              A pointer to the FvDevice to be checked.

  @retval EFI_OUT_OF_RESOURCES  No enough buffer could be allocated.
  @retval EFI_SUCCESS           FV is consistent and cache is allocated.
  @retval EFI_VOLUME_CORRUPTED  File system is corrupted.

**/
EFI_STATUS
FvCheck (
  IN OUT FV_DEVICE  *FvDevice
  )
{
  EFI_STATUS                          Status;
  EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL  *Fvb;
  EFI_FIRMWARE_VOLUME_HEADER          *FwVolHeader;
  EFI_FIRMWARE_VOLUME_EXT_HEADER      *FwVolExtHeader;
  EFI_FVB_ATTRIBUTES_2                FvbAttributes;
  EFI_FV_BLOCK_MAP_ENTRY              *BlockMap;
  FFS_FILE_LIST_ENTRY                 *FfsFileEntry;
  EFI_FFS_FILE_HEADER                 *FfsHeader;
  UINT8                               *CacheLocation;
  UINTN                               Index;
  EFI_LBA                             LbaIndex;
  UINTN                               Size;
  EFI_FFS_FILE_STATE                  FileState;
  UINT8                               *TopFvAddress;
  UINTN                               TestLength;
  EFI_PHYSICAL_ADDRESS                PhysicalAddress;
  BOOLEAN                             FileCached;
  UINTN                               WholeFileSize;
  EFI_FFS_FILE_HEADER                 *CacheFfsHeader;

  FileCached     = FALSE;
  CacheFfsHeader = NULL;

  Fvb         = FvDevice->Fvb;
  FwVolHeader = FvDevice->FwVolHeader;

  Status = Fvb->GetAttributes (Fvb, &FvbAttributes);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Size = (UINTN)FwVolHeader->FvLength;
  if ((FvbAttributes & EFI_FVB2_MEMORY_MAPPED) != 0) {
    FvDevice->IsMemoryMapped = TRUE;

    Status = Fvb->GetPhysicalAddress (Fvb, &PhysicalAddress);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // Don't cache memory mapped FV really.
    //
    FvDevice->CachedFv = (UINT8 *)(UINTN)PhysicalAddress;
  } else {
    FvDevice->IsMemoryMapped = FALSE;
    FvDevice->CachedFv       = AllocatePool (Size);

    if (FvDevice->CachedFv == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }
  }

  //
  // Remember a pointer to the end of the CachedFv
  //
  FvDevice->EndOfCachedFv = FvDevice->CachedFv + Size;

  if (!FvDevice->IsMemoryMapped) {
    //
    // Copy FV into memory using the block map.
    //
    BlockMap      = FwVolHeader->BlockMap;
    CacheLocation = FvDevice->CachedFv;
    LbaIndex      = 0;
    while ((BlockMap->NumBlocks != 0) || (BlockMap->Length != 0)) {
      //
      // read the FV data
      //
      Size = BlockMap->Length;
      for (Index = 0; Index < BlockMap->NumBlocks; Index++) {
        Status = Fvb->Read (
                        Fvb,
                        LbaIndex,
                        0,
                        &Size,
                        CacheLocation
                        );

        //
        // Not check EFI_BAD_BUFFER_SIZE, for Size = BlockMap->Length
        //
        if (EFI_ERROR (Status)) {
          goto Done;
        }

        LbaIndex++;
        CacheLocation += BlockMap->Length;
      }

      BlockMap++;
    }
  }

  //
  // Scan to check the free space & File list
  //
  if ((FvbAttributes & EFI_FVB2_ERASE_POLARITY) != 0) {
    FvDevice->ErasePolarity = 1;
  } else {
    FvDevice->ErasePolarity = 0;
  }

  //
  // go through the whole FV cache, check the consistence of the FV.
  // Make a linked list of all the Ffs file headers
  //
  Status = EFI_SUCCESS;
  InitializeListHead (&FvDevice->FfsFileListHeader);

  //
  // Build FFS list
  //
  if (FwVolHeader->ExtHeaderOffset != 0) {
    //
    // Searching for files starts on an 8 byte aligned boundary after the end of the Extended Header if it exists.
    //
    FwVolExtHeader = (EFI_FIRMWARE_VOLUME_EXT_HEADER *)(FvDevice->CachedFv + FwVolHeader->ExtHeaderOffset);
    FfsHeader      = (EFI_FFS_FILE_HEADER *)((UINT8 *)FwVolExtHeader + FwVolExtHeader->ExtHeaderSize);
  } else {
    FfsHeader = (EFI_FFS_FILE_HEADER *)(FvDevice->CachedFv + FwVolHeader->HeaderLength);
  }

  FfsHeader    = (EFI_FFS_FILE_HEADER *)ALIGN_POINTER (FfsHeader, 8);
  TopFvAddress = FvDevice->EndOfCachedFv;
  while (((UINTN)FfsHeader >= (UINTN)FvDevice->CachedFv) && ((UINTN)FfsHeader <= (UINTN)((UINTN)TopFvAddress - sizeof (EFI_FFS_FILE_HEADER)))) {
    if (FileCached) {
      CoreFreePool (CacheFfsHeader);
      FileCached = FALSE;
    }

    TestLength = TopFvAddress - ((UINT8 *)FfsHeader);
    if (TestLength > sizeof (EFI_FFS_FILE_HEADER)) {
      TestLength = sizeof (EFI_FFS_FILE_HEADER);
    }

    if (IsBufferErased (FvDevice->ErasePolarity, FfsHeader, TestLength)) {
      //
      // We have found the free space so we are done!
      //
      goto Done;
    }

    if (!IsValidFfsHeader (FvDevice->ErasePolarity, FfsHeader, &FileState)) {
      if ((FileState == EFI_FILE_HEADER_INVALID) ||
          (FileState == EFI_FILE_HEADER_CONSTRUCTION))
      {
        if (IS_FFS_FILE2 (FfsHeader)) {
          if (!FvDevice->IsFfs3Fv) {
            DEBUG ((DEBUG_ERROR, "Found a FFS3 formatted file: %g in a non-FFS3 formatted FV.\n", &FfsHeader->Name));
          }

          FfsHeader = (EFI_FFS_FILE_HEADER *)((UINT8 *)FfsHeader + sizeof (EFI_FFS_FILE_HEADER2));
        } else {
          FfsHeader = (EFI_FFS_FILE_HEADER *)((UINT8 *)FfsHeader + sizeof (EFI_FFS_FILE_HEADER));
        }

        continue;
      } else {
        //
        // File system is corrputed
        //
        Status = EFI_VOLUME_CORRUPTED;
        goto Done;
      }
    }

    CacheFfsHeader = FfsHeader;
    if ((CacheFfsHeader->Attributes & FFS_ATTRIB_CHECKSUM) == FFS_ATTRIB_CHECKSUM) {
      if (FvDevice->IsMemoryMapped) {
        //
        // Memory mapped FV has not been cached.
        // Here is to cache FFS file to memory buffer for following checksum calculating.
        // And then, the cached file buffer can be also used for FvReadFile.
        //
        WholeFileSize  = IS_FFS_FILE2 (CacheFfsHeader) ? FFS_FILE2_SIZE (CacheFfsHeader) : FFS_FILE_SIZE (CacheFfsHeader);
        CacheFfsHeader = AllocateCopyPool (WholeFileSize, CacheFfsHeader);
        if (CacheFfsHeader == NULL) {
          Status = EFI_OUT_OF_RESOURCES;
          goto Done;
        }

        FileCached = TRUE;
      }
    }

    if (!IsValidFfsFile (FvDevice->ErasePolarity, CacheFfsHeader)) {
      //
      // File system is corrupted
      //
      Status = EFI_VOLUME_CORRUPTED;
      goto Done;
    }

    if (IS_FFS_FILE2 (CacheFfsHeader)) {
      ASSERT (FFS_FILE2_SIZE (CacheFfsHeader) > 0x00FFFFFF);
      if (!FvDevice->IsFfs3Fv) {
        DEBUG ((DEBUG_ERROR, "Found a FFS3 formatted file: %g in a non-FFS3 formatted FV.\n", &CacheFfsHeader->Name));
        FfsHeader = (EFI_FFS_FILE_HEADER *)((UINT8 *)FfsHeader + FFS_FILE2_SIZE (CacheFfsHeader));
        //
        // Adjust pointer to the next 8-byte aligned boundary.
        //
        FfsHeader = (EFI_FFS_FILE_HEADER *)(((UINTN)FfsHeader + 7) & ~0x07);
        continue;
      }
    }

    FileState = GetFileState (FvDevice->ErasePolarity, CacheFfsHeader);

    //
    // check for non-deleted file
    //
    if (FileState != EFI_FILE_DELETED) {
      //
      // Create a FFS list entry for each non-deleted file
      //
      FfsFileEntry = AllocateZeroPool (sizeof (FFS_FILE_LIST_ENTRY));
      if (FfsFileEntry == NULL) {
        Status = EFI_OUT_OF_RESOURCES;
        goto Done;
      }

      FfsFileEntry->FfsHeader  = CacheFfsHeader;
      FfsFileEntry->FileCached = FileCached;
      FileCached               = FALSE;
      InsertTailList (&FvDevice->FfsFileListHeader, &FfsFileEntry->Link);
    }

    if (IS_FFS_FILE2 (CacheFfsHeader)) {
      FfsHeader = (EFI_FFS_FILE_HEADER *)((UINT8 *)FfsHeader + FFS_FILE2_SIZE (CacheFfsHeader));
    } else {
      FfsHeader = (EFI_FFS_FILE_HEADER *)((UINT8 *)FfsHeader + FFS_FILE_SIZE (CacheFfsHeader));
    }

    //
    // Adjust pointer to the next 8-byte aligned boundary.
    //
    FfsHeader = (EFI_FFS_FILE_HEADER *)(((UINTN)FfsHeader + 7) & ~0x07);
  }

Done:
  if (EFI_ERROR (Status)) {
    if (FileCached) {
      CoreFreePool (CacheFfsHeader);
      FileCached = FALSE;
    }

    FreeFvDeviceResource (FvDevice);
  }

  return Status;
}

/**
  This notification function is invoked when an instance of the
  EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL is produced.  It layers an instance of the
  EFI_FIRMWARE_VOLUME2_PROTOCOL on the same handle.  This is the function where
  the actual initialization of the EFI_FIRMWARE_VOLUME2_PROTOCOL is done.

  @param  Event                 The event that occurred
  @param  Context               For EFI compatiblity.  Not used.

**/
VOID
EFIAPI
NotifyFwVolBlock (
  IN  EFI_EVENT  Event,
  IN  VOID       *Context
  )
{
  EFI_HANDLE                          Handle;
  EFI_STATUS                          Status;
  UINTN                               BufferSize;
  EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL  *Fvb;
  EFI_FIRMWARE_VOLUME2_PROTOCOL       *Fv;
  FV_DEVICE                           *FvDevice;
  EFI_FIRMWARE_VOLUME_HEADER          *FwVolHeader;

  //
  // Examine all new handles
  //
  for ( ; ;) {
    //
    // Get the next handle
    //
    BufferSize = sizeof (Handle);
    Status     = CoreLocateHandle (
                   ByRegisterNotify,
                   NULL,
                   gEfiFwVolBlockNotifyReg,
                   &BufferSize,
                   &Handle
                   );

    //
    // If not found, we're done
    //
    if (EFI_NOT_FOUND == Status) {
      break;
    }

    if (EFI_ERROR (Status)) {
      continue;
    }

    //
    // Get the FirmwareVolumeBlock protocol on that handle
    //
    Status = CoreHandleProtocol (Handle, &gEfiFirmwareVolumeBlockProtocolGuid, (VOID **)&Fvb);
    ASSERT_EFI_ERROR (Status);
    ASSERT (Fvb != NULL);

    //
    // Make sure the Fv Header is O.K.
    //
    Status = GetFwVolHeader (Fvb, &FwVolHeader);
    if (EFI_ERROR (Status)) {
      continue;
    }

    ASSERT (FwVolHeader != NULL);

    if (!VerifyFvHeaderChecksum (FwVolHeader)) {
      CoreFreePool (FwVolHeader);
      continue;
    }

    //
    // Check if there is an FV protocol already installed in that handle
    //
    Status = CoreHandleProtocol (Handle, &gEfiFirmwareVolume2ProtocolGuid, (VOID **)&Fv);
    if (!EFI_ERROR (Status)) {
      //
      // Update Fv to use a new Fvb
      //
      FvDevice = BASE_CR (Fv, FV_DEVICE, Fv);
      if (FvDevice->Signature == FV2_DEVICE_SIGNATURE) {
        //
        // Only write into our device structure if it's our device structure
        //
        FvDevice->Fvb = Fvb;
      }
    } else {
      //
      // No FwVol protocol on the handle so create a new one
      //
      FvDevice = AllocateCopyPool (sizeof (FV_DEVICE), &mFvDevice);
      if (FvDevice == NULL) {
        return;
      }

      FvDevice->Fvb             = Fvb;
      FvDevice->Handle          = Handle;
      FvDevice->FwVolHeader     = FwVolHeader;
      FvDevice->IsFfs3Fv        = CompareGuid (&FwVolHeader->FileSystemGuid, &gEfiFirmwareFileSystem3Guid);
      FvDevice->Fv.ParentHandle = Fvb->ParentHandle;
      //
      // Inherit the authentication status from FVB.
      //
      FvDevice->AuthenticationStatus = GetFvbAuthenticationStatus (Fvb);

      if (!EFI_ERROR (FvCheck (FvDevice))) {
        //
        // Install an New FV protocol on the existing handle
        //
        Status = CoreInstallProtocolInterface (
                   &Handle,
                   &gEfiFirmwareVolume2ProtocolGuid,
                   EFI_NATIVE_INTERFACE,
                   &FvDevice->Fv
                   );
        ASSERT_EFI_ERROR (Status);
      } else {
        //
        // Free FvDevice Buffer for the corrupt FV image.
        //
        CoreFreePool (FvDevice);
      }
    }
  }

  return;
}

/**
  This routine is the driver initialization entry point.  It registers
  a notification function.  This notification function are responsible
  for building the FV stack dynamically.

  @param  ImageHandle           The image handle.
  @param  SystemTable           The system table.

  @retval EFI_SUCCESS           Function successfully returned.

**/
EFI_STATUS
EFIAPI
FwVolDriverInit (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  gEfiFwVolBlockEvent = EfiCreateProtocolNotifyEvent (
                          &gEfiFirmwareVolumeBlockProtocolGuid,
                          TPL_CALLBACK,
                          NotifyFwVolBlock,
                          NULL,
                          &gEfiFwVolBlockNotifyReg
                          );
  return EFI_SUCCESS;
}
