/** @file
  Dxe ResetSystem library implementation.

  Copyright (c) 2022 Loongson Technology Corporation Limited. All rights reserved.<BR>

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

**/

#include <Base.h>
#include <Library/DebugLib.h>
#include <Library/UefiRuntimeLib.h> // EfiConvertPointer()
#include <Library/DxeServicesTableLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include "ResetSystemAcpiGed.h"
#include <Library/UefiLib.h>

/**
  Modifies the attributes to Runtime type for a page size memory region.

  @param  BaseAddress            Specified start address

  @retval EFI_SUCCESS           The attributes were set for the memory region.
  @retval EFI_INVALID_PARAMETER Length is zero.
  @retval EFI_UNSUPPORTED       The processor does not support one or more bytes of the memory
                                resource range specified by BaseAddress and Length.
  @retval EFI_UNSUPPORTED       The bit mask of attributes is not support for the memory resource
                                range specified by BaseAddress and Length.
  @retval EFI_ACCESS_DEFINED    The attributes for the memory resource range specified by
                                BaseAddress and Length cannot be modified.
  @retval EFI_OUT_OF_RESOURCES  There are not enough system resources to modify the attributes of
                                the memory resource range.
  @retval EFI_NOT_AVAILABLE_YET The attributes cannot be set because CPU architectural protocol is
                                not available yet.
**/
EFI_STATUS
SetMemoryAttributesRunTime (
  UINTN Address
  )
{
  EFI_STATUS  Status;
  EFI_GCD_MEMORY_SPACE_DESCRIPTOR  Descriptor;

  Address &= ~EFI_PAGE_MASK;

  Status = gDS->GetMemorySpaceDescriptor (Address, &Descriptor);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "%a: GetMemorySpaceDescriptor failed\n", __FUNCTION__));
    return Status;
  }

  if (Descriptor.GcdMemoryType == EfiGcdMemoryTypeNonExistent) {
    Status = gDS->AddMemorySpace (
                  EfiGcdMemoryTypeMemoryMappedIo,
                  Address,
                  EFI_PAGE_SIZE,
                  EFI_MEMORY_UC | EFI_MEMORY_RUNTIME
                  );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_INFO, "%a: AddMemorySpace failed\n", __FUNCTION__));
      return Status;
    }

    Status = gDS->SetMemorySpaceAttributes (
                  Address,
                  EFI_PAGE_SIZE,
                  EFI_MEMORY_RUNTIME
                  );
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_INFO, "%a:%d SetMemorySpaceAttributes failed\n", __FUNCTION__, __LINE__));
      return Status;
    }
  } else if (!(Descriptor.Attributes & EFI_MEMORY_RUNTIME)) {
    Status = gDS->SetMemorySpaceAttributes (
                  Address,
                  EFI_PAGE_SIZE,
                  Descriptor.Attributes | EFI_MEMORY_RUNTIME
                  );

    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_INFO, "%a:%d SetMemorySpaceAttributes failed\n", __FUNCTION__, __LINE__));
      return Status;
    }
  }
  return EFI_SUCCESS;
}

/**
  Find the power manager related info from ACPI table

  @retval RETURN_SUCCESS     Successfully find out all the required information.
  @retval RETURN_NOT_FOUND   Failed to find the required info.
**/
STATIC EFI_STATUS
GetPowerManagerByParseAcpiInfo (
  VOID
)
{
  EFI_ACPI_5_0_FIXED_ACPI_DESCRIPTION_TABLE    *Fadt = NULL;
  EFI_ACPI_3_0_ROOT_SYSTEM_DESCRIPTION_POINTER *Rsdp = NULL;
  EFI_ACPI_DESCRIPTION_HEADER                  *Xsdt = NULL;
  EFI_ACPI_DESCRIPTION_HEADER                  *Rsdt = NULL;
  UINT32                                       *Entry32 = NULL;
  UINTN                                         Entry32Num;
  UINT32                                       *Signature = NULL;
  UINTN                                         Idx;
  EFI_STATUS           Status;

  Status = EfiGetSystemConfigurationTable (&gEfiAcpiTableGuid, (VOID **)&Rsdp);
  if (EFI_ERROR (Status)) {
    Status = EfiGetSystemConfigurationTable (&gEfiAcpi10TableGuid, (VOID **)&Rsdp);
  }

  if (EFI_ERROR (Status) || (Rsdp == NULL)) {
    DEBUG ((DEBUG_ERROR, "EFI_ERROR or Rsdp == NULL\n"));
    return RETURN_NOT_FOUND;
  }

  Rsdt = (EFI_ACPI_DESCRIPTION_HEADER *)(UINTN)Rsdp->RsdtAddress;
  Entry32    = (UINT32 *)(UINTN)(Rsdt + 1);
  Entry32Num = (Rsdt->Length - sizeof (EFI_ACPI_DESCRIPTION_HEADER)) >> 2;
  for (Idx = 0; Idx < Entry32Num; Idx++) {
    Signature = (UINT32 *)(UINTN)Entry32[Idx];
    if (*Signature == EFI_ACPI_5_0_FIXED_ACPI_DESCRIPTION_TABLE_SIGNATURE) {
      Fadt = (EFI_ACPI_5_0_FIXED_ACPI_DESCRIPTION_TABLE *)Signature;
      DEBUG ((DEBUG_INFO, "Found Fadt in Rsdt\n"));
      goto Done;
    }
  }

  Xsdt = (EFI_ACPI_DESCRIPTION_HEADER *)Rsdp->XsdtAddress;
  Entry32    = (UINT32 *)(Xsdt + 1);
  Entry32Num = (Xsdt->Length - sizeof (EFI_ACPI_DESCRIPTION_HEADER)) >> 2;
  for (Idx = 0; Idx < Entry32Num; Idx++) {
    Signature = (UINT32 *)(UINTN)Entry32[Idx];
    if (*Signature == EFI_ACPI_5_0_FIXED_ACPI_DESCRIPTION_TABLE_SIGNATURE) {
      Fadt = (EFI_ACPI_5_0_FIXED_ACPI_DESCRIPTION_TABLE *)Signature;
      DEBUG ((DEBUG_INFO, "Found Fadt in Xsdt\n"));
      goto Done;
    }
  }

  DEBUG ((DEBUG_ERROR, " Fadt Not Found\n"));
  return RETURN_NOT_FOUND;

Done:
  mPowerManager.ResetRegAddr        = Fadt->ResetReg.Address;
  mPowerManager.ResetValue          = Fadt->ResetValue;
  mPowerManager.SleepControlRegAddr = Fadt->SleepControlReg.Address;
  mPowerManager.SleepStatusRegAddr  = Fadt->SleepStatusReg.Address;
  return RETURN_SUCCESS;
}

/**
  This is a notification function registered on EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE
  event. It converts a pointer to a new virtual address.

  @param[in] Event        Event whose notification function is being invoked.
  @param[in] Context      Pointer to the notification function's context
**/
VOID
EFIAPI
ResetSystemLibAddressChangeEvent (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  EfiConvertPointer (0, (VOID **)&mPowerManager.SleepControlRegAddr);
  EfiConvertPointer (0, (VOID **)&mPowerManager.SleepStatusRegAddr);
  EfiConvertPointer (0, (VOID **)&mPowerManager.ResetRegAddr);
}

/**
  Notification function of ACPI Table change.

  This is a notification function registered on ACPI Table change event.
  It saves the Century address stored in ACPI FADT table.

  @param  Event        Event whose notification function is being invoked.
  @param  Context      Pointer to the notification function's context.
**/
STATIC VOID
AcpiNotificationEvent (
  IN EFI_EVENT Event,
  IN VOID      *Context
  )
{
  EFI_STATUS                       Status;

  Status = GetPowerManagerByParseAcpiInfo ();
  if (EFI_ERROR (Status)) {
    return ;
  }

  DEBUG ((DEBUG_INFO, "%a: sleepControl %llx\n", __FUNCTION__, mPowerManager.SleepControlRegAddr));
  ASSERT (mPowerManager.SleepControlRegAddr);
  Status =  SetMemoryAttributesRunTime (mPowerManager.SleepControlRegAddr);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "%a:%d\n",  __FUNCTION__, __LINE__));
    return ;
  }

  DEBUG ((DEBUG_INFO, "%a: sleepStatus %llx\n", __FUNCTION__, mPowerManager.SleepStatusRegAddr));
  ASSERT (mPowerManager.SleepStatusRegAddr);
  Status =  SetMemoryAttributesRunTime (mPowerManager.SleepStatusRegAddr);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "%a:%d\n",  __FUNCTION__, __LINE__));
    return ;
  }

  DEBUG ((DEBUG_INFO, "%a: ResetReg %llx\n", __FUNCTION__, mPowerManager.ResetRegAddr));
  ASSERT (mPowerManager.ResetRegAddr);
  Status =  SetMemoryAttributesRunTime (mPowerManager.ResetRegAddr);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_INFO, "%a:%d\n",  __FUNCTION__, __LINE__));
  }
  return ;
}

/**
  The constructor function to Register ACPI Table change event and Address Change Event.

  @retval EFI_SUCCESS   The constructor always returns RETURN_SUCCESS.
**/
EFI_STATUS
EFIAPI
ResetSystemLibConstructor (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;
  EFI_EVENT   Event;
  EFI_EVENT   ResetSystemVirtualNotifyEvent;

  Status = gBS->CreateEventEx (
                    EVT_NOTIFY_SIGNAL,
                    TPL_CALLBACK,
                    AcpiNotificationEvent,
                    NULL,
                    &gEfiAcpiTableGuid,
                    &Event
                    );

  //
  // Register SetVirtualAddressMap () notify function
  //
  Status = gBS->CreateEvent (
                  EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE,
                  TPL_NOTIFY,
                  ResetSystemLibAddressChangeEvent,
                  NULL,
                  &ResetSystemVirtualNotifyEvent
                  );
  ASSERT_EFI_ERROR (Status);
  return Status;
}
