/** @file
*
*  Copyright (c) 2011-2012, ARM Limited. All rights reserved.
*
*  This program and the accompanying materials
*  are licensed and made available under the terms and conditions of the BSD License
*  which accompanies this distribution.  The full text of the license may be found at
*  http://opensource.org/licenses/bsd-license.php
*
*  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
*  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
*
**/

#include "BdsInternal.h"

STATIC
EFI_STATUS
BdsLoadFileFromFirmwareVolume (
  IN  EFI_HANDLE      FvHandle,
  IN  CHAR16    *FilePath,
  IN  EFI_FV_FILETYPE FileTypeFilter,
  OUT EFI_DEVICE_PATH     **EfiAppDevicePath
  )
{
  EFI_FIRMWARE_VOLUME2_PROTOCOL *FvProtocol;
  VOID                          *Key;
  EFI_STATUS                    Status, FileStatus;
  EFI_GUID                      NameGuid;
  EFI_FV_FILETYPE               FileType;
  EFI_FV_FILE_ATTRIBUTES        Attributes;
  UINTN                         Size;
  UINTN                         UiStringLen;
  CHAR16                        *UiSection;
  UINT32                        Authentication;
  EFI_DEVICE_PATH               *FvDevicePath;
  MEDIA_FW_VOL_FILEPATH_DEVICE_PATH    FileDevicePath;

  Status = gBS->HandleProtocol (FvHandle,&gEfiFirmwareVolume2ProtocolGuid, (VOID **)&FvProtocol);
  if (EFI_ERROR(Status)) {
    return Status;
  }

  // Length of FilePath
  UiStringLen = StrLen (FilePath);

  // Allocate Key
  Key = AllocatePool (FvProtocol->KeySize);
  ASSERT (Key != NULL);
  ZeroMem (Key, FvProtocol->KeySize);

  do {
    // Search in all files
    FileType = FileTypeFilter;

    Status = FvProtocol->GetNextFile (FvProtocol, Key, &FileType, &NameGuid, &Attributes, &Size);
    if (!EFI_ERROR (Status)) {
      UiSection = NULL;
      FileStatus = FvProtocol->ReadSection (
                    FvProtocol,
                    &NameGuid,
                    EFI_SECTION_USER_INTERFACE,
                    0,
                    (VOID **)&UiSection,
                    &Size,
                    &Authentication
                    );
      if (!EFI_ERROR (FileStatus)) {
        if (StrnCmp (FilePath, UiSection, UiStringLen) == 0) {
          //
          // We found a UiString match.
          //
          Status = gBS->HandleProtocol (FvHandle, &gEfiDevicePathProtocolGuid, (VOID **)&FvDevicePath);

          // Generate the Device Path for the file
          //DevicePath = DuplicateDevicePath(FvDevicePath);
          EfiInitializeFwVolDevicepathNode (&FileDevicePath, &NameGuid);
          *EfiAppDevicePath = AppendDevicePathNode (FvDevicePath, (EFI_DEVICE_PATH_PROTOCOL *)&FileDevicePath);

          FreePool (Key);
          FreePool (UiSection);
          return FileStatus;
        }
        FreePool (UiSection);
      }
    }
  } while (!EFI_ERROR (Status));

  FreePool(Key);
  return Status;
}

/**
  Start an EFI Application from any Firmware Volume

  @param  EfiApp                EFI Application Name

  @retval EFI_SUCCESS           All drivers have been connected
  @retval EFI_NOT_FOUND         The Linux kernel Device Path has not been found
  @retval EFI_OUT_OF_RESOURCES  There is not enough resource memory to store the matching results.

**/
EFI_STATUS
BdsLoadApplication (
  IN EFI_HANDLE                  ParentImageHandle,
  IN CHAR16*                     EfiApp,
  IN UINTN                       LoadOptionsSize,
  IN VOID*                       LoadOptions
  )
{
  EFI_STATUS                      Status;
  UINTN                           NoHandles, HandleIndex;
  EFI_HANDLE                      *Handles;
  EFI_DEVICE_PATH                 *EfiAppDevicePath;

  // Need to connect every drivers to ensure no dependencies are missing for the application
  Status = BdsConnectAllDrivers();
  if (EFI_ERROR(Status)) {
    DEBUG ((EFI_D_ERROR, "FAIL to connect all drivers\n"));
    return Status;
  }

  // Search the application in any Firmware Volume
  Status = gBS->LocateHandleBuffer (ByProtocol, &gEfiFirmwareVolume2ProtocolGuid, NULL, &NoHandles, &Handles);
  if (EFI_ERROR (Status) || (NoHandles == 0)) {
    DEBUG ((EFI_D_ERROR, "FAIL to find Firmware Volume\n"));
    return Status;
  }

  // Search in all Firmware Volume for the EFI Application
  for (HandleIndex = 0; HandleIndex < NoHandles; HandleIndex++) {
    EfiAppDevicePath = NULL;
    Status = BdsLoadFileFromFirmwareVolume (Handles[HandleIndex], EfiApp, EFI_FV_FILETYPE_APPLICATION, &EfiAppDevicePath);
    if (!EFI_ERROR (Status)) {
      // Start the application
      Status = BdsStartEfiApplication (ParentImageHandle, EfiAppDevicePath, LoadOptionsSize, LoadOptions);
      return Status;
    }
  }

  return Status;
}
