/** @file
*
*  Copyright (c) 2015, Hisilicon Limited. All rights reserved.
*  Copyright (c) 2015, Linaro Limited. All rights reserved.
*
*  SPDX-License-Identifier: BSD-2-Clause-Patent
*
**/


#include <Uefi.h>
#include <Pi/PiDxeCis.h>
#include <Library/DebugLib.h>
#include <libfdt.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PrintLib.h>
#include <Guid/Fdt.h>
#include <Protocol/HisiBoardNicProtocol.h>
#include <Library/DxeServicesTableLib.h>
#include <Library/FdtUpdateLib.h>

STATIC
EFI_STATUS
InstallFdtIntoConfigurationTable (
  IN VOID* FdtBlob,
  IN UINTN FdtSize
  )
{
  EFI_STATUS Status;

  // Check the FDT header is valid. We only make this check in DEBUG mode in case the FDT header change on
  // production device and this ASSERT() becomes not valid.
  if(!(fdt_check_header (FdtBlob) == 0))
  {
      DEBUG ((EFI_D_ERROR,"can not find FdtBlob \n"));
      return EFI_INVALID_PARAMETER;
  }

  // Ensure the Size of the Device Tree is smaller than the size of the read file
  if(!((UINTN)fdt_totalsize (FdtBlob) <= FdtSize))
  {
      DEBUG ((EFI_D_ERROR,"FdtBlob <= FdtSize \n"));
      return EFI_INVALID_PARAMETER;
  }

  // Install the FDT into the Configuration Table
  Status = gBS->InstallConfigurationTable (&gFdtTableGuid, FdtBlob);

  return Status;
}

EFI_STATUS
SetNvramSpace (VOID)
{
    EFI_STATUS          Status;
    EFI_GCD_MEMORY_SPACE_DESCRIPTOR desp = {0};

    if (PcdGet64(PcdReservedNvramSize) == 0) {
      return EFI_SUCCESS;
    }

    Status = gDS->GetMemorySpaceDescriptor(PcdGet64(PcdReservedNvramBase),&desp);
    if(EFI_ERROR(Status)){
         DEBUG ((EFI_D_ERROR,"get memory space error:--------- \n"));
        return Status;
    }
    desp.Attributes |= EFI_MEMORY_RUNTIME | EFI_MEMORY_WB;
    Status = gDS->SetMemorySpaceAttributes(PcdGet64(PcdReservedNvramBase),PcdGet64(PcdReservedNvramSize), desp.Attributes);
    if(EFI_ERROR(Status)){
        DEBUG ((EFI_D_ERROR,"set memory space error:--------- \n"));
        return Status;
    }

    return EFI_SUCCESS;
}


EFI_STATUS
EFIAPI UpdateFdt (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable)
{
    INTN                Error;
    VOID*               Fdt;
    UINT32              Size;
    UINTN               NewFdtBlobSize;
    UINTN               NewFdtBlobBase;
    EFI_STATUS          Status = EFI_SUCCESS;
    UINT32              Index = 0;
    UINTN               FDTConfigTable;

    (VOID) SetNvramSpace ();

    Fdt = (VOID*)(PcdGet64(FdtFileAddress));


    Error = fdt_check_header ((VOID*)(PcdGet64(FdtFileAddress)));
    DEBUG ((EFI_D_ERROR,"fdtfileaddress:--------- 0x%lx\n",PcdGet64(FdtFileAddress)));
    if (Error != 0)
    {
        DEBUG ((EFI_D_ERROR,"ERROR: Device Tree header not valid (%a)\n", fdt_strerror(Error)));
        return EFI_INVALID_PARAMETER;
    }

    Size = (UINTN)fdt_totalsize ((VOID*)(PcdGet64(FdtFileAddress)));
    NewFdtBlobSize = Size + ADD_FILE_LENGTH;

    Status = gBS->AllocatePages (AllocateAnyPages, EfiRuntimeServicesData, EFI_SIZE_TO_PAGES(NewFdtBlobSize), &NewFdtBlobBase);
    if (EFI_ERROR (Status))
    {
        return EFI_OUT_OF_RESOURCES;
    }

    (VOID) CopyMem((VOID*)NewFdtBlobBase, Fdt, Size);

    Status = EFIFdtUpdate(NewFdtBlobBase);
    if (EFI_ERROR (Status))
    {
        DEBUG((EFI_D_ERROR, "%a(%d):EFIFdtUpdate Fail!\n", __FUNCTION__,__LINE__));
        goto EXIT;
    }


    Status = InstallFdtIntoConfigurationTable ((VOID*)(UINTN)NewFdtBlobBase, NewFdtBlobSize);
    DEBUG ((EFI_D_ERROR, "NewFdtBlobBase: 0x%lx  NewFdtBlobSize:0x%lx\n",NewFdtBlobBase,NewFdtBlobSize));
    if (EFI_ERROR (Status))
    {
        DEBUG ((EFI_D_ERROR, "installfdtconfiguration table fail():\n"));
        goto EXIT;
    }


    for (Index = 0; Index < gST->NumberOfTableEntries; Index ++)
    {
        if (CompareGuid (&gFdtTableGuid, &(gST->ConfigurationTable[Index].VendorGuid)))
        {
            FDTConfigTable = (UINTN)gST->ConfigurationTable[Index].VendorTable;
            DEBUG ((EFI_D_ERROR, "FDTConfigTable Address: 0x%lx\n",FDTConfigTable));
            break;
        }
    }

    return Status;

    EXIT:

         gBS->FreePages(NewFdtBlobBase,EFI_SIZE_TO_PAGES(NewFdtBlobSize));

    return Status;

}
