/** @file

  Copyright (c) 2013-2015, ARM Ltd. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "AndroidFastbootApp.h"

#include <Protocol/DevicePath.h>
#include <Protocol/LoadedImage.h>

#include <Library/DevicePathLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>

// Device Path representing an image in memory
#pragma pack(1)
typedef struct {
  MEMMAP_DEVICE_PATH          Node1;
  EFI_DEVICE_PATH_PROTOCOL    End;
} MEMORY_DEVICE_PATH;
#pragma pack()

STATIC CONST MEMORY_DEVICE_PATH  MemoryDevicePathTemplate =
{
  {
    {
      HARDWARE_DEVICE_PATH,
      HW_MEMMAP_DP,
      {
        (UINT8)(sizeof (MEMMAP_DEVICE_PATH)),
        (UINT8)((sizeof (MEMMAP_DEVICE_PATH)) >> 8),
      },
    }, // Header
    0, // StartingAddress (set at runtime)
    0  // EndingAddress   (set at runtime)
  }, // Node1
  {
    END_DEVICE_PATH_TYPE,
    END_ENTIRE_DEVICE_PATH_SUBTYPE,
    { sizeof (EFI_DEVICE_PATH_PROTOCOL), 0 }
  } // End
};

/**
  Start an EFI Application from a Device Path

  @param  ParentImageHandle     Handle of the calling image
  @param  DevicePath            Location of the EFI Application

  @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.

**/
STATIC
EFI_STATUS
StartEfiApplication (
  IN EFI_HANDLE                ParentImageHandle,
  IN EFI_DEVICE_PATH_PROTOCOL  *DevicePath,
  IN UINTN                     LoadOptionsSize,
  IN VOID                      *LoadOptions
  )
{
  EFI_STATUS                 Status;
  EFI_HANDLE                 ImageHandle;
  EFI_LOADED_IMAGE_PROTOCOL  *LoadedImage;

  // Load the image from the device path with Boot Services function
  Status = gBS->LoadImage (
                  TRUE,
                  ParentImageHandle,
                  DevicePath,
                  NULL,
                  0,
                  &ImageHandle
                  );
  if (EFI_ERROR (Status)) {
    //
    // With EFI_SECURITY_VIOLATION retval, the Image was loaded and an ImageHandle was created
    // with a valid EFI_LOADED_IMAGE_PROTOCOL, but the image can not be started right now.
    // If the caller doesn't have the option to defer the execution of an image, we should
    // unload image for the EFI_SECURITY_VIOLATION to avoid resource leak.
    //
    if (Status == EFI_SECURITY_VIOLATION) {
      gBS->UnloadImage (ImageHandle);
    }

    return Status;
  }

  // Passed LoadOptions to the EFI Application
  if (LoadOptionsSize != 0) {
    Status = gBS->HandleProtocol (
                    ImageHandle,
                    &gEfiLoadedImageProtocolGuid,
                    (VOID **)&LoadedImage
                    );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    LoadedImage->LoadOptionsSize = LoadOptionsSize;
    LoadedImage->LoadOptions     = LoadOptions;
  }

  // Before calling the image, enable the Watchdog Timer for  the 5 Minute period
  gBS->SetWatchdogTimer (5 * 60, 0x0000, 0x00, NULL);
  // Start the image
  Status = gBS->StartImage (ImageHandle, NULL, NULL);
  // Clear the Watchdog Timer after the image returns
  gBS->SetWatchdogTimer (0x0000, 0x0000, 0x0000, NULL);

  return Status;
}

EFI_STATUS
BootAndroidBootImg (
  IN UINTN  BufferSize,
  IN VOID   *Buffer
  )
{
  EFI_STATUS          Status;
  CHAR8               KernelArgs[ANDROID_BOOTIMG_KERNEL_ARGS_SIZE];
  VOID                *Kernel;
  UINTN               KernelSize;
  VOID                *Ramdisk;
  UINTN               RamdiskSize;
  MEMORY_DEVICE_PATH  KernelDevicePath;
  CHAR16              *LoadOptions, *NewLoadOptions;

  Status = ParseAndroidBootImg (
             Buffer,
             &Kernel,
             &KernelSize,
             &Ramdisk,
             &RamdiskSize,
             KernelArgs
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  KernelDevicePath = MemoryDevicePathTemplate;

  // Have to cast to UINTN before casting to EFI_PHYSICAL_ADDRESS in order to
  // appease GCC.
  KernelDevicePath.Node1.StartingAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)Kernel;
  KernelDevicePath.Node1.EndingAddress   = (EFI_PHYSICAL_ADDRESS)(UINTN)Kernel + KernelSize;

  // Initialize Linux command line
  LoadOptions = CatSPrint (NULL, L"%a", KernelArgs);
  if (LoadOptions == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  if (RamdiskSize != 0) {
    NewLoadOptions = CatSPrint (
                       LoadOptions,
                       L" initrd=0x%x,0x%x",
                       (UINTN)Ramdisk,
                       RamdiskSize
                       );
    FreePool (LoadOptions);
    if (NewLoadOptions == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    LoadOptions = NewLoadOptions;
  }

  Status = StartEfiApplication (
             gImageHandle,
             (EFI_DEVICE_PATH_PROTOCOL *)&KernelDevicePath,
             StrSize (LoadOptions),
             LoadOptions
             );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Couldn't Boot Linux: %d\n", Status));
    Status = EFI_DEVICE_ERROR;
    goto FreeLoadOptions;
  }

  // If we got here we do a confused face because BootLinuxFdt returned,
  // reporting success.
  DEBUG ((DEBUG_ERROR, "WARNING: BdsBootLinuxFdt returned EFI_SUCCESS.\n"));
  return EFI_SUCCESS;

FreeLoadOptions:
  FreePool (LoadOptions);
  return Status;
}
