/** @file
  This file deals with Architecture Protocol (AP) registration in
  the Dxe Core. The mArchProtocols[] array represents a list of
  events that represent the Architectural Protocols.

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

**/

#include "DxeMain.h"

//
// DXE Core Global Variables for all of the Architectural Protocols.
// If a protocol is installed mArchProtocols[].Present will be TRUE.
//
// CoreNotifyOnArchProtocolInstallation () fills in mArchProtocols[].Event
// and mArchProtocols[].Registration as it creates events for every array
// entry.
//
EFI_CORE_PROTOCOL_NOTIFY_ENTRY  mArchProtocols[] = {
  { &gEfiSecurityArchProtocolGuid,         (VOID **)&gSecurity,      NULL, NULL, FALSE },
  { &gEfiCpuArchProtocolGuid,              (VOID **)&gCpu,           NULL, NULL, FALSE },
  { &gEfiMetronomeArchProtocolGuid,        (VOID **)&gMetronome,     NULL, NULL, FALSE },
  { &gEfiTimerArchProtocolGuid,            (VOID **)&gTimer,         NULL, NULL, FALSE },
  { &gEfiBdsArchProtocolGuid,              (VOID **)&gBds,           NULL, NULL, FALSE },
  { &gEfiWatchdogTimerArchProtocolGuid,    (VOID **)&gWatchdogTimer, NULL, NULL, FALSE },
  { &gEfiRuntimeArchProtocolGuid,          (VOID **)&gRuntime,       NULL, NULL, FALSE },
  { &gEfiVariableArchProtocolGuid,         (VOID **)NULL,            NULL, NULL, FALSE },
  { &gEfiVariableWriteArchProtocolGuid,    (VOID **)NULL,            NULL, NULL, FALSE },
  { &gEfiCapsuleArchProtocolGuid,          (VOID **)NULL,            NULL, NULL, FALSE },
  { &gEfiMonotonicCounterArchProtocolGuid, (VOID **)NULL,            NULL, NULL, FALSE },
  { &gEfiResetArchProtocolGuid,            (VOID **)NULL,            NULL, NULL, FALSE },
  { &gEfiRealTimeClockArchProtocolGuid,    (VOID **)NULL,            NULL, NULL, FALSE },
  { NULL,                                  (VOID **)NULL,            NULL, NULL, FALSE }
};

//
// Optional protocols that the DXE Core will use if they are present
//
EFI_CORE_PROTOCOL_NOTIFY_ENTRY  mOptionalProtocols[] = {
  { &gEfiSecurity2ArchProtocolGuid,        (VOID **)&gSecurity2,     NULL, NULL, FALSE },
  { &gEfiSmmBase2ProtocolGuid,             (VOID **)&gSmmBase2,      NULL, NULL, FALSE },
  { NULL,                                  (VOID **)NULL,            NULL, NULL, FALSE }
};

//
// Following is needed to display missing architectural protocols in debug builds
//
typedef struct {
  EFI_GUID  *ProtocolGuid;
  CHAR8     *GuidString;
} GUID_TO_STRING_PROTOCOL_ENTRY;

GLOBAL_REMOVE_IF_UNREFERENCED CONST GUID_TO_STRING_PROTOCOL_ENTRY mMissingProtocols[] = {
  { &gEfiSecurityArchProtocolGuid,         "Security"           },
  { &gEfiCpuArchProtocolGuid,              "CPU"                },
  { &gEfiMetronomeArchProtocolGuid,        "Metronome"          },
  { &gEfiTimerArchProtocolGuid,            "Timer"              },
  { &gEfiBdsArchProtocolGuid,              "Bds"                },
  { &gEfiWatchdogTimerArchProtocolGuid,    "Watchdog Timer"     },
  { &gEfiRuntimeArchProtocolGuid,          "Runtime"            },
  { &gEfiVariableArchProtocolGuid,         "Variable"           },
  { &gEfiVariableWriteArchProtocolGuid,    "Variable Write"     },
  { &gEfiCapsuleArchProtocolGuid,          "Capsule"            },
  { &gEfiMonotonicCounterArchProtocolGuid, "Monotonic Counter"  },
  { &gEfiResetArchProtocolGuid,            "Reset"              },
  { &gEfiRealTimeClockArchProtocolGuid,    "Real Time Clock"    },
  { NULL,                                  ""                   }
};

/**
  Return TRUE if all AP services are available.

  @retval EFI_SUCCESS    All AP services are available
  @retval EFI_NOT_FOUND  At least one AP service is not available

**/
EFI_STATUS
CoreAllEfiServicesAvailable (
  VOID
  )
{
  EFI_CORE_PROTOCOL_NOTIFY_ENTRY  *Entry;

  for (Entry = mArchProtocols; Entry->ProtocolGuid != NULL; Entry++) {
    if (!Entry->Present) {
      return EFI_NOT_FOUND;
    }
  }
  return EFI_SUCCESS;
}


/**
  Notification event handler registered by CoreNotifyOnArchProtocolInstallation ().
  This notify function is registered for every architectural protocol. This handler
  updates mArchProtocol[] array entry with protocol instance data and sets it's
  present flag to TRUE. If any constructor is required it is executed. The EFI
  System Table headers are updated.

  @param  Event          The Event that is being processed, not used.
  @param  Context        Event Context, not used.

**/
VOID
EFIAPI
GenericProtocolNotify (
  IN  EFI_EVENT  Event,
  IN  VOID       *Context
  )
{
  EFI_STATUS                      Status;
  EFI_CORE_PROTOCOL_NOTIFY_ENTRY  *Entry;
  VOID                            *Protocol;
  LIST_ENTRY                      *Link;
  LIST_ENTRY                      TempLinkNode;

  Protocol = NULL;

  //
  // Get Entry from Context
  //
  Entry = (EFI_CORE_PROTOCOL_NOTIFY_ENTRY *)Context;

  //
  // See if the expected protocol is present in the handle database
  //
  Status = CoreLocateProtocol (Entry->ProtocolGuid, Entry->Registration, &Protocol);
  if (EFI_ERROR (Status)) {
    return;
  }

  //
  // Mark the protocol as present
  //
  Entry->Present = TRUE;

  //
  // Update protocol global variable if one exists. Entry->Protocol points to a global variable
  // if one exists in the DXE core for this Architectural Protocol
  //
  if (Entry->Protocol != NULL) {
    *(Entry->Protocol) = Protocol;
  }

  //
  // Do special operations for Architectural Protocols
  //

  if (CompareGuid (Entry->ProtocolGuid, &gEfiTimerArchProtocolGuid)) {
    //
    // Register the Core timer tick handler with the Timer AP
    //
    gTimer->RegisterHandler (gTimer, CoreTimerTick);
  }

  if (CompareGuid (Entry->ProtocolGuid, &gEfiRuntimeArchProtocolGuid)) {
    //
    // When runtime architectural protocol is available, updates CRC32 in the Debug Table
    //
    CoreUpdateDebugTableCrc32 ();

    //
    // Update the Runtime Architectural protocol with the template that the core was
    // using so there would not need to be a dependency on the Runtime AP
    //

    //
    // Copy all the registered Image to new gRuntime protocol
    //
    for (Link = gRuntimeTemplate.ImageHead.ForwardLink; Link != &gRuntimeTemplate.ImageHead; Link = TempLinkNode.ForwardLink) {
      CopyMem (&TempLinkNode, Link, sizeof(LIST_ENTRY));
      InsertTailList (&gRuntime->ImageHead, Link);
    }
    //
    // Copy all the registered Event to new gRuntime protocol
    //
    for (Link = gRuntimeTemplate.EventHead.ForwardLink; Link != &gRuntimeTemplate.EventHead; Link = TempLinkNode.ForwardLink) {
      CopyMem (&TempLinkNode, Link, sizeof(LIST_ENTRY));
      InsertTailList (&gRuntime->EventHead, Link);
    }

    //
    // Clean up gRuntimeTemplate
    //
    gRuntimeTemplate.ImageHead.ForwardLink = &gRuntimeTemplate.ImageHead;
    gRuntimeTemplate.ImageHead.BackLink    = &gRuntimeTemplate.ImageHead;
    gRuntimeTemplate.EventHead.ForwardLink = &gRuntimeTemplate.EventHead;
    gRuntimeTemplate.EventHead.BackLink    = &gRuntimeTemplate.EventHead;
  }

  //
  // It's over kill to do them all every time, but it saves a lot of code.
  //
  CalculateEfiHdrCrc (&gDxeCoreRT->Hdr);
  CalculateEfiHdrCrc (&gBS->Hdr);
  CalculateEfiHdrCrc (&gDxeCoreST->Hdr);
  CalculateEfiHdrCrc (&gDxeCoreDS->Hdr);
}

/**
  Creates an event for each entry in a table that is fired everytime a Protocol
  of a specific type is installed.

  @param Entry  Pointer to EFI_CORE_PROTOCOL_NOTIFY_ENTRY.

**/
VOID
CoreNotifyOnProtocolEntryTable (
  EFI_CORE_PROTOCOL_NOTIFY_ENTRY  *Entry
  )
{
  EFI_STATUS  Status;

  for (; Entry->ProtocolGuid != NULL; Entry++) {
    //
    // Create the event
    //
    Status = CoreCreateEvent (
              EVT_NOTIFY_SIGNAL,
              TPL_CALLBACK,
              GenericProtocolNotify,
              Entry,
              &Entry->Event
              );
    ASSERT_EFI_ERROR(Status);

    //
    // Register for protocol notifactions on this event
    //
    Status = CoreRegisterProtocolNotify (
              Entry->ProtocolGuid,
              Entry->Event,
              &Entry->Registration
              );
    ASSERT_EFI_ERROR(Status);
  }
}

/**
  Creates an events for the Architectural Protocols and the optional protocols
  that are fired everytime a Protocol of a specific type is installed.

**/
VOID
CoreNotifyOnProtocolInstallation (
  VOID
  )
{
  CoreNotifyOnProtocolEntryTable (mArchProtocols);
  CoreNotifyOnProtocolEntryTable (mOptionalProtocols);
}


/**
  Displays Architectural protocols that were not loaded and are required for DXE
  core to function.  Only used in Debug Builds.

**/
VOID
CoreDisplayMissingArchProtocols (
  VOID
  )
{
  EFI_CORE_PROTOCOL_NOTIFY_ENTRY       *Entry;
  CONST GUID_TO_STRING_PROTOCOL_ENTRY  *MissingEntry;

  for (Entry = mArchProtocols; Entry->ProtocolGuid != NULL; Entry++) {
    if (!Entry->Present) {
      for (MissingEntry = mMissingProtocols; MissingEntry->ProtocolGuid != NULL; MissingEntry++) {
        if (CompareGuid (Entry->ProtocolGuid, MissingEntry->ProtocolGuid)) {
          DEBUG ((DEBUG_ERROR, "\n%a Arch Protocol not present!!\n", MissingEntry->GuidString));
          break;
        }
      }
    }
  }
}
