/** @file
  This module install ACPI Boot Graphics Resource Table (BGRT).

  Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.<BR>
  Copyright (c) 2016, Microsoft Corporation<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent
**/

#include <Uefi.h>

#include <IndustryStandard/Acpi.h>

#include <Protocol/AcpiTable.h>
#include <Protocol/GraphicsOutput.h>
#include <Protocol/BootLogo.h>
#include <Protocol/BootLogo2.h>

#include <Guid/EventGroup.h>

#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Library/PcdLib.h>
#include <Library/SafeIntLib.h>
#include <Library/BmpSupportLib.h>

/**
  Update information of logo image drawn on screen.

  @param[in] This          The pointer to the Boot Logo protocol 2 instance.
  @param[in] BltBuffer     The BLT buffer for logo drawn on screen. If BltBuffer
                           is set to NULL, it indicates that logo image is no
                           longer on the screen.
  @param[in] DestinationX  X coordinate of destination for the BltBuffer.
  @param[in] DestinationY  Y coordinate of destination for the BltBuffer.
  @param[in] Width         Width of rectangle in BltBuffer in pixels.
  @param[in] Height        Hight of rectangle in BltBuffer in pixels.

  @retval EFI_SUCCESS            The boot logo information was updated.
  @retval EFI_INVALID_PARAMETER  One of the parameters has an invalid value.
  @retval EFI_OUT_OF_RESOURCES   The logo information was not updated due to
                                 insufficient memory resources.
**/
EFI_STATUS
EFIAPI
SetBootLogo (
  IN EFI_BOOT_LOGO_PROTOCOL         *This,
  IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *BltBuffer       OPTIONAL,
  IN UINTN                          DestinationX,
  IN UINTN                          DestinationY,
  IN UINTN                          Width,
  IN UINTN                          Height
  );

/**
  Update information of logo image drawn on screen.

  @param[in] This          The pointer to the Boot Logo protocol 2 instance.
  @param[in] BltBuffer     The BLT buffer for logo drawn on screen. If BltBuffer
                           is set to NULL, it indicates that logo image is no
                           longer on the screen.
  @param[in] DestinationX  X coordinate of destination for the BltBuffer.
  @param[in] DestinationY  Y coordinate of destination for the BltBuffer.
  @param[in] Width         Width of rectangle in BltBuffer in pixels.
  @param[in] Height        Hight of rectangle in BltBuffer in pixels.

  @retval EFI_SUCCESS            The boot logo information was updated.
  @retval EFI_INVALID_PARAMETER  One of the parameters has an invalid value.
  @retval EFI_OUT_OF_RESOURCES   The logo information was not updated due to
                                 insufficient memory resources.
**/
EFI_STATUS
EFIAPI
SetBootLogo2 (
  IN EDKII_BOOT_LOGO2_PROTOCOL      *This,
  IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *BltBuffer       OPTIONAL,
  IN UINTN                          DestinationX,
  IN UINTN                          DestinationY,
  IN UINTN                          Width,
  IN UINTN                          Height
  );

/**
  Get the location of the boot logo on the screen.

  @param[in]  This          The pointer to the Boot Logo Protocol 2 instance
  @param[out] BltBuffer     Returns pointer to the GOP BLT buffer that was
                            previously registered with SetBootLogo2(). The
                            buffer returned must not be modified or freed.
  @param[out] DestinationX  Returns the X start position of the GOP BLT buffer
                            that was previously registered with SetBootLogo2().
  @param[out] DestinationY  Returns the Y start position of the GOP BLT buffer
                            that was previously registered with SetBootLogo2().
  @param[out] Width         Returns the width of the GOP BLT buffer
                            that was previously registered with SetBootLogo2().
  @param[out] Height        Returns the height of the GOP BLT buffer
                            that was previously registered with SetBootLogo2().

  @retval EFI_SUCCESS            The location of the boot logo was returned.
  @retval EFI_NOT_READY          The boot logo has not been set.
  @retval EFI_INVALID_PARAMETER  BltBuffer is NULL.
  @retval EFI_INVALID_PARAMETER  DestinationX is NULL.
  @retval EFI_INVALID_PARAMETER  DestinationY is NULL.
  @retval EFI_INVALID_PARAMETER  Width is NULL.
  @retval EFI_INVALID_PARAMETER  Height is NULL.
**/
EFI_STATUS
EFIAPI
GetBootLogo2 (
  IN  EDKII_BOOT_LOGO2_PROTOCOL      *This,
  OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL  **BltBuffer,
  OUT UINTN                          *DestinationX,
  OUT UINTN                          *DestinationY,
  OUT UINTN                          *Width,
  OUT UINTN                          *Height
  );

//
// Boot Logo Protocol Handle
//
EFI_HANDLE  mBootLogoHandle = NULL;

//
// Boot Logo Protocol Instance
//
EFI_BOOT_LOGO_PROTOCOL  mBootLogoProtocolTemplate = {
  SetBootLogo
};

///
/// Boot Logo 2 Protocol instance
///
EDKII_BOOT_LOGO2_PROTOCOL  mBootLogo2ProtocolTemplate = {
  SetBootLogo2,
  GetBootLogo2
};

EFI_EVENT                      mBootGraphicsReadyToBootEvent;
UINTN                          mBootGraphicsResourceTableKey = 0;
BOOLEAN                        mIsLogoValid                  = FALSE;
EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *mLogoBltBuffer               = NULL;
UINTN                          mLogoDestX                    = 0;
UINTN                          mLogoDestY                    = 0;
UINTN                          mLogoWidth                    = 0;
UINTN                          mLogoHeight                   = 0;
BOOLEAN                        mAcpiBgrtInstalled            = FALSE;
BOOLEAN                        mAcpiBgrtStatusChanged        = FALSE;
BOOLEAN                        mAcpiBgrtBufferChanged        = FALSE;

//
// ACPI Boot Graphics Resource Table template
//
EFI_ACPI_5_0_BOOT_GRAPHICS_RESOURCE_TABLE  mBootGraphicsResourceTableTemplate = {
  {
    EFI_ACPI_5_0_BOOT_GRAPHICS_RESOURCE_TABLE_SIGNATURE,
    sizeof (EFI_ACPI_5_0_BOOT_GRAPHICS_RESOURCE_TABLE),
    EFI_ACPI_5_0_BOOT_GRAPHICS_RESOURCE_TABLE_REVISION, // Revision
    0x00,                                               // Checksum will be updated at runtime
    //
    // It is expected that these values will be updated at EntryPoint.
    //
    { 0x00 },   // OEM ID is a 6 bytes long field
    0x00,       // OEM Table ID(8 bytes long)
    0x00,       // OEM Revision
    0x00,       // Creator ID
    0x00,       // Creator Revision
  },
  EFI_ACPI_5_0_BGRT_VERSION,         // Version
  EFI_ACPI_5_0_BGRT_STATUS_VALID,    // Status
  EFI_ACPI_5_0_BGRT_IMAGE_TYPE_BMP,  // Image Type
  0,                                 // Image Address
  0,                                 // Image Offset X
  0                                  // Image Offset Y
};

/**
  Update information of logo image drawn on screen.

  @param  This           The pointer to the Boot Logo protocol instance.
  @param  BltBuffer      The BLT buffer for logo drawn on screen. If BltBuffer
                         is set to NULL, it indicates that logo image is no
                         longer on the screen.
  @param  DestinationX   X coordinate of destination for the BltBuffer.
  @param  DestinationY   Y coordinate of destination for the BltBuffer.
  @param  Width          Width of rectangle in BltBuffer in pixels.
  @param  Height         Hight of rectangle in BltBuffer in pixels.

  @retval EFI_SUCCESS             The boot logo information was updated.
  @retval EFI_INVALID_PARAMETER   One of the parameters has an invalid value.
  @retval EFI_OUT_OF_RESOURCES    The logo information was not updated due to
                                  insufficient memory resources.

**/
EFI_STATUS
EFIAPI
SetBootLogo (
  IN EFI_BOOT_LOGO_PROTOCOL         *This,
  IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *BltBuffer       OPTIONAL,
  IN UINTN                          DestinationX,
  IN UINTN                          DestinationY,
  IN UINTN                          Width,
  IN UINTN                          Height
  )
{
  //
  // Call same service in Boot Logo 2 Protocol
  //
  return SetBootLogo2 (
           &mBootLogo2ProtocolTemplate,
           BltBuffer,
           DestinationX,
           DestinationY,
           Width,
           Height
           );
}

/**
  Update information of logo image drawn on screen.

  @param[in] This          The pointer to the Boot Logo protocol 2 instance.
  @param[in] BltBuffer     The BLT buffer for logo drawn on screen. If BltBuffer
                           is set to NULL, it indicates that logo image is no
                           longer on the screen.
  @param[in] DestinationX  X coordinate of destination for the BltBuffer.
  @param[in] DestinationY  Y coordinate of destination for the BltBuffer.
  @param[in] Width         Width of rectangle in BltBuffer in pixels.
  @param[in] Height        Hight of rectangle in BltBuffer in pixels.

  @retval EFI_SUCCESS            The boot logo information was updated.
  @retval EFI_INVALID_PARAMETER  One of the parameters has an invalid value.
  @retval EFI_OUT_OF_RESOURCES   The logo information was not updated due to
                                 insufficient memory resources.
**/
EFI_STATUS
EFIAPI
SetBootLogo2 (
  IN EDKII_BOOT_LOGO2_PROTOCOL      *This,
  IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL  *BltBuffer       OPTIONAL,
  IN UINTN                          DestinationX,
  IN UINTN                          DestinationY,
  IN UINTN                          Width,
  IN UINTN                          Height
  )
{
  EFI_STATUS  Status;
  UINTN       BufferSize;
  UINT32      Result32;

  if (BltBuffer == NULL) {
    mIsLogoValid           = FALSE;
    mAcpiBgrtStatusChanged = TRUE;
    return EFI_SUCCESS;
  }

  //
  // Width and height are not allowed to be zero.
  //
  if ((Width == 0) || (Height == 0)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Verify destination, width, and height do not overflow 32-bit values.
  // The Boot Graphics Resource Table only has 32-bit fields for these values.
  //
  Status = SafeUintnToUint32 (DestinationX, &Result32);
  if (EFI_ERROR (Status)) {
    return EFI_INVALID_PARAMETER;
  }

  Status = SafeUintnToUint32 (DestinationY, &Result32);
  if (EFI_ERROR (Status)) {
    return EFI_INVALID_PARAMETER;
  }

  Status = SafeUintnToUint32 (Width, &Result32);
  if (EFI_ERROR (Status)) {
    return EFI_INVALID_PARAMETER;
  }

  Status = SafeUintnToUint32 (Height, &Result32);
  if (EFI_ERROR (Status)) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Ensure the Height * Width * sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL) does
  // not overflow UINTN
  //
  Status = SafeUintnMult (
             Width,
             Height,
             &BufferSize
             );
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  Status = SafeUintnMult (
             BufferSize,
             sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL),
             &BufferSize
             );
  if (EFI_ERROR (Status)) {
    return EFI_UNSUPPORTED;
  }

  //
  // Update state
  //
  mAcpiBgrtBufferChanged = TRUE;

  //
  // Free old logo buffer
  //
  if (mLogoBltBuffer != NULL) {
    FreePool (mLogoBltBuffer);
    mLogoBltBuffer = NULL;
  }

  //
  // Allocate new logo buffer
  //
  mLogoBltBuffer = AllocateCopyPool (BufferSize, BltBuffer);
  if (mLogoBltBuffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  mLogoDestX   = DestinationX;
  mLogoDestY   = DestinationY;
  mLogoWidth   = Width;
  mLogoHeight  = Height;
  mIsLogoValid = TRUE;

  return EFI_SUCCESS;
}

/**
  Get the location of the boot logo on the screen.

  @param[in]  This          The pointer to the Boot Logo Protocol 2 instance
  @param[out] BltBuffer     Returns pointer to the GOP BLT buffer that was
                            previously registered with SetBootLogo2(). The
                            buffer returned must not be modified or freed.
  @param[out] DestinationX  Returns the X start position of the GOP BLT buffer
                            that was previously registered with SetBootLogo2().
  @param[out] DestinationY  Returns the Y start position of the GOP BLT buffer
                            that was previously registered with SetBootLogo2().
  @param[out] Width         Returns the width of the GOP BLT buffer
                            that was previously registered with SetBootLogo2().
  @param[out] Height        Returns the height of the GOP BLT buffer
                            that was previously registered with SetBootLogo2().

  @retval EFI_SUCCESS            The location of the boot logo was returned.
  @retval EFI_NOT_READY          The boot logo has not been set.
  @retval EFI_INVALID_PARAMETER  BltBuffer is NULL.
  @retval EFI_INVALID_PARAMETER  DestinationX is NULL.
  @retval EFI_INVALID_PARAMETER  DestinationY is NULL.
  @retval EFI_INVALID_PARAMETER  Width is NULL.
  @retval EFI_INVALID_PARAMETER  Height is NULL.
**/
EFI_STATUS
EFIAPI
GetBootLogo2 (
  IN  EDKII_BOOT_LOGO2_PROTOCOL      *This,
  OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL  **BltBuffer,
  OUT UINTN                          *DestinationX,
  OUT UINTN                          *DestinationY,
  OUT UINTN                          *Width,
  OUT UINTN                          *Height
  )
{
  //
  // If the boot logo has not been set with SetBootLogo() or SetBootLogo() was
  // called with a NULL BltBuffer then the boot logo is not valid and
  // EFI_NOT_READY is returned.
  //
  if (mLogoBltBuffer == NULL) {
    DEBUG ((DEBUG_ERROR, "Request to get boot logo location before boot logo has been set.\n"));
    return EFI_NOT_READY;
  }

  //
  // Make sure none of the boot logo location parameters are NULL.
  //
  if ((BltBuffer == NULL) || (DestinationX == NULL) || (DestinationY == NULL) ||
      (Width == NULL) || (Height == NULL))
  {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Boot logo is valid.  Return values from module globals.
  //
  *BltBuffer    = mLogoBltBuffer;
  *DestinationX = mLogoDestX;
  *DestinationY = mLogoDestY;
  *Width        = mLogoWidth;
  *Height       = mLogoHeight;

  return EFI_SUCCESS;
}

/**
  Notify function for event group EFI_EVENT_GROUP_READY_TO_BOOT. This is used to
  install the Boot Graphics Resource Table.

  @param[in]  Event   The Event that is being processed.
  @param[in]  Context The Event Context.

**/
VOID
EFIAPI
BgrtReadyToBootEventNotify (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  EFI_STATUS               Status;
  EFI_ACPI_TABLE_PROTOCOL  *AcpiTableProtocol;
  VOID                     *ImageBuffer;
  UINT32                   BmpSize;

  //
  // Get ACPI Table protocol.
  //
  Status = gBS->LocateProtocol (
                  &gEfiAcpiTableProtocolGuid,
                  NULL,
                  (VOID **)&AcpiTableProtocol
                  );
  if (EFI_ERROR (Status)) {
    return;
  }

  //
  // Check whether Boot Graphics Resource Table is already installed.
  //
  if (mAcpiBgrtInstalled) {
    if (!mAcpiBgrtStatusChanged && !mAcpiBgrtBufferChanged) {
      //
      // Nothing has changed
      //
      return;
    } else {
      //
      // If BGRT data change happens, then uninstall orignal AcpiTable first
      //
      Status = AcpiTableProtocol->UninstallAcpiTable (
                                    AcpiTableProtocol,
                                    mBootGraphicsResourceTableKey
                                    );
      if (EFI_ERROR (Status)) {
        return;
      }
    }
  } else {
    //
    // Check whether Logo exists
    //
    if (mLogoBltBuffer == NULL) {
      return;
    }
  }

  if (mAcpiBgrtBufferChanged) {
    //
    // Free the old BMP image buffer
    //
    ImageBuffer = (UINT8 *)(UINTN)mBootGraphicsResourceTableTemplate.ImageAddress;
    if (ImageBuffer != NULL) {
      FreePool (ImageBuffer);
    }

    //
    // Convert GOP Blt buffer to BMP image.  Pass in ImageBuffer set to NULL
    // so the BMP image is allocated by TranslateGopBltToBmp().
    //
    ImageBuffer = NULL;
    Status      = TranslateGopBltToBmp (
                    mLogoBltBuffer,
                    (UINT32)mLogoHeight,
                    (UINT32)mLogoWidth,
                    &ImageBuffer,
                    &BmpSize
                    );
    if (EFI_ERROR (Status)) {
      return;
    }

    //
    // Free the logo buffer
    //
    FreePool (mLogoBltBuffer);
    mLogoBltBuffer = NULL;

    //
    // Update BMP image fields of the Boot Graphics Resource Table
    //
    mBootGraphicsResourceTableTemplate.ImageAddress = (UINT64)(UINTN)ImageBuffer;
    mBootGraphicsResourceTableTemplate.ImageOffsetX = (UINT32)mLogoDestX;
    mBootGraphicsResourceTableTemplate.ImageOffsetY = (UINT32)mLogoDestY;
  }

  //
  // Update Status field of Boot Graphics Resource Table
  //
  if (mIsLogoValid) {
    mBootGraphicsResourceTableTemplate.Status = EFI_ACPI_5_0_BGRT_STATUS_VALID;
  } else {
    mBootGraphicsResourceTableTemplate.Status = EFI_ACPI_5_0_BGRT_STATUS_INVALID;
  }

  //
  // Update Checksum of Boot Graphics Resource Table
  //
  mBootGraphicsResourceTableTemplate.Header.Checksum = 0;
  mBootGraphicsResourceTableTemplate.Header.Checksum =
    CalculateCheckSum8 (
      (UINT8 *)&mBootGraphicsResourceTableTemplate,
      sizeof (EFI_ACPI_5_0_BOOT_GRAPHICS_RESOURCE_TABLE)
      );

  //
  // Publish Boot Graphics Resource Table.
  //
  Status = AcpiTableProtocol->InstallAcpiTable (
                                AcpiTableProtocol,
                                &mBootGraphicsResourceTableTemplate,
                                sizeof (EFI_ACPI_5_0_BOOT_GRAPHICS_RESOURCE_TABLE),
                                &mBootGraphicsResourceTableKey
                                );
  if (EFI_ERROR (Status)) {
    return;
  }

  mAcpiBgrtInstalled     = TRUE;
  mAcpiBgrtStatusChanged = FALSE;
  mAcpiBgrtBufferChanged = FALSE;
}

/**
  The module Entry Point of the Boot Graphics Resource Table DXE driver.

  @param[in]  ImageHandle    The firmware allocated handle for the EFI image.
  @param[in]  SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS    The entry point is executed successfully.
  @retval Other          Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
BootGraphicsDxeEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS                   Status;
  EFI_ACPI_DESCRIPTION_HEADER  *Header;

  //
  // Update Header fields of Boot Graphics Resource Table from PCDs
  //
  Header = &mBootGraphicsResourceTableTemplate.Header;
  ZeroMem (Header->OemId, sizeof (Header->OemId));
  CopyMem (
    Header->OemId,
    PcdGetPtr (PcdAcpiDefaultOemId),
    MIN (PcdGetSize (PcdAcpiDefaultOemId), sizeof (Header->OemId))
    );
  WriteUnaligned64 (&Header->OemTableId, PcdGet64 (PcdAcpiDefaultOemTableId));
  Header->OemRevision     = PcdGet32 (PcdAcpiDefaultOemRevision);
  Header->CreatorId       = PcdGet32 (PcdAcpiDefaultCreatorId);
  Header->CreatorRevision = PcdGet32 (PcdAcpiDefaultCreatorRevision);

  //
  // Install Boot Logo and Boot Logo 2 Protocols.
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &mBootLogoHandle,
                  &gEfiBootLogoProtocolGuid,
                  &mBootLogoProtocolTemplate,
                  &gEdkiiBootLogo2ProtocolGuid,
                  &mBootLogo2ProtocolTemplate,
                  NULL
                  );
  ASSERT_EFI_ERROR (Status);

  //
  // Register notify function to install BGRT on ReadyToBoot Event.
  //
  Status = gBS->CreateEventEx (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  BgrtReadyToBootEventNotify,
                  NULL,
                  &gEfiEventReadyToBootGuid,
                  &mBootGraphicsReadyToBootEvent
                  );
  ASSERT_EFI_ERROR (Status);

  return Status;
}
