/** @file
*
*  Copyright (c) 2012-2014, ARM Limited. All rights reserved.
*
*  SPDX-License-Identifier: BSD-2-Clause-Patent
*
**/

#include <Library/IoLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>

#include <Protocol/SimpleFileSystem.h>

#include "BootMonFsInternal.h"

UINT32
BootMonFsChecksum (
  IN VOID   *Data,
  IN UINT32 Size
  )
{
  UINT32  *Ptr;
  UINT32  Word;
  UINT32  Checksum;

  ASSERT (Size % 4 == 0);

  Checksum = 0;
  Ptr = (UINT32*)Data;

  while (Size > 0) {
    Word = *Ptr++;
    Size -= 4;

    if (Word > ~Checksum) {
      Checksum++;
    }

    Checksum += Word;
  }

  return ~Checksum;
}

EFI_STATUS
BootMonFsComputeFooterChecksum (
  IN OUT HW_IMAGE_DESCRIPTION *Footer
  )
{
  HW_IMAGE_DESCRIPTION *Description;
  UINT32                Index;

  Footer->Attributes = 1;

  Description = AllocateZeroPool (sizeof (HW_IMAGE_DESCRIPTION));
  if (Description == NULL) {
    DEBUG ((DEBUG_ERROR, "BootMonFsComputeFooterChecksum: Unable to allocate memory.\n"));
    return EFI_OUT_OF_RESOURCES;
  }

  // Copy over to temporary shim
  CopyMem (Description, Footer, sizeof (HW_IMAGE_DESCRIPTION));

  // BootMon doesn't checksum the previous checksum
  Description->FooterChecksum = 0;

  // Blank out regions which aren't being used.
  for (Index = Footer->RegionCount; Index < HW_IMAGE_DESCRIPTION_REGION_MAX; Index++) {
    Description->Region[Index].Checksum = 0;
    Description->Region[Index].LoadAddress = 0;
    Description->Region[Index].Offset = 0;
    Description->Region[Index].Size = 0;
  }

  // Compute the checksum
  Footer->FooterChecksum = BootMonFsChecksum (Description, sizeof (HW_IMAGE_DESCRIPTION));

  FreePool (Description);

  return EFI_SUCCESS;
}

BOOLEAN
BootMonFsIsImageValid (
  IN HW_IMAGE_DESCRIPTION  *Desc,
  IN EFI_LBA                Lba
  )
{
  EFI_STATUS            Status;
  HW_IMAGE_FOOTER      *Footer;
  UINT32                Checksum;

  Footer = &Desc->Footer;

  // Check that the verification bytes are present
  if ((Footer->FooterSignature1 != HW_IMAGE_FOOTER_SIGNATURE_1) ||
      (Footer->FooterSignature2 != HW_IMAGE_FOOTER_SIGNATURE_2)) {
    return FALSE;
  }

  if (Footer->Version == HW_IMAGE_FOOTER_VERSION) {
    if (Footer->Offset != HW_IMAGE_FOOTER_OFFSET) {
      return FALSE;
    }
  } else if (Footer->Version == HW_IMAGE_FOOTER_VERSION2) {
    if (Footer->Offset != HW_IMAGE_FOOTER_OFFSET2) {
      return FALSE;
    }
  } else {
    return FALSE;
  }

  Checksum = Desc->FooterChecksum;
  Status = BootMonFsComputeFooterChecksum (Desc);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Warning: failed to compute checksum for image '%a'\n", Desc->Footer.Filename));
  }

  if (Desc->FooterChecksum != Checksum) {
    DEBUG ((DEBUG_ERROR, "Warning: image '%a' checksum mismatch.\n", Desc->Footer.Filename));
  }

  if ((Desc->BlockEnd != Lba) || (Desc->BlockStart > Desc->BlockEnd)) {
    return FALSE;
  }

  return TRUE;
}

STATIC
EFI_STATUS
BootMonFsDiscoverNextImage (
  IN     BOOTMON_FS_INSTANCE      *Instance,
  IN OUT EFI_LBA                  *LbaStart,
  IN OUT BOOTMON_FS_FILE          *File
  )
{
  EFI_DISK_IO_PROTOCOL  *DiskIo;
  EFI_LBA                CurrentLba;
  UINT64                 DescOffset;
  EFI_STATUS             Status;

  DiskIo = Instance->DiskIo;

  CurrentLba = *LbaStart;

  // Look for images in the rest of this block
  while (CurrentLba <= Instance->Media->LastBlock) {
    // Work out the byte offset into media of the image description in this block
    // If present, the image description is at the very end of the block.
    DescOffset = ((CurrentLba + 1) * Instance->Media->BlockSize) - sizeof (HW_IMAGE_DESCRIPTION);

    // Read the image description from media
    Status = DiskIo->ReadDisk (DiskIo,
                       Instance->Media->MediaId,
                       DescOffset,
                       sizeof (HW_IMAGE_DESCRIPTION),
                       &File->HwDescription
                       );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    // If we found a valid image description...
    if (BootMonFsIsImageValid (&File->HwDescription, (CurrentLba - Instance->Media->LowestAlignedLba))) {
      DEBUG ((EFI_D_ERROR, "Found image: %a in block %d.\n",
        &(File->HwDescription.Footer.Filename),
        (UINTN)(CurrentLba - Instance->Media->LowestAlignedLba)
        ));
      File->HwDescAddress = DescOffset;

      *LbaStart = CurrentLba + 1;
      return EFI_SUCCESS;
    } else {
      CurrentLba++;
    }
  }

  *LbaStart = CurrentLba;
  return EFI_NOT_FOUND;
}

EFI_STATUS
BootMonFsInitialize (
  IN BOOTMON_FS_INSTANCE *Instance
  )
{
  EFI_STATUS               Status;
  EFI_LBA                  Lba;
  UINT32                   ImageCount;
  BOOTMON_FS_FILE          *NewFile;

  ImageCount = 0;
  Lba = 0;

  while (1) {
    Status = BootMonFsCreateFile (Instance, &NewFile);
    if (EFI_ERROR (Status)) {
      return Status;
    }

    Status = BootMonFsDiscoverNextImage (Instance, &Lba, NewFile);
    if (EFI_ERROR (Status)) {
      // Free NewFile allocated by BootMonFsCreateFile ()
      FreePool (NewFile);
      break;
    }
    InsertTailList (&Instance->RootFile->Link, &NewFile->Link);
    ImageCount++;
  }

  Instance->Initialized = TRUE;
  return EFI_SUCCESS;
}
