]> git.proxmox.com Git - mirror_edk2.git/commitdiff
ArmPkg: implement EFI_MP_SERVICES_PROTOCOL based on PSCI calls
authorRebecca Cran <quic_rcran@quicinc.com>
Tue, 17 Jan 2023 04:57:31 +0000 (21:57 -0700)
committermergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Fri, 27 Jan 2023 14:18:56 +0000 (14:18 +0000)
Add support for EFI_MP_SERVICES_PROTOCOL during the DXE phase under
AArch64.

PSCI_CPU_ON is called to power on the core, the supplied procedure is
executed and PSCI_CPU_OFF is called to power off the core.

Fixes contributed by Ard Biesheuvel.

Signed-off-by: Rebecca Cran <rebecca@quicinc.com>
Reviewed-by: Ard Biesheuvel <ardb@kernel.org>
Tested-by: Kun Qin <kun.qin@microsoft.com>
ArmPkg/ArmPkg.dsc
ArmPkg/Drivers/ArmPsciMpServicesDxe/ArmPsciMpServicesDxe.c [new file with mode: 0644]
ArmPkg/Drivers/ArmPsciMpServicesDxe/ArmPsciMpServicesDxe.inf [new file with mode: 0644]
ArmPkg/Drivers/ArmPsciMpServicesDxe/MpFuncs.S [new file with mode: 0644]
ArmPkg/Drivers/ArmPsciMpServicesDxe/MpServicesInternal.h [new file with mode: 0644]

index ac24ebce4892cd1a899ec92eec68c6d122b55277..1e873b90c56d3c384aedac89cd806a85a0671df1 100644 (file)
   ArmPkg/Universal/Smbios/OemMiscLibNull/OemMiscLibNull.inf\r
 \r
 [Components.AARCH64]\r
+  ArmPkg/Drivers/ArmPsciMpServicesDxe/ArmPsciMpServicesDxe.inf\r
   ArmPkg/Drivers/MmCommunicationDxe/MmCommunication.inf\r
   ArmPkg/Library/ArmMmuLib/ArmMmuPeiLib.inf\r
 \r
diff --git a/ArmPkg/Drivers/ArmPsciMpServicesDxe/ArmPsciMpServicesDxe.c b/ArmPkg/Drivers/ArmPsciMpServicesDxe/ArmPsciMpServicesDxe.c
new file mode 100644 (file)
index 0000000..f822a98
--- /dev/null
@@ -0,0 +1,1859 @@
+/** @file\r
+  Construct MP Services Protocol.\r
+\r
+  The MP Services Protocol provides a generalized way of performing following tasks:\r
+    - Retrieving information of multi-processor environment and MP-related status of\r
+      specific processors.\r
+    - Dispatching user-provided function to APs.\r
+    - Maintain MP-related processor status.\r
+\r
+  The MP Services Protocol must be produced on any system with more than one logical\r
+  processor.\r
+\r
+  The Protocol is available only during boot time.\r
+\r
+  MP Services Protocol is hardware-independent. Most of the logic of this protocol\r
+  is architecturally neutral. It abstracts the multi-processor environment and\r
+  status of processors, and provides interfaces to retrieve information, maintain,\r
+  and dispatch.\r
+\r
+  MP Services Protocol may be consumed by ACPI module. The ACPI module may use this\r
+  protocol to retrieve data that are needed for an MP platform and report them to OS.\r
+  MP Services Protocol may also be used to program and configure processors, such\r
+  as MTRR synchronization for memory space attributes setting in DXE Services.\r
+  MP Services Protocol may be used by non-CPU DXE drivers to speed up platform boot\r
+  by taking advantage of the processing capabilities of the APs, for example, using\r
+  APs to help test system memory in parallel with other device initialization.\r
+  Diagnostics applications may also use this protocol for multi-processor.\r
+\r
+  Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved.<BR>\r
+  SPDX-License-Identifier: BSD-2-Clause-Patent\r
+\r
+**/\r
+\r
+#include <PiDxe.h>\r
+\r
+#include <Library/ArmLib.h>\r
+#include <Library/ArmMmuLib.h>\r
+#include <Library/ArmPlatformLib.h>\r
+#include <Library/ArmSmcLib.h>\r
+#include <Library/BaseMemoryLib.h>\r
+#include <Library/CacheMaintenanceLib.h>\r
+#include <Library/CpuExceptionHandlerLib.h>\r
+#include <Library/DebugLib.h>\r
+#include <Library/HobLib.h>\r
+#include <Library/MemoryAllocationLib.h>\r
+#include <Library/UefiBootServicesTableLib.h>\r
+#include <Library/UefiLib.h>\r
+#include <IndustryStandard/ArmStdSmc.h>\r
+#include <Ppi/ArmMpCoreInfo.h>\r
+#include <Protocol/LoadedImage.h>\r
+\r
+#include "MpServicesInternal.h"\r
+\r
+#define POLL_INTERVAL_US  50000\r
+\r
+STATIC CPU_MP_DATA  mCpuMpData;\r
+STATIC BOOLEAN      mNonBlockingModeAllowed;\r
+UINT64              *gApStacksBase;\r
+UINT64              *gProcessorIDs;\r
+CONST UINT64        gApStackSize = AP_STACK_SIZE;\r
+VOID                *gTtbr0;\r
+UINTN               gTcr;\r
+UINTN               gMair;\r
+\r
+STATIC\r
+BOOLEAN\r
+IsCurrentProcessorBSP (\r
+  VOID\r
+  );\r
+\r
+/** Turns on the specified core using PSCI and executes the user-supplied\r
+    function that's been configured via a previous call to SetApProcedure.\r
+\r
+    @param ProcessorIndex The index of the core to turn on.\r
+\r
+    @retval EFI_SUCCESS      Success.\r
+    @retval EFI_DEVICE_ERROR The processor could not be turned on.\r
+\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+DispatchCpu (\r
+  IN UINTN  ProcessorIndex\r
+  )\r
+{\r
+  ARM_SMC_ARGS  Args;\r
+  EFI_STATUS    Status;\r
+\r
+  Status = EFI_SUCCESS;\r
+\r
+  mCpuMpData.CpuData[ProcessorIndex].State = CpuStateBusy;\r
+\r
+  /* Turn the AP on */\r
+  if (sizeof (Args.Arg0) == sizeof (UINT32)) {\r
+    Args.Arg0 = ARM_SMC_ID_PSCI_CPU_ON_AARCH32;\r
+  } else {\r
+    Args.Arg0 = ARM_SMC_ID_PSCI_CPU_ON_AARCH64;\r
+  }\r
+\r
+  Args.Arg1 = gProcessorIDs[ProcessorIndex];\r
+  Args.Arg2 = (UINTN)ApEntryPoint;\r
+\r
+  ArmCallSmc (&Args);\r
+\r
+  if (Args.Arg0 != ARM_SMC_PSCI_RET_SUCCESS) {\r
+    DEBUG ((DEBUG_ERROR, "PSCI_CPU_ON call failed: %d\n", Args.Arg0));\r
+    Status = EFI_DEVICE_ERROR;\r
+  }\r
+\r
+  return Status;\r
+}\r
+\r
+/** Returns whether the specified processor is the BSP.\r
+\r
+  @param[in] ProcessorIndex The index the processor to check.\r
+\r
+  @return TRUE if the processor is the BSP, FALSE otherwise.\r
+**/\r
+STATIC\r
+BOOLEAN\r
+IsProcessorBSP (\r
+  UINTN  ProcessorIndex\r
+  )\r
+{\r
+  EFI_PROCESSOR_INFORMATION  *CpuInfo;\r
+\r
+  CpuInfo = &mCpuMpData.CpuData[ProcessorIndex].Info;\r
+\r
+  return (CpuInfo->StatusFlag & PROCESSOR_AS_BSP_BIT) != 0;\r
+}\r
+\r
+/** Get the Application Processors state.\r
+\r
+  @param[in]  CpuData    The pointer to CPU_AP_DATA of specified AP.\r
+\r
+  @return The AP status.\r
+**/\r
+CPU_STATE\r
+GetApState (\r
+  IN  CPU_AP_DATA  *CpuData\r
+  )\r
+{\r
+  return CpuData->State;\r
+}\r
+\r
+/** Configures the processor context with the user-supplied procedure and\r
+    argument.\r
+\r
+   @param CpuData           The processor context.\r
+   @param Procedure         The user-supplied procedure.\r
+   @param ProcedureArgument The user-supplied procedure argument.\r
+\r
+**/\r
+STATIC\r
+VOID\r
+SetApProcedure (\r
+  IN   CPU_AP_DATA       *CpuData,\r
+  IN   EFI_AP_PROCEDURE  Procedure,\r
+  IN   VOID              *ProcedureArgument\r
+  )\r
+{\r
+  ASSERT (CpuData != NULL);\r
+  ASSERT (Procedure != NULL);\r
+\r
+  CpuData->Parameter = ProcedureArgument;\r
+  CpuData->Procedure = Procedure;\r
+}\r
+\r
+/** Returns the index of the next processor that is blocked.\r
+\r
+   @param[out] NextNumber The index of the next blocked processor.\r
+\r
+   @retval EFI_SUCCESS   Successfully found the next blocked processor.\r
+   @retval EFI_NOT_FOUND There are no blocked processors.\r
+\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+GetNextBlockedNumber (\r
+  OUT UINTN  *NextNumber\r
+  )\r
+{\r
+  UINTN        Index;\r
+  CPU_STATE    State;\r
+  CPU_AP_DATA  *CpuData;\r
+\r
+  for (Index = 0; Index < mCpuMpData.NumberOfProcessors; Index++) {\r
+    CpuData = &mCpuMpData.CpuData[Index];\r
+    if (IsProcessorBSP (Index)) {\r
+      // Skip BSP\r
+      continue;\r
+    }\r
+\r
+    State = CpuData->State;\r
+\r
+    if (State == CpuStateBlocked) {\r
+      *NextNumber = Index;\r
+      return EFI_SUCCESS;\r
+    }\r
+  }\r
+\r
+  return EFI_NOT_FOUND;\r
+}\r
+\r
+/** Stalls the BSP for the minimum of POLL_INTERVAL_US and Timeout.\r
+\r
+   @param[in]  Timeout    The time limit in microseconds remaining for\r
+                          APs to return from Procedure.\r
+\r
+   @retval     StallTime  Time of execution stall.\r
+**/\r
+STATIC\r
+UINTN\r
+CalculateAndStallInterval (\r
+  IN UINTN  Timeout\r
+  )\r
+{\r
+  UINTN  StallTime;\r
+\r
+  if ((Timeout < POLL_INTERVAL_US) && (Timeout != 0)) {\r
+    StallTime = Timeout;\r
+  } else {\r
+    StallTime = POLL_INTERVAL_US;\r
+  }\r
+\r
+  gBS->Stall (StallTime);\r
+\r
+  return StallTime;\r
+}\r
+\r
+/**\r
+  This service retrieves the number of logical processor in the platform\r
+  and the number of those logical processors that are enabled on this boot.\r
+  This service may only be called from the BSP.\r
+\r
+  This function is used to retrieve the following information:\r
+    - The number of logical processors that are present in the system.\r
+    - The number of enabled logical processors in the system at the instant\r
+      this call is made.\r
+\r
+  Because MP Service Protocol provides services to enable and disable processors\r
+  dynamically, the number of enabled logical processors may vary during the\r
+  course of a boot session.\r
+\r
+  If this service is called from an AP, then EFI_DEVICE_ERROR is returned.\r
+  If NumberOfProcessors or NumberOfEnabledProcessors is NULL, then\r
+  EFI_INVALID_PARAMETER is returned. Otherwise, the total number of processors\r
+  is returned in NumberOfProcessors, the number of currently enabled processor\r
+  is returned in NumberOfEnabledProcessors, and EFI_SUCCESS is returned.\r
+\r
+  @param[in]  This                        A pointer to the\r
+                                          EFI_MP_SERVICES_PROTOCOL instance.\r
+  @param[out] NumberOfProcessors          Pointer to the total number of logical\r
+                                          processors in the system, including\r
+                                          the BSP and disabled APs.\r
+  @param[out] NumberOfEnabledProcessors   Pointer to the number of enabled\r
+                                          logical processors that exist in the\r
+                                          system, including the BSP.\r
+\r
+  @retval EFI_SUCCESS             The number of logical processors and enabled\r
+                                  logical processors was retrieved.\r
+  @retval EFI_DEVICE_ERROR        The calling processor is an AP.\r
+  @retval EFI_INVALID_PARAMETER   NumberOfProcessors is NULL.\r
+  @retval EFI_INVALID_PARAMETER   NumberOfEnabledProcessors is NULL.\r
+\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+GetNumberOfProcessors (\r
+  IN  EFI_MP_SERVICES_PROTOCOL  *This,\r
+  OUT UINTN                     *NumberOfProcessors,\r
+  OUT UINTN                     *NumberOfEnabledProcessors\r
+  )\r
+{\r
+  if ((NumberOfProcessors == NULL) || (NumberOfEnabledProcessors == NULL)) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  if (!IsCurrentProcessorBSP ()) {\r
+    return EFI_DEVICE_ERROR;\r
+  }\r
+\r
+  *NumberOfProcessors        = mCpuMpData.NumberOfProcessors;\r
+  *NumberOfEnabledProcessors = mCpuMpData.NumberOfEnabledProcessors;\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+  Gets detailed MP-related information on the requested processor at the\r
+  instant this call is made. This service may only be called from the BSP.\r
+\r
+  This service retrieves detailed MP-related information about any processor\r
+  on the platform. Note the following:\r
+    - The processor information may change during the course of a boot session.\r
+    - The information presented here is entirely MP related.\r
+\r
+  Information regarding the number of caches and their sizes, frequency of\r
+  operation, slot numbers is all considered platform-related information and is\r
+  not provided by this service.\r
+\r
+  @param[in]  This                  A pointer to the EFI_MP_SERVICES_PROTOCOL\r
+                                    instance.\r
+  @param[in]  ProcessorIndex        The index of the processor.\r
+  @param[out] ProcessorInfoBuffer   A pointer to the buffer where information\r
+                                    for the requested processor is deposited.\r
+\r
+  @retval EFI_SUCCESS             Processor information was returned.\r
+  @retval EFI_DEVICE_ERROR        The calling processor is an AP.\r
+  @retval EFI_INVALID_PARAMETER   ProcessorInfoBuffer is NULL.\r
+  @retval EFI_NOT_FOUND           The processor with the handle specified by\r
+                                  ProcessorNumber does not exist in the platform.\r
+\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+GetProcessorInfo (\r
+  IN  EFI_MP_SERVICES_PROTOCOL   *This,\r
+  IN  UINTN                      ProcessorIndex,\r
+  OUT EFI_PROCESSOR_INFORMATION  *ProcessorInfoBuffer\r
+  )\r
+{\r
+  if (ProcessorInfoBuffer == NULL) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  if (!IsCurrentProcessorBSP ()) {\r
+    return EFI_DEVICE_ERROR;\r
+  }\r
+\r
+  ProcessorIndex &= ~CPU_V2_EXTENDED_TOPOLOGY;\r
+\r
+  if (ProcessorIndex >= mCpuMpData.NumberOfProcessors) {\r
+    return EFI_NOT_FOUND;\r
+  }\r
+\r
+  CopyMem (\r
+    ProcessorInfoBuffer,\r
+    &mCpuMpData.CpuData[ProcessorIndex],\r
+    sizeof (EFI_PROCESSOR_INFORMATION)\r
+    );\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+  This service executes a caller provided function on all enabled APs. APs can\r
+  run either simultaneously or one at a time in sequence. This service supports\r
+  both blocking and non-blocking requests. The non-blocking requests use EFI\r
+  events so the BSP can detect when the APs have finished. This service may only\r
+  be called from the BSP.\r
+\r
+  This function is used to dispatch all the enabled APs to the function\r
+  specified by Procedure.  If any enabled AP is busy, then EFI_NOT_READY is\r
+  returned immediately and Procedure is not started on any AP.\r
+\r
+  If SingleThread is TRUE, all the enabled APs execute the function specified by\r
+  Procedure one by one, in ascending order of processor handle number.\r
+  Otherwise, all the enabled APs execute the function specified by Procedure\r
+  simultaneously.\r
+\r
+  If WaitEvent is NULL, execution is in blocking mode. The BSP waits until all\r
+  APs finish or TimeoutInMicroseconds expires. Otherwise, execution is in\r
+  non-blocking mode, and the BSP returns from this service without waiting for\r
+  APs. If a non-blocking mode is requested after the UEFI Event\r
+  EFI_EVENT_GROUP_READY_TO_BOOT is signaled, then EFI_UNSUPPORTED must be\r
+  returned.\r
+\r
+  If the timeout specified by TimeoutInMicroseconds expires before all APs\r
+  return from Procedure, then Procedure on the failed APs is terminated.\r
+  All enabled APs are always available for further calls to\r
+  EFI_MP_SERVICES_PROTOCOL.StartupAllAPs() and\r
+  EFI_MP_SERVICES_PROTOCOL.StartupThisAP(). If FailedCpuList is not NULL, its\r
+  content points to the list of processor handle numbers in which Procedure was\r
+  terminated.\r
+\r
+  Note: It is the responsibility of the consumer of the\r
+  EFI_MP_SERVICES_PROTOCOL.StartupAllAPs() to make sure that the nature of the\r
+  code that is executed on the BSP and the dispatched APs is well controlled.\r
+  The MP Services Protocol does not guarantee that the Procedure function is\r
+  MP-safe. Hence, the tasks that can be run in parallel are limited to certain\r
+  independent tasks and well-controlled exclusive code. EFI services and\r
+  protocols may not be called by APs unless otherwise specified.\r
+\r
+  In blocking execution mode, BSP waits until all APs finish or\r
+  TimeoutInMicroseconds expires.\r
+\r
+  In non-blocking execution mode, BSP is freed to return to the caller and then\r
+  proceed to the next task without having to wait for APs. The following\r
+  sequence needs to occur in a non-blocking execution mode:\r
+\r
+    -# The caller that intends to use this MP Services Protocol in non-blocking\r
+       mode creates WaitEvent by calling the EFI CreateEvent() service.  The\r
+       caller invokes EFI_MP_SERVICES_PROTOCOL.StartupAllAPs(). If the parameter\r
+       WaitEvent is not NULL, then StartupAllAPs() executes in non-blocking\r
+       mode. It requests the function specified by Procedure to be started on\r
+       all the enabled APs, and releases the BSP to continue with other tasks.\r
+    -# The caller can use the CheckEvent() and WaitForEvent() services to check\r
+       the state of the WaitEvent created in step 1.\r
+    -# When the APs complete their task or TimeoutInMicroSecondss expires, the\r
+       MP Service signals WaitEvent by calling the EFI SignalEvent() function.\r
+       If FailedCpuList is not NULL, its content is available when WaitEvent is\r
+       signaled. If all APs returned from Procedure prior to the timeout, then\r
+       FailedCpuList is set to NULL. If not all APs return from Procedure before\r
+       the timeout, then FailedCpuList is filled in with the list of the failed\r
+       APs. The buffer is allocated by MP Service Protocol using AllocatePool().\r
+       It is the caller's responsibility to free the buffer with FreePool()\r
+       service.\r
+    -# This invocation of SignalEvent() function informs the caller that invoked\r
+       EFI_MP_SERVICES_PROTOCOL.StartupAllAPs() that either all the APs\r
+       completed the specified task or a timeout occurred. The contents of\r
+       FailedCpuList can be examined to determine which APs did not complete the\r
+       specified task prior to the timeout.\r
+\r
+  @param[in]  This                    A pointer to the EFI_MP_SERVICES_PROTOCOL\r
+                                      instance.\r
+  @param[in]  Procedure               A pointer to the function to be run on\r
+                                      enabled APs of the system. See type\r
+                                      EFI_AP_PROCEDURE.\r
+  @param[in]  SingleThread            If TRUE, then all the enabled APs execute\r
+                                      the function specified by Procedure one by\r
+                                      one, in ascending order of processor\r
+                                      handle number.  If FALSE, then all the\r
+                                      enabled APs execute the function specified\r
+                                      by Procedure simultaneously.\r
+  @param[in]  WaitEvent               The event created by the caller with\r
+                                      CreateEvent() service.  If it is NULL,\r
+                                      then execute in blocking mode. BSP waits\r
+                                      until all APs finish or\r
+                                      TimeoutInMicroseconds expires.  If it's\r
+                                      not NULL, then execute in non-blocking\r
+                                      mode. BSP requests the function specified\r
+                                      by Procedure to be started on all the\r
+                                      enabled APs, and go on executing\r
+                                      immediately. If all return from Procedure,\r
+                                      or TimeoutInMicroseconds expires, this\r
+                                      event is signaled. The BSP can use the\r
+                                      CheckEvent() or WaitForEvent()\r
+                                      services to check the state of event. Type\r
+                                      EFI_EVENT is defined in CreateEvent() in\r
+                                      the Unified Extensible Firmware Interface\r
+                                      Specification.\r
+  @param[in]  TimeoutInMicroseconds   Indicates the time limit in microseconds\r
+                                      for APs to return from Procedure, either\r
+                                      for blocking or non-blocking mode. Zero\r
+                                      means infinity.  If the timeout expires\r
+                                      before all APs return from Procedure, then\r
+                                      Procedure on the failed APs is terminated.\r
+                                      All enabled APs are available for next\r
+                                      function assigned by\r
+                                      EFI_MP_SERVICES_PROTOCOL.StartupAllAPs()\r
+                                      or EFI_MP_SERVICES_PROTOCOL.StartupThisAP().\r
+                                      If the timeout expires in blocking mode,\r
+                                      BSP returns EFI_TIMEOUT.  If the timeout\r
+                                      expires in non-blocking mode, WaitEvent\r
+                                      is signaled with SignalEvent().\r
+  @param[in]  ProcedureArgument       The parameter passed into Procedure for\r
+                                      all APs.\r
+  @param[out] FailedCpuList           If NULL, this parameter is ignored.\r
+                                      Otherwise, if all APs finish successfully,\r
+                                      then its content is set to NULL. If not\r
+                                      all APs finish before timeout expires,\r
+                                      then its content is set to address of the\r
+                                      buffer holding handle numbers of the\r
+                                      failed APs.\r
+                                      The buffer is allocated by MP Service\r
+                                      Protocol, and it's the caller's\r
+                                      responsibility to free the buffer with\r
+                                      FreePool() service.\r
+                                      In blocking mode, it is ready for\r
+                                      consumption when the call returns. In\r
+                                      non-blocking mode, it is ready when\r
+                                      WaitEvent is signaled. The list of failed\r
+                                      CPU is terminated by  END_OF_CPU_LIST.\r
+\r
+  @retval EFI_SUCCESS             In blocking mode, all APs have finished before\r
+                                  the timeout expired.\r
+  @retval EFI_SUCCESS             In non-blocking mode, function has been\r
+                                  dispatched to all enabled APs.\r
+  @retval EFI_UNSUPPORTED         A non-blocking mode request was made after the\r
+                                  UEFI event EFI_EVENT_GROUP_READY_TO_BOOT was\r
+                                  signaled.\r
+  @retval EFI_DEVICE_ERROR        Caller processor is AP.\r
+  @retval EFI_NOT_STARTED         No enabled APs exist in the system.\r
+  @retval EFI_NOT_READY           Any enabled APs are busy.\r
+  @retval EFI_TIMEOUT             In blocking mode, the timeout expired before\r
+                                  all enabled APs have finished.\r
+  @retval EFI_INVALID_PARAMETER   Procedure is NULL.\r
+\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+StartupAllAPs (\r
+  IN  EFI_MP_SERVICES_PROTOCOL  *This,\r
+  IN  EFI_AP_PROCEDURE          Procedure,\r
+  IN  BOOLEAN                   SingleThread,\r
+  IN  EFI_EVENT                 WaitEvent               OPTIONAL,\r
+  IN  UINTN                     TimeoutInMicroseconds,\r
+  IN  VOID                      *ProcedureArgument      OPTIONAL,\r
+  OUT UINTN                     **FailedCpuList         OPTIONAL\r
+  )\r
+{\r
+  EFI_STATUS  Status;\r
+\r
+  if (!IsCurrentProcessorBSP ()) {\r
+    return EFI_DEVICE_ERROR;\r
+  }\r
+\r
+  if ((mCpuMpData.NumberOfProcessors == 1) || (mCpuMpData.NumberOfEnabledProcessors == 1)) {\r
+    return EFI_NOT_STARTED;\r
+  }\r
+\r
+  if (Procedure == NULL) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  if ((WaitEvent != NULL) && !mNonBlockingModeAllowed) {\r
+    return EFI_UNSUPPORTED;\r
+  }\r
+\r
+  if (FailedCpuList != NULL) {\r
+    mCpuMpData.FailedList = AllocateZeroPool (\r
+                              (mCpuMpData.NumberOfProcessors + 1) *\r
+                              sizeof (UINTN)\r
+                              );\r
+    if (mCpuMpData.FailedList == NULL) {\r
+      return EFI_OUT_OF_RESOURCES;\r
+    }\r
+\r
+    SetMemN (\r
+      mCpuMpData.FailedList,\r
+      (mCpuMpData.NumberOfProcessors + 1) *\r
+      sizeof (UINTN),\r
+      END_OF_CPU_LIST\r
+      );\r
+    mCpuMpData.FailedListIndex = 0;\r
+    *FailedCpuList             = mCpuMpData.FailedList;\r
+  }\r
+\r
+  StartupAllAPsPrepareState (SingleThread);\r
+\r
+  // If any enabled APs are busy (ignoring the BSP), return EFI_NOT_READY\r
+  if (mCpuMpData.StartCount != (mCpuMpData.NumberOfEnabledProcessors - 1)) {\r
+    return EFI_NOT_READY;\r
+  }\r
+\r
+  if (WaitEvent != NULL) {\r
+    Status = StartupAllAPsWithWaitEvent (\r
+               Procedure,\r
+               ProcedureArgument,\r
+               WaitEvent,\r
+               TimeoutInMicroseconds,\r
+               SingleThread,\r
+               FailedCpuList\r
+               );\r
+\r
+    if (EFI_ERROR (Status) && (FailedCpuList != NULL)) {\r
+      if (mCpuMpData.FailedListIndex == 0) {\r
+        FreePool (*FailedCpuList);\r
+        *FailedCpuList = NULL;\r
+      }\r
+    }\r
+  } else {\r
+    Status = StartupAllAPsNoWaitEvent (\r
+               Procedure,\r
+               ProcedureArgument,\r
+               TimeoutInMicroseconds,\r
+               SingleThread,\r
+               FailedCpuList\r
+               );\r
+\r
+    if (FailedCpuList != NULL) {\r
+      if (mCpuMpData.FailedListIndex == 0) {\r
+        FreePool (*FailedCpuList);\r
+        *FailedCpuList = NULL;\r
+      }\r
+    }\r
+  }\r
+\r
+  return Status;\r
+}\r
+\r
+/**\r
+  This service lets the caller get one enabled AP to execute a caller-provided\r
+  function. The caller can request the BSP to either wait for the completion\r
+  of the AP or just proceed with the next task by using the EFI event mechanism.\r
+  See EFI_MP_SERVICES_PROTOCOL.StartupAllAPs() for more details on non-blocking\r
+  execution support.  This service may only be called from the BSP.\r
+\r
+  This function is used to dispatch one enabled AP to the function specified by\r
+  Procedure passing in the argument specified by ProcedureArgument.  If WaitEvent\r
+  is NULL, execution is in blocking mode. The BSP waits until the AP finishes or\r
+  TimeoutInMicroSecondss expires. Otherwise, execution is in non-blocking mode.\r
+  BSP proceeds to the next task without waiting for the AP. If a non-blocking mode\r
+  is requested after the UEFI Event EFI_EVENT_GROUP_READY_TO_BOOT is signaled,\r
+  then EFI_UNSUPPORTED must be returned.\r
+\r
+  If the timeout specified by TimeoutInMicroseconds expires before the AP returns\r
+  from Procedure, then execution of Procedure by the AP is terminated. The AP is\r
+  available for subsequent calls to EFI_MP_SERVICES_PROTOCOL.StartupAllAPs() and\r
+  EFI_MP_SERVICES_PROTOCOL.StartupThisAP().\r
+\r
+  @param[in]  This                    A pointer to the EFI_MP_SERVICES_PROTOCOL\r
+                                      instance.\r
+  @param[in]  Procedure               A pointer to the function to be run on\r
+                                      enabled APs of the system. See type\r
+                                      EFI_AP_PROCEDURE.\r
+  @param[in]  ProcessorNumber         The handle number of the AP. The range is\r
+                                      from 0 to the total number of logical\r
+                                      processors minus 1. The total number of\r
+                                      logical processors can be retrieved by\r
+                                      EFI_MP_SERVICES_PROTOCOL.GetNumberOfProcessors().\r
+  @param[in]  WaitEvent               The event created by the caller with CreateEvent()\r
+                                      service.  If it is NULL, then execute in\r
+                                      blocking mode. BSP waits until all APs finish\r
+                                      or TimeoutInMicroseconds expires.  If it's\r
+                                      not NULL, then execute in non-blocking mode.\r
+                                      BSP requests the function specified by\r
+                                      Procedure to be started on all the enabled\r
+                                      APs, and go on executing immediately. If\r
+                                      all return from Procedure or TimeoutInMicroseconds\r
+                                      expires, this event is signaled. The BSP\r
+                                      can use the CheckEvent() or WaitForEvent()\r
+                                      services to check the state of event.  Type\r
+                                      EFI_EVENT is defined in CreateEvent() in\r
+                                      the Unified Extensible Firmware Interface\r
+                                      Specification.\r
+  @param[in]  TimeoutInMicroseconds   Indicates the time limit in microseconds for\r
+                                      APs to return from Procedure, either for\r
+                                      blocking or non-blocking mode. Zero means\r
+                                      infinity.  If the timeout expires before\r
+                                      all APs return from Procedure, then Procedure\r
+                                      on the failed APs is terminated. All enabled\r
+                                      APs are available for next function assigned\r
+                                      by EFI_MP_SERVICES_PROTOCOL.StartupAllAPs()\r
+                                      or EFI_MP_SERVICES_PROTOCOL.StartupThisAP().\r
+                                      If the timeout expires in blocking mode,\r
+                                      BSP returns EFI_TIMEOUT.  If the timeout\r
+                                      expires in non-blocking mode, WaitEvent\r
+                                      is signaled with SignalEvent().\r
+  @param[in]  ProcedureArgument       The parameter passed into Procedure for\r
+                                      all APs.\r
+  @param[out] Finished                If NULL, this parameter is ignored.  In\r
+                                      blocking mode, this parameter is ignored.\r
+                                      In non-blocking mode, if AP returns from\r
+                                      Procedure before the timeout expires, its\r
+                                      content is set to TRUE. Otherwise, the\r
+                                      value is set to FALSE. The caller can\r
+                                      determine if the AP returned from Procedure\r
+                                      by evaluating this value.\r
+\r
+  @retval EFI_SUCCESS             In blocking mode, specified AP finished before\r
+                                  the timeout expires.\r
+  @retval EFI_SUCCESS             In non-blocking mode, the function has been\r
+                                  dispatched to specified AP.\r
+  @retval EFI_UNSUPPORTED         A non-blocking mode request was made after the\r
+                                  UEFI event EFI_EVENT_GROUP_READY_TO_BOOT was\r
+                                  signaled.\r
+  @retval EFI_DEVICE_ERROR        The calling processor is an AP.\r
+  @retval EFI_TIMEOUT             In blocking mode, the timeout expired before\r
+                                  the specified AP has finished.\r
+  @retval EFI_NOT_READY           The specified AP is busy.\r
+  @retval EFI_NOT_FOUND           The processor with the handle specified by\r
+                                  ProcessorNumber does not exist.\r
+  @retval EFI_INVALID_PARAMETER   ProcessorNumber specifies the BSP or disabled AP.\r
+  @retval EFI_INVALID_PARAMETER   Procedure is NULL.\r
+\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+StartupThisAP (\r
+  IN  EFI_MP_SERVICES_PROTOCOL  *This,\r
+  IN  EFI_AP_PROCEDURE          Procedure,\r
+  IN  UINTN                     ProcessorNumber,\r
+  IN  EFI_EVENT                 WaitEvent               OPTIONAL,\r
+  IN  UINTN                     TimeoutInMicroseconds,\r
+  IN  VOID                      *ProcedureArgument      OPTIONAL,\r
+  OUT BOOLEAN                   *Finished               OPTIONAL\r
+  )\r
+{\r
+  EFI_STATUS   Status;\r
+  UINTN        Timeout;\r
+  CPU_AP_DATA  *CpuData;\r
+\r
+  if (!IsCurrentProcessorBSP ()) {\r
+    return EFI_DEVICE_ERROR;\r
+  }\r
+\r
+  if (Procedure == NULL) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  if (ProcessorNumber >= mCpuMpData.NumberOfProcessors) {\r
+    return EFI_NOT_FOUND;\r
+  }\r
+\r
+  CpuData = &mCpuMpData.CpuData[ProcessorNumber];\r
+\r
+  if (IsProcessorBSP (ProcessorNumber)) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  if (!IsProcessorEnabled (ProcessorNumber)) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  if ((GetApState (CpuData) != CpuStateIdle) &&\r
+      (GetApState (CpuData) != CpuStateFinished))\r
+  {\r
+    return EFI_NOT_READY;\r
+  }\r
+\r
+  if ((WaitEvent != NULL) && !mNonBlockingModeAllowed) {\r
+    return EFI_UNSUPPORTED;\r
+  }\r
+\r
+  Timeout = TimeoutInMicroseconds;\r
+\r
+  CpuData->Timeout       = TimeoutInMicroseconds;\r
+  CpuData->TimeTaken     = 0;\r
+  CpuData->TimeoutActive = (BOOLEAN)(TimeoutInMicroseconds != 0);\r
+\r
+  SetApProcedure (\r
+    CpuData,\r
+    Procedure,\r
+    ProcedureArgument\r
+    );\r
+\r
+  Status = DispatchCpu (ProcessorNumber);\r
+  if (EFI_ERROR (Status)) {\r
+    CpuData->State = CpuStateIdle;\r
+    return EFI_NOT_READY;\r
+  }\r
+\r
+  if (WaitEvent != NULL) {\r
+    // Non Blocking\r
+    if (Finished != NULL) {\r
+      CpuData->SingleApFinished = Finished;\r
+      *Finished                 = FALSE;\r
+    }\r
+\r
+    CpuData->WaitEvent = WaitEvent;\r
+    Status             = gBS->SetTimer (\r
+                                CpuData->CheckThisAPEvent,\r
+                                TimerPeriodic,\r
+                                POLL_INTERVAL_US\r
+                                );\r
+\r
+    return EFI_SUCCESS;\r
+  }\r
+\r
+  // Blocking\r
+  while (TRUE) {\r
+    if (GetApState (CpuData) == CpuStateFinished) {\r
+      CpuData->State = CpuStateIdle;\r
+      break;\r
+    }\r
+\r
+    if ((TimeoutInMicroseconds != 0) && (Timeout == 0)) {\r
+      return EFI_TIMEOUT;\r
+    }\r
+\r
+    Timeout -= CalculateAndStallInterval (Timeout);\r
+  }\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+  This service switches the requested AP to be the BSP from that point onward.\r
+  This service changes the BSP for all purposes.   This call can only be\r
+  performed by the current BSP.\r
+\r
+  This service switches the requested AP to be the BSP from that point onward.\r
+  This service changes the BSP for all purposes. The new BSP can take over the\r
+  execution of the old BSP and continue seamlessly from where the old one left\r
+  off. This service may not be supported after the UEFI Event EFI_EVENT_GROUP_READY_TO_BOOT\r
+  is signaled.\r
+\r
+  If the BSP cannot be switched prior to the return from this service, then\r
+  EFI_UNSUPPORTED must be returned.\r
+\r
+  @param[in] This              A pointer to the EFI_MP_SERVICES_PROTOCOL instance.\r
+  @param[in] ProcessorNumber   The handle number of AP that is to become the new\r
+                               BSP. The range is from 0 to the total number of\r
+                               logical processors minus 1. The total number of\r
+                               logical processors can be retrieved by\r
+                               EFI_MP_SERVICES_PROTOCOL.GetNumberOfProcessors().\r
+  @param[in] EnableOldBSP      If TRUE, then the old BSP will be listed as an\r
+                               enabled AP. Otherwise, it will be disabled.\r
+\r
+  @retval EFI_SUCCESS             BSP successfully switched.\r
+  @retval EFI_UNSUPPORTED         Switching the BSP cannot be completed prior to\r
+                                  this service returning.\r
+  @retval EFI_UNSUPPORTED         Switching the BSP is not supported.\r
+  @retval EFI_SUCCESS             The calling processor is an AP.\r
+  @retval EFI_NOT_FOUND           The processor with the handle specified by\r
+                                  ProcessorNumber does not exist.\r
+  @retval EFI_INVALID_PARAMETER   ProcessorNumber specifies the current BSP or\r
+                                  a disabled AP.\r
+  @retval EFI_NOT_READY           The specified AP is busy.\r
+\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+SwitchBSP (\r
+  IN EFI_MP_SERVICES_PROTOCOL  *This,\r
+  IN  UINTN                    ProcessorNumber,\r
+  IN  BOOLEAN                  EnableOldBSP\r
+  )\r
+{\r
+  return EFI_UNSUPPORTED;\r
+}\r
+\r
+/**\r
+  This service lets the caller enable or disable an AP from this point onward.\r
+  This service may only be called from the BSP.\r
+\r
+  This service allows the caller enable or disable an AP from this point onward.\r
+  The caller can optionally specify the health status of the AP by Health. If\r
+  an AP is being disabled, then the state of the disabled AP is implementation\r
+  dependent. If an AP is enabled, then the implementation must guarantee that a\r
+  complete initialization sequence is performed on the AP, so the AP is in a state\r
+  that is compatible with an MP operating system. This service may not be supported\r
+  after the UEFI Event EFI_EVENT_GROUP_READY_TO_BOOT is signaled.\r
+\r
+  If the enable or disable AP operation cannot be completed prior to the return\r
+  from this service, then EFI_UNSUPPORTED must be returned.\r
+\r
+  @param[in] This              A pointer to the EFI_MP_SERVICES_PROTOCOL instance.\r
+  @param[in] ProcessorNumber   The handle number of AP that is to become the new\r
+                               BSP. The range is from 0 to the total number of\r
+                               logical processors minus 1. The total number of\r
+                               logical processors can be retrieved by\r
+                               EFI_MP_SERVICES_PROTOCOL.GetNumberOfProcessors().\r
+  @param[in] EnableAP          Specifies the new state for the processor for\r
+                               enabled, FALSE for disabled.\r
+  @param[in] HealthFlag        If not NULL, a pointer to a value that specifies\r
+                               the new health status of the AP. This flag\r
+                               corresponds to StatusFlag defined in\r
+                               EFI_MP_SERVICES_PROTOCOL.GetProcessorInfo(). Only\r
+                               the PROCESSOR_HEALTH_STATUS_BIT is used. All other\r
+                               bits are ignored.  If it is NULL, this parameter\r
+                               is ignored.\r
+\r
+  @retval EFI_SUCCESS             The specified AP was enabled or disabled successfully.\r
+  @retval EFI_UNSUPPORTED         Enabling or disabling an AP cannot be completed\r
+                                  prior to this service returning.\r
+  @retval EFI_UNSUPPORTED         Enabling or disabling an AP is not supported.\r
+  @retval EFI_DEVICE_ERROR        The calling processor is an AP.\r
+  @retval EFI_NOT_FOUND           Processor with the handle specified by ProcessorNumber\r
+                                  does not exist.\r
+  @retval EFI_INVALID_PARAMETER   ProcessorNumber specifies the BSP.\r
+\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+EnableDisableAP (\r
+  IN  EFI_MP_SERVICES_PROTOCOL  *This,\r
+  IN  UINTN                     ProcessorNumber,\r
+  IN  BOOLEAN                   EnableAP,\r
+  IN  UINT32                    *HealthFlag OPTIONAL\r
+  )\r
+{\r
+  UINTN        StatusFlag;\r
+  CPU_AP_DATA  *CpuData;\r
+\r
+  StatusFlag = mCpuMpData.CpuData[ProcessorNumber].Info.StatusFlag;\r
+  CpuData    = &mCpuMpData.CpuData[ProcessorNumber];\r
+\r
+  if (!IsCurrentProcessorBSP ()) {\r
+    return EFI_DEVICE_ERROR;\r
+  }\r
+\r
+  if (ProcessorNumber >= mCpuMpData.NumberOfProcessors) {\r
+    return EFI_NOT_FOUND;\r
+  }\r
+\r
+  if (IsProcessorBSP (ProcessorNumber)) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  if (GetApState (CpuData) != CpuStateIdle) {\r
+    return EFI_UNSUPPORTED;\r
+  }\r
+\r
+  if (EnableAP) {\r
+    if (!IsProcessorEnabled (ProcessorNumber)) {\r
+      mCpuMpData.NumberOfEnabledProcessors++;\r
+    }\r
+\r
+    StatusFlag |= PROCESSOR_ENABLED_BIT;\r
+  } else {\r
+    if (IsProcessorEnabled (ProcessorNumber) && !IsProcessorBSP (ProcessorNumber)) {\r
+      mCpuMpData.NumberOfEnabledProcessors--;\r
+    }\r
+\r
+    StatusFlag &= ~PROCESSOR_ENABLED_BIT;\r
+  }\r
+\r
+  if ((HealthFlag != NULL) && !IsProcessorBSP (ProcessorNumber)) {\r
+    StatusFlag &= ~PROCESSOR_HEALTH_STATUS_BIT;\r
+    StatusFlag |= (*HealthFlag & PROCESSOR_HEALTH_STATUS_BIT);\r
+  }\r
+\r
+  mCpuMpData.CpuData[ProcessorNumber].Info.StatusFlag = StatusFlag;\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+  This return the handle number for the calling processor.  This service may be\r
+  called from the BSP and APs.\r
+\r
+  This service returns the processor handle number for the calling processor.\r
+  The returned value is in the range from 0 to the total number of logical\r
+  processors minus 1. The total number of logical processors can be retrieved\r
+  with EFI_MP_SERVICES_PROTOCOL.GetNumberOfProcessors(). This service may be\r
+  called from the BSP and APs. If ProcessorNumber is NULL, then EFI_INVALID_PARAMETER\r
+  is returned. Otherwise, the current processors handle number is returned in\r
+  ProcessorNumber, and EFI_SUCCESS is returned.\r
+\r
+  @param[in] This              A pointer to the EFI_MP_SERVICES_PROTOCOL instance.\r
+  @param[out] ProcessorNumber  The handle number of AP that is to become the new\r
+                               BSP. The range is from 0 to the total number of\r
+                               logical processors minus 1. The total number of\r
+                               logical processors can be retrieved by\r
+                               EFI_MP_SERVICES_PROTOCOL.GetNumberOfProcessors().\r
+\r
+  @retval EFI_SUCCESS             The current processor handle number was returned\r
+                                  in ProcessorNumber.\r
+  @retval EFI_INVALID_PARAMETER   ProcessorNumber is NULL.\r
+\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+WhoAmI (\r
+  IN EFI_MP_SERVICES_PROTOCOL  *This,\r
+  OUT UINTN                    *ProcessorNumber\r
+  )\r
+{\r
+  UINTN   Index;\r
+  UINT64  ProcessorId;\r
+\r
+  if (ProcessorNumber == NULL) {\r
+    return EFI_INVALID_PARAMETER;\r
+  }\r
+\r
+  ProcessorId = GET_MPIDR_AFFINITY_BITS (ArmReadMpidr ());\r
+  for (Index = 0; Index < mCpuMpData.NumberOfProcessors; Index++) {\r
+    if (ProcessorId == gProcessorIDs[Index]) {\r
+      *ProcessorNumber = Index;\r
+      break;\r
+    }\r
+  }\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+STATIC EFI_MP_SERVICES_PROTOCOL  mMpServicesProtocol = {\r
+  GetNumberOfProcessors,\r
+  GetProcessorInfo,\r
+  StartupAllAPs,\r
+  StartupThisAP,\r
+  SwitchBSP,\r
+  EnableDisableAP,\r
+  WhoAmI\r
+};\r
+\r
+/** Adds the specified processor the list of failed processors.\r
+\r
+   @param ProcessorIndex The processor index to add.\r
+   @param ApState        Processor state.\r
+\r
+**/\r
+STATIC\r
+VOID\r
+AddProcessorToFailedList (\r
+  UINTN      ProcessorIndex,\r
+  CPU_STATE  ApState\r
+  )\r
+{\r
+  UINTN    Index;\r
+  BOOLEAN  Found;\r
+\r
+  Found = FALSE;\r
+\r
+  if ((mCpuMpData.FailedList == NULL) ||\r
+      (ApState == CpuStateIdle) ||\r
+      (ApState == CpuStateFinished) ||\r
+      IsProcessorBSP (ProcessorIndex))\r
+  {\r
+    return;\r
+  }\r
+\r
+  // If we are retrying make sure we don't double count\r
+  for (Index = 0; Index < mCpuMpData.FailedListIndex; Index++) {\r
+    if (mCpuMpData.FailedList[Index] == ProcessorIndex) {\r
+      Found = TRUE;\r
+      break;\r
+    }\r
+  }\r
+\r
+  /* If the CPU isn't already in the FailedList, add it */\r
+  if (!Found) {\r
+    mCpuMpData.FailedList[mCpuMpData.FailedListIndex++] = ProcessorIndex;\r
+  }\r
+}\r
+\r
+/** Handles the StartupAllAPs case where the timeout has occurred.\r
+\r
+**/\r
+STATIC\r
+VOID\r
+ProcessStartupAllAPsTimeout (\r
+  VOID\r
+  )\r
+{\r
+  CPU_AP_DATA  *CpuData;\r
+  UINTN        Index;\r
+\r
+  if (mCpuMpData.FailedList == NULL) {\r
+    return;\r
+  }\r
+\r
+  for (Index = 0; Index < mCpuMpData.NumberOfProcessors; Index++) {\r
+    CpuData = &mCpuMpData.CpuData[Index];\r
+    if (IsProcessorBSP (Index)) {\r
+      // Skip BSP\r
+      continue;\r
+    }\r
+\r
+    if (!IsProcessorEnabled (Index)) {\r
+      // Skip Disabled processors\r
+      continue;\r
+    }\r
+\r
+    CpuData = &mCpuMpData.CpuData[Index];\r
+    AddProcessorToFailedList (Index, GetApState (CpuData));\r
+  }\r
+}\r
+\r
+/** Updates the status of the APs.\r
+\r
+   @param[in] ProcessorIndex The index of the AP to update.\r
+**/\r
+STATIC\r
+VOID\r
+UpdateApStatus (\r
+  IN UINTN  ProcessorIndex\r
+  )\r
+{\r
+  EFI_STATUS   Status;\r
+  CPU_AP_DATA  *CpuData;\r
+  CPU_AP_DATA  *NextCpuData;\r
+  CPU_STATE    State;\r
+  UINTN        NextNumber;\r
+\r
+  CpuData = &mCpuMpData.CpuData[ProcessorIndex];\r
+\r
+  if (IsProcessorBSP (ProcessorIndex)) {\r
+    // Skip BSP\r
+    return;\r
+  }\r
+\r
+  if (!IsProcessorEnabled (ProcessorIndex)) {\r
+    // Skip Disabled processors\r
+    return;\r
+  }\r
+\r
+  State = GetApState (CpuData);\r
+\r
+  switch (State) {\r
+    case CpuStateFinished:\r
+      if (mCpuMpData.SingleThread) {\r
+        Status = GetNextBlockedNumber (&NextNumber);\r
+        if (!EFI_ERROR (Status)) {\r
+          NextCpuData = &mCpuMpData.CpuData[NextNumber];\r
+\r
+          NextCpuData->State = CpuStateReady;\r
+\r
+          SetApProcedure (\r
+            NextCpuData,\r
+            mCpuMpData.Procedure,\r
+            mCpuMpData.ProcedureArgument\r
+            );\r
+\r
+          Status = DispatchCpu (NextNumber);\r
+          if (!EFI_ERROR (Status)) {\r
+            mCpuMpData.StartCount++;\r
+          } else {\r
+            AddProcessorToFailedList (NextNumber, NextCpuData->State);\r
+          }\r
+        }\r
+      }\r
+\r
+      CpuData->State = CpuStateIdle;\r
+      mCpuMpData.FinishCount++;\r
+      break;\r
+\r
+    default:\r
+      break;\r
+  }\r
+}\r
+\r
+/**\r
+  If a timeout is specified in StartupAllAps(), a timer is set, which invokes\r
+  this procedure periodically to check whether all APs have finished.\r
+\r
+  @param[in] Event   The WaitEvent the user supplied.\r
+  @param[in] Context The event context.\r
+**/\r
+STATIC\r
+VOID\r
+EFIAPI\r
+CheckAllAPsStatus (\r
+  IN  EFI_EVENT  Event,\r
+  IN  VOID       *Context\r
+  )\r
+{\r
+  EFI_STATUS  Status;\r
+  UINTN       Index;\r
+\r
+  mCpuMpData.AllTimeTaken += POLL_INTERVAL_US;\r
+\r
+  for (Index = 0; Index < mCpuMpData.NumberOfProcessors; Index++) {\r
+    UpdateApStatus (Index);\r
+  }\r
+\r
+  if (mCpuMpData.AllTimeoutActive && (mCpuMpData.AllTimeTaken > mCpuMpData.AllTimeout)) {\r
+    ProcessStartupAllAPsTimeout ();\r
+\r
+    // Force terminal exit\r
+    mCpuMpData.FinishCount = mCpuMpData.StartCount;\r
+  }\r
+\r
+  if (mCpuMpData.FinishCount != mCpuMpData.StartCount) {\r
+    return;\r
+  }\r
+\r
+  gBS->SetTimer (\r
+         mCpuMpData.CheckAllAPsEvent,\r
+         TimerCancel,\r
+         0\r
+         );\r
+\r
+  if (mCpuMpData.FailedListIndex == 0) {\r
+    if (mCpuMpData.FailedList != NULL) {\r
+      // Since we don't have the original `FailedCpuList`\r
+      // pointer here to set to NULL, don't free the\r
+      // memory.\r
+    }\r
+  }\r
+\r
+  Status = gBS->SignalEvent (mCpuMpData.AllWaitEvent);\r
+  ASSERT_EFI_ERROR (Status);\r
+  mCpuMpData.AllWaitEvent = NULL;\r
+}\r
+\r
+/** Invoked periodically via a timer to check the state of the processor.\r
+\r
+   @param Event   The event supplied by the timer expiration.\r
+   @param Context The processor context.\r
+\r
+**/\r
+STATIC\r
+VOID\r
+EFIAPI\r
+CheckThisAPStatus (\r
+  IN  EFI_EVENT  Event,\r
+  IN  VOID       *Context\r
+  )\r
+{\r
+  EFI_STATUS   Status;\r
+  CPU_AP_DATA  *CpuData;\r
+  CPU_STATE    State;\r
+\r
+  CpuData = Context;\r
+\r
+  CpuData->TimeTaken += POLL_INTERVAL_US;\r
+\r
+  State = GetApState (CpuData);\r
+\r
+  if (State == CpuStateFinished) {\r
+    Status = gBS->SetTimer (CpuData->CheckThisAPEvent, TimerCancel, 0);\r
+    ASSERT_EFI_ERROR (Status);\r
+\r
+    if (CpuData->SingleApFinished != NULL) {\r
+      *(CpuData->SingleApFinished) = TRUE;\r
+    }\r
+\r
+    if (CpuData->WaitEvent != NULL) {\r
+      Status = gBS->SignalEvent (CpuData->WaitEvent);\r
+      ASSERT_EFI_ERROR (Status);\r
+    }\r
+\r
+    CpuData->State = CpuStateIdle;\r
+  }\r
+\r
+  if (CpuData->TimeoutActive && (CpuData->TimeTaken > CpuData->Timeout)) {\r
+    Status = gBS->SetTimer (CpuData->CheckThisAPEvent, TimerCancel, 0);\r
+    if (CpuData->WaitEvent != NULL) {\r
+      Status = gBS->SignalEvent (CpuData->WaitEvent);\r
+      ASSERT_EFI_ERROR (Status);\r
+      CpuData->WaitEvent = NULL;\r
+    }\r
+  }\r
+}\r
+\r
+/**\r
+  This function is called by all processors (both BSP and AP) once and collects\r
+  MP related data.\r
+\r
+  @param BSP            TRUE if the processor is the BSP.\r
+  @param Mpidr          The MPIDR for the specified processor. This should be\r
+                        the full MPIDR and not only the affinity bits.\r
+  @param ProcessorIndex The index of the processor.\r
+\r
+  @return EFI_SUCCESS if the data for the processor collected and filled in.\r
+\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+FillInProcessorInformation (\r
+  IN BOOLEAN  BSP,\r
+  IN UINTN    Mpidr,\r
+  IN UINTN    ProcessorIndex\r
+  )\r
+{\r
+  EFI_PROCESSOR_INFORMATION  *CpuInfo;\r
+\r
+  CpuInfo = &mCpuMpData.CpuData[ProcessorIndex].Info;\r
+\r
+  CpuInfo->ProcessorId = GET_MPIDR_AFFINITY_BITS (Mpidr);\r
+  CpuInfo->StatusFlag  = PROCESSOR_ENABLED_BIT | PROCESSOR_HEALTH_STATUS_BIT;\r
+\r
+  if (BSP) {\r
+    CpuInfo->StatusFlag |= PROCESSOR_AS_BSP_BIT;\r
+  }\r
+\r
+  if ((Mpidr & MPIDR_MT_BIT) > 0) {\r
+    CpuInfo->Location.Package = GET_MPIDR_AFF2 (Mpidr);\r
+    CpuInfo->Location.Core    = GET_MPIDR_AFF1 (Mpidr);\r
+    CpuInfo->Location.Thread  = GET_MPIDR_AFF0 (Mpidr);\r
+\r
+    CpuInfo->ExtendedInformation.Location2.Package = GET_MPIDR_AFF3 (Mpidr);\r
+    CpuInfo->ExtendedInformation.Location2.Die     = GET_MPIDR_AFF2 (Mpidr);\r
+    CpuInfo->ExtendedInformation.Location2.Core    = GET_MPIDR_AFF1 (Mpidr);\r
+    CpuInfo->ExtendedInformation.Location2.Thread  = GET_MPIDR_AFF0 (Mpidr);\r
+  } else {\r
+    CpuInfo->Location.Package = GET_MPIDR_AFF1 (Mpidr);\r
+    CpuInfo->Location.Core    = GET_MPIDR_AFF0 (Mpidr);\r
+    CpuInfo->Location.Thread  = 0;\r
+\r
+    CpuInfo->ExtendedInformation.Location2.Package = GET_MPIDR_AFF2 (Mpidr);\r
+    CpuInfo->ExtendedInformation.Location2.Die     = GET_MPIDR_AFF1 (Mpidr);\r
+    CpuInfo->ExtendedInformation.Location2.Core    = GET_MPIDR_AFF0 (Mpidr);\r
+    CpuInfo->ExtendedInformation.Location2.Thread  = 0;\r
+  }\r
+\r
+  mCpuMpData.CpuData[ProcessorIndex].State = BSP ? CpuStateBusy : CpuStateIdle;\r
+\r
+  mCpuMpData.CpuData[ProcessorIndex].Procedure = NULL;\r
+  mCpuMpData.CpuData[ProcessorIndex].Parameter = NULL;\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+/** Initializes the MP Services system data\r
+\r
+   @param NumberOfProcessors The number of processors, both BSP and AP.\r
+   @param CoreInfo           CPU information gathered earlier during boot.\r
+\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+MpServicesInitialize (\r
+  IN   UINTN                NumberOfProcessors,\r
+  IN   CONST ARM_CORE_INFO  *CoreInfo\r
+  )\r
+{\r
+  EFI_STATUS  Status;\r
+  UINTN       Index;\r
+  EFI_EVENT   ReadyToBootEvent;\r
+  BOOLEAN     IsBsp;\r
+\r
+  //\r
+  // Clear the data structure area first.\r
+  //\r
+  ZeroMem (&mCpuMpData, sizeof (CPU_MP_DATA));\r
+  //\r
+  // First BSP fills and inits all known values, including its own records.\r
+  //\r
+  mCpuMpData.NumberOfProcessors        = NumberOfProcessors;\r
+  mCpuMpData.NumberOfEnabledProcessors = NumberOfProcessors;\r
+\r
+  mCpuMpData.CpuData = AllocateZeroPool (\r
+                         mCpuMpData.NumberOfProcessors * sizeof (CPU_AP_DATA)\r
+                         );\r
+\r
+  if (mCpuMpData.CpuData == NULL) {\r
+    return EFI_OUT_OF_RESOURCES;\r
+  }\r
+\r
+  /* Allocate one extra for the sentinel entry at the end */\r
+  gProcessorIDs = AllocateZeroPool ((mCpuMpData.NumberOfProcessors + 1) * sizeof (UINT64));\r
+  ASSERT (gProcessorIDs != NULL);\r
+\r
+  Status = gBS->CreateEvent (\r
+                  EVT_TIMER | EVT_NOTIFY_SIGNAL,\r
+                  TPL_CALLBACK,\r
+                  CheckAllAPsStatus,\r
+                  NULL,\r
+                  &mCpuMpData.CheckAllAPsEvent\r
+                  );\r
+  ASSERT_EFI_ERROR (Status);\r
+\r
+  gApStacksBase = AllocatePages (\r
+                    EFI_SIZE_TO_PAGES (\r
+                      mCpuMpData.NumberOfProcessors *\r
+                      gApStackSize\r
+                      )\r
+                    );\r
+  ASSERT (gApStacksBase != NULL);\r
+\r
+  for (Index = 0; Index < mCpuMpData.NumberOfProcessors; Index++) {\r
+    if (GET_MPIDR_AFFINITY_BITS (ArmReadMpidr ()) == CoreInfo[Index].Mpidr) {\r
+      IsBsp = TRUE;\r
+    } else {\r
+      IsBsp = FALSE;\r
+    }\r
+\r
+    FillInProcessorInformation (IsBsp, CoreInfo[Index].Mpidr, Index);\r
+\r
+    gProcessorIDs[Index] = mCpuMpData.CpuData[Index].Info.ProcessorId;\r
+\r
+    Status = gBS->CreateEvent (\r
+                    EVT_TIMER | EVT_NOTIFY_SIGNAL,\r
+                    TPL_CALLBACK,\r
+                    CheckThisAPStatus,\r
+                    (VOID *)&mCpuMpData.CpuData[Index],\r
+                    &mCpuMpData.CpuData[Index].CheckThisAPEvent\r
+                    );\r
+    ASSERT_EFI_ERROR (Status);\r
+  }\r
+\r
+  gProcessorIDs[Index] = MAX_UINT64;\r
+\r
+  gTcr   = ArmGetTCR ();\r
+  gMair  = ArmGetMAIR ();\r
+  gTtbr0 = ArmGetTTBR0BaseAddress ();\r
+\r
+  //\r
+  // The global pointer variables as well as the gProcessorIDs array contents\r
+  // are accessed by the other cores so we must clean them to the PoC\r
+  //\r
+  WriteBackDataCacheRange (&gProcessorIDs, sizeof (UINT64 *));\r
+  WriteBackDataCacheRange (&gApStacksBase, sizeof (UINT64 *));\r
+\r
+  WriteBackDataCacheRange (\r
+    gProcessorIDs,\r
+    (mCpuMpData.NumberOfProcessors + 1) * sizeof (UINT64)\r
+    );\r
+\r
+  mNonBlockingModeAllowed = TRUE;\r
+\r
+  Status = EfiCreateEventReadyToBootEx (\r
+             TPL_CALLBACK,\r
+             ReadyToBootSignaled,\r
+             NULL,\r
+             &ReadyToBootEvent\r
+             );\r
+  ASSERT_EFI_ERROR (Status);\r
+\r
+  return EFI_SUCCESS;\r
+}\r
+\r
+/**\r
+  Event notification function called when the EFI_EVENT_GROUP_READY_TO_BOOT is\r
+  signaled. After this point, non-blocking mode is no longer allowed.\r
+\r
+  @param  Event     Event whose notification function is being invoked.\r
+  @param  Context   The pointer to the notification function's context,\r
+                    which is implementation-dependent.\r
+\r
+**/\r
+STATIC\r
+VOID\r
+EFIAPI\r
+ReadyToBootSignaled (\r
+  IN  EFI_EVENT  Event,\r
+  IN  VOID       *Context\r
+  )\r
+{\r
+  mNonBlockingModeAllowed = FALSE;\r
+}\r
+\r
+/** Initialize multi-processor support.\r
+\r
+  @param ImageHandle  Image handle.\r
+  @param SystemTable  System table.\r
+\r
+  @return EFI_SUCCESS on success, or an error code.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+ArmPsciMpServicesDxeInitialize (\r
+  IN EFI_HANDLE        ImageHandle,\r
+  IN EFI_SYSTEM_TABLE  *SystemTable\r
+  )\r
+{\r
+  EFI_STATUS                 Status;\r
+  EFI_HANDLE                 Handle;\r
+  UINTN                      MaxCpus;\r
+  EFI_LOADED_IMAGE_PROTOCOL  *Image;\r
+  EFI_HOB_GENERIC_HEADER     *Hob;\r
+  VOID                       *HobData;\r
+  UINTN                      HobDataSize;\r
+  CONST ARM_CORE_INFO        *CoreInfo;\r
+\r
+  MaxCpus = 1;\r
+\r
+  Status = gBS->HandleProtocol (\r
+                  ImageHandle,\r
+                  &gEfiLoadedImageProtocolGuid,\r
+                  (VOID **)&Image\r
+                  );\r
+  ASSERT_EFI_ERROR (Status);\r
+\r
+  //\r
+  // Parts of the code in this driver may be executed by other cores running\r
+  // with the MMU off so we need to ensure that everything is clean to the\r
+  // point of coherency (PoC)\r
+  //\r
+  WriteBackDataCacheRange (Image->ImageBase, Image->ImageSize);\r
+\r
+  Hob = GetFirstGuidHob (&gArmMpCoreInfoGuid);\r
+  if (Hob != NULL) {\r
+    HobData     = GET_GUID_HOB_DATA (Hob);\r
+    HobDataSize = GET_GUID_HOB_DATA_SIZE (Hob);\r
+    CoreInfo    = (ARM_CORE_INFO *)HobData;\r
+    MaxCpus     = HobDataSize / sizeof (ARM_CORE_INFO);\r
+  }\r
+\r
+  if (MaxCpus == 1) {\r
+    DEBUG ((DEBUG_WARN, "Trying to use EFI_MP_SERVICES_PROTOCOL on a UP system"));\r
+    // We are not MP so nothing to do\r
+    return EFI_NOT_FOUND;\r
+  }\r
+\r
+  Status = MpServicesInitialize (MaxCpus, CoreInfo);\r
+  if (Status != EFI_SUCCESS) {\r
+    ASSERT_EFI_ERROR (Status);\r
+    return Status;\r
+  }\r
+\r
+  //\r
+  // Now install the MP services protocol.\r
+  //\r
+  Handle = NULL;\r
+  Status = gBS->InstallMultipleProtocolInterfaces (\r
+                  &Handle,\r
+                  &gEfiMpServiceProtocolGuid,\r
+                  &mMpServicesProtocol,\r
+                  NULL\r
+                  );\r
+  ASSERT_EFI_ERROR (Status);\r
+\r
+  return Status;\r
+}\r
+\r
+/** AP exception handler.\r
+\r
+  @param InterruptType The AArch64 CPU exception type.\r
+  @param SystemContext System context.\r
+\r
+**/\r
+STATIC\r
+VOID\r
+EFIAPI\r
+ApExceptionHandler (\r
+  IN CONST EFI_EXCEPTION_TYPE  InterruptType,\r
+  IN CONST EFI_SYSTEM_CONTEXT  SystemContext\r
+  )\r
+{\r
+  ARM_SMC_ARGS  Args;\r
+  UINT64        Mpidr;\r
+  UINTN         Index;\r
+  UINTN         ProcessorIndex;\r
+\r
+  Mpidr = GET_MPIDR_AFFINITY_BITS (ArmReadMpidr ());\r
+\r
+  Index          = 0;\r
+  ProcessorIndex = MAX_UINT64;\r
+\r
+  do {\r
+    if (gProcessorIDs[Index] == Mpidr) {\r
+      ProcessorIndex = Index;\r
+      break;\r
+    }\r
+\r
+    Index++;\r
+  } while (gProcessorIDs[Index] != MAX_UINT64);\r
+\r
+  if (ProcessorIndex != MAX_UINT64) {\r
+    mCpuMpData.CpuData[ProcessorIndex].State = CpuStateFinished;\r
+    ArmDataMemoryBarrier ();\r
+  }\r
+\r
+  Args.Arg0 = ARM_SMC_ID_PSCI_CPU_OFF;\r
+  ArmCallSmc (&Args);\r
+\r
+  /* Should never be reached */\r
+  ASSERT (FALSE);\r
+  CpuDeadLoop ();\r
+}\r
+\r
+/** C entry-point for the AP.\r
+    This function gets called from the assembly function ApEntryPoint.\r
+\r
+**/\r
+VOID\r
+ApProcedure (\r
+  VOID\r
+  )\r
+{\r
+  ARM_SMC_ARGS      Args;\r
+  EFI_AP_PROCEDURE  UserApProcedure;\r
+  VOID              *UserApParameter;\r
+  UINTN             ProcessorIndex;\r
+\r
+  ProcessorIndex = 0;\r
+\r
+  WhoAmI (&mMpServicesProtocol, &ProcessorIndex);\r
+\r
+  /* Fetch the user-supplied procedure and parameter to execute */\r
+  UserApProcedure = mCpuMpData.CpuData[ProcessorIndex].Procedure;\r
+  UserApParameter = mCpuMpData.CpuData[ProcessorIndex].Parameter;\r
+\r
+  InitializeCpuExceptionHandlers (NULL);\r
+  RegisterCpuInterruptHandler (EXCEPT_AARCH64_SYNCHRONOUS_EXCEPTIONS, ApExceptionHandler);\r
+  RegisterCpuInterruptHandler (EXCEPT_AARCH64_IRQ, ApExceptionHandler);\r
+  RegisterCpuInterruptHandler (EXCEPT_AARCH64_FIQ, ApExceptionHandler);\r
+  RegisterCpuInterruptHandler (EXCEPT_AARCH64_SERROR, ApExceptionHandler);\r
+\r
+  UserApProcedure (UserApParameter);\r
+\r
+  mCpuMpData.CpuData[ProcessorIndex].State = CpuStateFinished;\r
+\r
+  ArmDataMemoryBarrier ();\r
+\r
+  /* Since we're finished with this AP, turn it off */\r
+  Args.Arg0 = ARM_SMC_ID_PSCI_CPU_OFF;\r
+  ArmCallSmc (&Args);\r
+\r
+  /* Should never be reached */\r
+  ASSERT (FALSE);\r
+  CpuDeadLoop ();\r
+}\r
+\r
+/** Returns whether the processor executing this function is the BSP.\r
+\r
+    @return Whether the current processor is the BSP.\r
+**/\r
+STATIC\r
+BOOLEAN\r
+IsCurrentProcessorBSP (\r
+  VOID\r
+  )\r
+{\r
+  EFI_STATUS  Status;\r
+  UINTN       ProcessorIndex;\r
+\r
+  Status = WhoAmI (&mMpServicesProtocol, &ProcessorIndex);\r
+  if (EFI_ERROR (Status)) {\r
+    ASSERT_EFI_ERROR (Status);\r
+    return FALSE;\r
+  }\r
+\r
+  return IsProcessorBSP (ProcessorIndex);\r
+}\r
+\r
+/** Returns whether the specified processor is enabled.\r
+\r
+   @param[in] ProcessorIndex The index of the processor to check.\r
+\r
+   @return TRUE if the processor is enabled, FALSE otherwise.\r
+**/\r
+STATIC\r
+BOOLEAN\r
+IsProcessorEnabled (\r
+  UINTN  ProcessorIndex\r
+  )\r
+{\r
+  EFI_PROCESSOR_INFORMATION  *CpuInfo;\r
+\r
+  CpuInfo = &mCpuMpData.CpuData[ProcessorIndex].Info;\r
+\r
+  return (CpuInfo->StatusFlag & PROCESSOR_ENABLED_BIT) != 0;\r
+}\r
+\r
+/** Sets up the state for the StartupAllAPs function.\r
+\r
+   @param SingleThread Whether the APs will execute sequentially.\r
+\r
+**/\r
+STATIC\r
+VOID\r
+StartupAllAPsPrepareState (\r
+  IN BOOLEAN  SingleThread\r
+  )\r
+{\r
+  UINTN        Index;\r
+  CPU_STATE    APInitialState;\r
+  CPU_AP_DATA  *CpuData;\r
+\r
+  mCpuMpData.FinishCount  = 0;\r
+  mCpuMpData.StartCount   = 0;\r
+  mCpuMpData.SingleThread = SingleThread;\r
+\r
+  APInitialState = CpuStateReady;\r
+\r
+  for (Index = 0; Index < mCpuMpData.NumberOfProcessors; Index++) {\r
+    CpuData = &mCpuMpData.CpuData[Index];\r
+\r
+    //\r
+    // Get APs prepared, and put failing APs into FailedCpuList.\r
+    // If "SingleThread", only 1 AP will put into ready state, other AP will be\r
+    // put into ready state 1 by 1, until the previous 1 finished its task.\r
+    // If not "SingleThread", all APs are put into ready state from the\r
+    // beginning\r
+    //\r
+\r
+    if (IsProcessorBSP (Index)) {\r
+      // Skip BSP\r
+      continue;\r
+    }\r
+\r
+    if (!IsProcessorEnabled (Index)) {\r
+      // Skip Disabled processors\r
+      if (mCpuMpData.FailedList != NULL) {\r
+        mCpuMpData.FailedList[mCpuMpData.FailedListIndex++] = Index;\r
+      }\r
+\r
+      continue;\r
+    }\r
+\r
+    // If any APs finished after timing out, reset state to Idle\r
+    if (GetApState (CpuData) == CpuStateFinished) {\r
+      CpuData->State = CpuStateIdle;\r
+    }\r
+\r
+    if (GetApState (CpuData) != CpuStateIdle) {\r
+      // Skip busy processors\r
+      if (mCpuMpData.FailedList != NULL) {\r
+        mCpuMpData.FailedList[mCpuMpData.FailedListIndex++] = Index;\r
+      }\r
+    }\r
+\r
+    CpuData->State = APInitialState;\r
+\r
+    mCpuMpData.StartCount++;\r
+    if (SingleThread) {\r
+      APInitialState = CpuStateBlocked;\r
+    }\r
+  }\r
+}\r
+\r
+/** Handles execution of StartupAllAPs when a WaitEvent has been specified.\r
+\r
+  @param Procedure         The user-supplied procedure.\r
+  @param ProcedureArgument The user-supplied procedure argument.\r
+  @param WaitEvent         The wait event to be signaled when the work is\r
+                           complete or a timeout has occurred.\r
+  @param TimeoutInMicroseconds The timeout for the work to be completed. Zero\r
+                               indicates an infinite timeout.\r
+  @param SingleThread          Whether the APs will execute sequentially.\r
+  @param FailedCpuList         User-supplied pointer for list of failed CPUs.\r
+\r
+   @return EFI_SUCCESS on success.\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+StartupAllAPsWithWaitEvent (\r
+  IN EFI_AP_PROCEDURE  Procedure,\r
+  IN VOID              *ProcedureArgument,\r
+  IN EFI_EVENT         WaitEvent,\r
+  IN UINTN             TimeoutInMicroseconds,\r
+  IN BOOLEAN           SingleThread,\r
+  IN UINTN             **FailedCpuList\r
+  )\r
+{\r
+  EFI_STATUS   Status;\r
+  UINTN        Index;\r
+  CPU_AP_DATA  *CpuData;\r
+\r
+  for (Index = 0; Index < mCpuMpData.NumberOfProcessors; Index++) {\r
+    CpuData = &mCpuMpData.CpuData[Index];\r
+    if (IsProcessorBSP (Index)) {\r
+      // Skip BSP\r
+      continue;\r
+    }\r
+\r
+    if (!IsProcessorEnabled (Index)) {\r
+      // Skip Disabled processors\r
+      continue;\r
+    }\r
+\r
+    if (GetApState (CpuData) == CpuStateReady) {\r
+      SetApProcedure (CpuData, Procedure, ProcedureArgument);\r
+      if ((mCpuMpData.StartCount == 0) || !SingleThread) {\r
+        Status = DispatchCpu (Index);\r
+        if (EFI_ERROR (Status)) {\r
+          AddProcessorToFailedList (Index, CpuData->State);\r
+          break;\r
+        }\r
+      }\r
+    }\r
+  }\r
+\r
+  if (EFI_ERROR (Status)) {\r
+    return EFI_NOT_READY;\r
+  }\r
+\r
+  //\r
+  // Save data into private data structure, and create timer to poll AP state\r
+  // before exiting\r
+  //\r
+  mCpuMpData.Procedure         = Procedure;\r
+  mCpuMpData.ProcedureArgument = ProcedureArgument;\r
+  mCpuMpData.AllWaitEvent      = WaitEvent;\r
+  mCpuMpData.AllTimeout        = TimeoutInMicroseconds;\r
+  mCpuMpData.AllTimeTaken      = 0;\r
+  mCpuMpData.AllTimeoutActive  = (BOOLEAN)(TimeoutInMicroseconds != 0);\r
+  Status                       = gBS->SetTimer (\r
+                                        mCpuMpData.CheckAllAPsEvent,\r
+                                        TimerPeriodic,\r
+                                        POLL_INTERVAL_US\r
+                                        );\r
+\r
+  return Status;\r
+}\r
+\r
+/** Handles execution of StartupAllAPs when no wait event has been specified.\r
+\r
+  @param Procedure             The user-supplied procedure.\r
+  @param ProcedureArgument     The user-supplied procedure argument.\r
+  @param TimeoutInMicroseconds The timeout for the work to be completed. Zero\r
+                                indicates an infinite timeout.\r
+  @param SingleThread          Whether the APs will execute sequentially.\r
+  @param FailedCpuList         User-supplied pointer for list of failed CPUs.\r
+\r
+  @return EFI_SUCCESS on success.\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+StartupAllAPsNoWaitEvent (\r
+  IN EFI_AP_PROCEDURE  Procedure,\r
+  IN VOID              *ProcedureArgument,\r
+  IN UINTN             TimeoutInMicroseconds,\r
+  IN BOOLEAN           SingleThread,\r
+  IN UINTN             **FailedCpuList\r
+  )\r
+{\r
+  EFI_STATUS   Status;\r
+  UINTN        Index;\r
+  UINTN        NextIndex;\r
+  UINTN        Timeout;\r
+  CPU_AP_DATA  *CpuData;\r
+  BOOLEAN      DispatchError;\r
+\r
+  Timeout       = TimeoutInMicroseconds;\r
+  DispatchError = FALSE;\r
+\r
+  while (TRUE) {\r
+    for (Index = 0; Index < mCpuMpData.NumberOfProcessors; Index++) {\r
+      CpuData = &mCpuMpData.CpuData[Index];\r
+      if (IsProcessorBSP (Index)) {\r
+        // Skip BSP\r
+        continue;\r
+      }\r
+\r
+      if (!IsProcessorEnabled (Index)) {\r
+        // Skip Disabled processors\r
+        continue;\r
+      }\r
+\r
+      switch (GetApState (CpuData)) {\r
+        case CpuStateReady:\r
+          SetApProcedure (CpuData, Procedure, ProcedureArgument);\r
+          Status = DispatchCpu (Index);\r
+          if (EFI_ERROR (Status)) {\r
+            AddProcessorToFailedList (Index, CpuData->State);\r
+            CpuData->State = CpuStateIdle;\r
+            mCpuMpData.StartCount--;\r
+            DispatchError = TRUE;\r
+\r
+            if (SingleThread) {\r
+              // Dispatch the next available AP\r
+              Status = GetNextBlockedNumber (&NextIndex);\r
+              if (!EFI_ERROR (Status)) {\r
+                mCpuMpData.CpuData[NextIndex].State = CpuStateReady;\r
+              }\r
+            }\r
+          }\r
+\r
+          break;\r
+\r
+        case CpuStateFinished:\r
+          mCpuMpData.FinishCount++;\r
+          if (SingleThread) {\r
+            Status = GetNextBlockedNumber (&NextIndex);\r
+            if (!EFI_ERROR (Status)) {\r
+              mCpuMpData.CpuData[NextIndex].State = CpuStateReady;\r
+            }\r
+          }\r
+\r
+          CpuData->State = CpuStateIdle;\r
+          break;\r
+\r
+        default:\r
+          break;\r
+      }\r
+    }\r
+\r
+    if (mCpuMpData.FinishCount == mCpuMpData.StartCount) {\r
+      Status = EFI_SUCCESS;\r
+      break;\r
+    }\r
+\r
+    if ((TimeoutInMicroseconds != 0) && (Timeout == 0)) {\r
+      Status = EFI_TIMEOUT;\r
+      break;\r
+    }\r
+\r
+    Timeout -= CalculateAndStallInterval (Timeout);\r
+  }\r
+\r
+  if (Status == EFI_TIMEOUT) {\r
+    // Add any remaining CPUs to the FailedCpuList\r
+    if (FailedCpuList != NULL) {\r
+      for (Index = 0; Index < mCpuMpData.NumberOfProcessors; Index++) {\r
+        AddProcessorToFailedList (Index, mCpuMpData.CpuData[Index].State);\r
+      }\r
+    }\r
+  }\r
+\r
+  if (DispatchError) {\r
+    Status = EFI_NOT_READY;\r
+  }\r
+\r
+  return Status;\r
+}\r
diff --git a/ArmPkg/Drivers/ArmPsciMpServicesDxe/ArmPsciMpServicesDxe.inf b/ArmPkg/Drivers/ArmPsciMpServicesDxe/ArmPsciMpServicesDxe.inf
new file mode 100644 (file)
index 0000000..2c9ab99
--- /dev/null
@@ -0,0 +1,56 @@
+## @file\r
+#  ARM MP services protocol driver\r
+#\r
+#  Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved.<BR>\r
+#\r
+#  SPDX-License-Identifier: BSD-2-Clause-Patent\r
+#\r
+##\r
+\r
+[Defines]\r
+  INF_VERSION                    = 1.27\r
+  BASE_NAME                      = ArmPsciMpServicesDxe\r
+  FILE_GUID                      = 007ab472-dc4a-4df8-a5c2-abb4a327278c\r
+  MODULE_TYPE                    = DXE_DRIVER\r
+  VERSION_STRING                 = 1.0\r
+\r
+  ENTRY_POINT                    = ArmPsciMpServicesDxeInitialize\r
+\r
+[Sources.Common]\r
+  ArmPsciMpServicesDxe.c\r
+  MpFuncs.S\r
+  MpServicesInternal.h\r
+\r
+[Packages]\r
+  ArmPkg/ArmPkg.dec\r
+  ArmPlatformPkg/ArmPlatformPkg.dec\r
+  EmbeddedPkg/EmbeddedPkg.dec\r
+  MdePkg/MdePkg.dec\r
+  MdeModulePkg/MdeModulePkg.dec\r
+\r
+[LibraryClasses]\r
+  ArmLib\r
+  ArmMmuLib\r
+  ArmSmcLib\r
+  BaseMemoryLib\r
+  CacheMaintenanceLib\r
+  CpuExceptionHandlerLib\r
+  DebugLib\r
+  HobLib\r
+  MemoryAllocationLib\r
+  UefiBootServicesTableLib\r
+  UefiDriverEntryPoint\r
+  UefiLib\r
+\r
+[Protocols]\r
+  gEfiMpServiceProtocolGuid            ## PRODUCES\r
+  gEfiLoadedImageProtocolGuid          ## CONSUMES\r
+\r
+[Guids]\r
+  gArmMpCoreInfoGuid\r
+\r
+[Depex]\r
+  TRUE\r
+\r
+[BuildOptions]\r
+  GCC:*_*_*_CC_FLAGS = -mstrict-align\r
diff --git a/ArmPkg/Drivers/ArmPsciMpServicesDxe/MpFuncs.S b/ArmPkg/Drivers/ArmPsciMpServicesDxe/MpFuncs.S
new file mode 100644 (file)
index 0000000..f73edc1
--- /dev/null
@@ -0,0 +1,74 @@
+#===============================================================================\r
+#  Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.\r
+#\r
+#  SPDX-License-Identifier: BSD-2-Clause-Patent\r
+#===============================================================================\r
+\r
+.text\r
+.align 3\r
+\r
+#include <AsmMacroIoLibV8.h>\r
+#include <IndustryStandard/ArmStdSmc.h>\r
+#include <Library/ArmLib.h>\r
+\r
+#include "MpServicesInternal.h"\r
+\r
+GCC_ASM_IMPORT (gApStacksBase)\r
+GCC_ASM_IMPORT (gProcessorIDs)\r
+GCC_ASM_IMPORT (ApProcedure)\r
+GCC_ASM_IMPORT (gApStackSize)\r
+GCC_ASM_IMPORT (gTcr)\r
+GCC_ASM_IMPORT (gTtbr0)\r
+GCC_ASM_IMPORT (gMair)\r
+\r
+GCC_ASM_EXPORT (ApEntryPoint)\r
+\r
+// Entry-point for the AP\r
+// VOID\r
+// ApEntryPoint (\r
+//   VOID\r
+//   );\r
+ASM_PFX(ApEntryPoint):\r
+  // Configure the MMU and caches\r
+  ldr x0, gTcr\r
+  bl ArmSetTCR\r
+  ldr x0, gTtbr0\r
+  bl ArmSetTTBR0\r
+  ldr x0, gMair\r
+  bl ArmSetMAIR\r
+  bl ArmDisableAlignmentCheck\r
+  bl ArmEnableStackAlignmentCheck\r
+  bl ArmEnableInstructionCache\r
+  bl ArmEnableDataCache\r
+  bl ArmEnableMmu\r
+\r
+  mrs x0, mpidr_el1\r
+  // Mask the non-affinity bits\r
+  bic x0, x0, 0x00ff000000\r
+  and x0, x0, 0xffffffffff\r
+  ldr x1, gProcessorIDs\r
+  mov x2, 0                   // x2 = processor index\r
+\r
+// Find index in gProcessorIDs for current processor\r
+1:\r
+  ldr x3, [x1, x2, lsl #3]    // x4 = gProcessorIDs + x2 * 8\r
+  cmp x3, #-1                 // check if we've reached the end of gProcessorIDs\r
+  beq ProcessorNotFound\r
+  add x2, x2, 1               // x2++\r
+  cmp x0, x3                  // if mpidr_el1 != gProcessorIDs[x] then loop\r
+  bne 1b\r
+\r
+// Calculate stack address\r
+  // x2 contains the index for the current processor plus 1\r
+  ldr x0, gApStacksBase\r
+  ldr x1, gApStackSize\r
+  mul x3, x2, x1              // x3 = (ProcessorIndex + 1) * gApStackSize\r
+  add sp, x0, x3              // sp = gApStacksBase + x3\r
+  mov x29, xzr\r
+  bl ApProcedure              // doesn't return\r
+\r
+ProcessorNotFound:\r
+// Turn off the processor\r
+  MOV32 (w0, ARM_SMC_ID_PSCI_CPU_OFF)\r
+  smc #0\r
+  b .\r
diff --git a/ArmPkg/Drivers/ArmPsciMpServicesDxe/MpServicesInternal.h b/ArmPkg/Drivers/ArmPsciMpServicesDxe/MpServicesInternal.h
new file mode 100644 (file)
index 0000000..a0c203f
--- /dev/null
@@ -0,0 +1,345 @@
+/** @file\r
+\r
+Copyright (c) 2022, Qualcomm Innovation Center, Inc. All rights reserved.<BR>\r
+Copyright (c) 2006 - 2011, Intel Corporation. All rights reserved.<BR>\r
+Portions copyright (c) 2011, Apple Inc. All rights reserved.\r
+\r
+SPDX-License-Identifier: BSD-2-Clause-Patent\r
+\r
+**/\r
+\r
+#ifndef MP_SERVICES_INTERNAL_H_\r
+#define MP_SERVICES_INTERNAL_H_\r
+\r
+#include <Protocol/Cpu.h>\r
+#include <Protocol/MpService.h>\r
+\r
+#include <Library/BaseLib.h>\r
+#include <Library/UefiLib.h>\r
+\r
+#define AP_STACK_SIZE  0x1000\r
+\r
+//\r
+// Internal Data Structures\r
+//\r
+\r
+//\r
+// AP state\r
+//\r
+// The state transitions for an AP when it processes a procedure are:\r
+//  Idle ----> Ready ----> Busy ----> Finished ----> Idle\r
+//       [BSP]       [BSP]      [AP]           [BSP]\r
+//\r
+typedef enum {\r
+  CpuStateIdle,\r
+  CpuStateReady,\r
+  CpuStateBlocked,\r
+  CpuStateBusy,\r
+  CpuStateFinished,\r
+  CpuStateDisabled\r
+} CPU_STATE;\r
+\r
+//\r
+// Define Individual Processor Data block.\r
+//\r
+typedef struct {\r
+  EFI_PROCESSOR_INFORMATION    Info;\r
+  EFI_AP_PROCEDURE             Procedure;\r
+  VOID                         *Parameter;\r
+  CPU_STATE                    State;\r
+  EFI_EVENT                    CheckThisAPEvent;\r
+  EFI_EVENT                    WaitEvent;\r
+  UINTN                        Timeout;\r
+  UINTN                        TimeTaken;\r
+  BOOLEAN                      TimeoutActive;\r
+  BOOLEAN                      *SingleApFinished;\r
+} CPU_AP_DATA;\r
+\r
+//\r
+// Define MP data block which consumes individual processor block.\r
+//\r
+typedef struct {\r
+  UINTN               NumberOfProcessors;\r
+  UINTN               NumberOfEnabledProcessors;\r
+  EFI_EVENT           CheckAllAPsEvent;\r
+  EFI_EVENT           AllWaitEvent;\r
+  UINTN               FinishCount;\r
+  UINTN               StartCount;\r
+  EFI_AP_PROCEDURE    Procedure;\r
+  VOID                *ProcedureArgument;\r
+  BOOLEAN             SingleThread;\r
+  UINTN               StartedNumber;\r
+  CPU_AP_DATA         *CpuData;\r
+  UINTN               *FailedList;\r
+  UINTN               FailedListIndex;\r
+  UINTN               AllTimeout;\r
+  UINTN               AllTimeTaken;\r
+  BOOLEAN             AllTimeoutActive;\r
+} CPU_MP_DATA;\r
+\r
+/** Secondary core entry point.\r
+\r
+**/\r
+VOID\r
+ApEntryPoint (\r
+  VOID\r
+  );\r
+\r
+/** C entry-point for the AP.\r
+    This function gets called from the assembly function ApEntryPoint.\r
+**/\r
+VOID\r
+ApProcedure (\r
+  VOID\r
+  );\r
+\r
+/** Turns on the specified core using PSCI and executes the user-supplied\r
+    function that's been configured via a previous call to SetApProcedure.\r
+\r
+   @param ProcessorIndex The index of the core to turn on.\r
+\r
+   @retval EFI_SUCCESS       The processor was successfully turned on.\r
+   @retval EFI_DEVICE_ERROR  An error occurred turning the processor on.\r
+\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+EFIAPI\r
+DispatchCpu (\r
+  IN UINTN  ProcessorIndex\r
+  );\r
+\r
+/** Returns whether the specified processor is the BSP.\r
+\r
+   @param[in] ProcessorIndex The index the processor to check.\r
+\r
+   @return TRUE if the processor is the BSP, FALSE otherwise.\r
+**/\r
+STATIC\r
+BOOLEAN\r
+IsProcessorBSP (\r
+  UINTN  ProcessorIndex\r
+  );\r
+\r
+/** Returns whether the processor executing this function is the BSP.\r
+\r
+   @return Whether the current processor is the BSP.\r
+**/\r
+STATIC\r
+BOOLEAN\r
+IsCurrentProcessorBSP (\r
+  VOID\r
+  );\r
+\r
+/** Returns whether the specified processor is enabled.\r
+\r
+   @param[in] ProcessorIndex The index of the processor to check.\r
+\r
+   @return TRUE if the processor is enabled, FALSE otherwise.\r
+**/\r
+STATIC\r
+BOOLEAN\r
+IsProcessorEnabled (\r
+  UINTN  ProcessorIndex\r
+  );\r
+\r
+/** Configures the processor context with the user-supplied procedure and\r
+    argument.\r
+\r
+   @param CpuData           The processor context.\r
+   @param Procedure         The user-supplied procedure.\r
+   @param ProcedureArgument The user-supplied procedure argument.\r
+\r
+**/\r
+STATIC\r
+VOID\r
+SetApProcedure (\r
+  IN   CPU_AP_DATA       *CpuData,\r
+  IN   EFI_AP_PROCEDURE  Procedure,\r
+  IN   VOID              *ProcedureArgument\r
+  );\r
+\r
+/**\r
+  Get the Application Processors state.\r
+\r
+  @param[in]  CpuData    The pointer to CPU_AP_DATA of specified AP\r
+\r
+  @return  The AP status\r
+**/\r
+CPU_STATE\r
+GetApState (\r
+  IN  CPU_AP_DATA  *CpuData\r
+  );\r
+\r
+/** Returns the index of the next processor that is blocked.\r
+\r
+   @param[out] NextNumber The index of the next blocked processor.\r
+\r
+   @retval EFI_SUCCESS   Successfully found the next blocked processor.\r
+   @retval EFI_NOT_FOUND There are no blocked processors.\r
+\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+GetNextBlockedNumber (\r
+  OUT UINTN  *NextNumber\r
+  );\r
+\r
+/** Stalls the BSP for the minimum of gPollInterval and Timeout.\r
+\r
+   @param[in]  Timeout    The time limit in microseconds remaining for\r
+                          APs to return from Procedure.\r
+\r
+   @retval     StallTime  Time of execution stall.\r
+**/\r
+STATIC\r
+UINTN\r
+CalculateAndStallInterval (\r
+  IN UINTN  Timeout\r
+  );\r
+\r
+/** Sets up the state for the StartupAllAPs function.\r
+\r
+   @param SingleThread Whether the APs will execute sequentially.\r
+\r
+**/\r
+STATIC\r
+VOID\r
+StartupAllAPsPrepareState (\r
+  IN BOOLEAN  SingleThread\r
+  );\r
+\r
+/** Handles execution of StartupAllAPs when a WaitEvent has been specified.\r
+\r
+  @param Procedure         The user-supplied procedure.\r
+  @param ProcedureArgument The user-supplied procedure argument.\r
+  @param WaitEvent         The wait event to be signaled when the work is\r
+                           complete or a timeout has occurred.\r
+  @param TimeoutInMicroseconds The timeout for the work to be completed. Zero\r
+                               indicates an infinite timeout.\r
+  @param SingleThread          Whether the APs will execute sequentially.\r
+  @param FailedCpuList         User-supplied pointer for list of failed CPUs.\r
+\r
+   @return EFI_SUCCESS on success.\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+StartupAllAPsWithWaitEvent (\r
+  IN EFI_AP_PROCEDURE  Procedure,\r
+  IN VOID              *ProcedureArgument,\r
+  IN EFI_EVENT         WaitEvent,\r
+  IN UINTN             TimeoutInMicroseconds,\r
+  IN BOOLEAN           SingleThread,\r
+  IN UINTN             **FailedCpuList\r
+  );\r
+\r
+/** Handles execution of StartupAllAPs when no wait event has been specified.\r
+\r
+   @param Procedure             The user-supplied procedure.\r
+   @param ProcedureArgument     The user-supplied procedure argument.\r
+   @param TimeoutInMicroseconds The timeout for the work to be completed. Zero\r
+                                indicates an infinite timeout.\r
+   @param SingleThread          Whether the APs will execute sequentially.\r
+   @param FailedCpuList         User-supplied pointer for list of failed CPUs.\r
+\r
+   @return EFI_SUCCESS on success.\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+StartupAllAPsNoWaitEvent (\r
+  IN EFI_AP_PROCEDURE  Procedure,\r
+  IN VOID              *ProcedureArgument,\r
+  IN UINTN             TimeoutInMicroseconds,\r
+  IN BOOLEAN           SingleThread,\r
+  IN UINTN             **FailedCpuList\r
+  );\r
+\r
+/** Adds the specified processor the list of failed processors.\r
+\r
+   @param ProcessorIndex The processor index to add.\r
+   @param ApState         Processor state.\r
+\r
+**/\r
+STATIC\r
+VOID\r
+AddProcessorToFailedList (\r
+  UINTN      ProcessorIndex,\r
+  CPU_STATE  ApState\r
+  );\r
+\r
+/** Handles the StartupAllAPs case where the timeout has occurred.\r
+\r
+**/\r
+STATIC\r
+VOID\r
+ProcessStartupAllAPsTimeout (\r
+  VOID\r
+  );\r
+\r
+/**\r
+  If a timeout is specified in StartupAllAps(), a timer is set, which invokes\r
+  this procedure periodically to check whether all APs have finished.\r
+\r
+  @param[in] Event   The WaitEvent the user supplied.\r
+  @param[in] Context The event context.\r
+**/\r
+STATIC\r
+VOID\r
+EFIAPI\r
+CheckAllAPsStatus (\r
+  IN  EFI_EVENT  Event,\r
+  IN  VOID       *Context\r
+  );\r
+\r
+/** Invoked periodically via a timer to check the state of the processor.\r
+\r
+   @param Event   The event supplied by the timer expiration.\r
+   @param Context The processor context.\r
+\r
+**/\r
+STATIC\r
+VOID\r
+EFIAPI\r
+CheckThisAPStatus (\r
+  IN  EFI_EVENT  Event,\r
+  IN  VOID       *Context\r
+  );\r
+\r
+/**\r
+  This function is called by all processors (both BSP and AP) once and collects\r
+  MP related data.\r
+\r
+  @param BSP            TRUE if the processor is the BSP.\r
+  @param Mpidr          The MPIDR for the specified processor. This should be\r
+                        the full MPIDR and not only the affinity bits.\r
+  @param ProcessorIndex The index of the processor.\r
+\r
+  @return EFI_SUCCESS if the data for the processor collected and filled in.\r
+\r
+**/\r
+STATIC\r
+EFI_STATUS\r
+FillInProcessorInformation (\r
+  IN BOOLEAN  BSP,\r
+  IN UINTN    Mpidr,\r
+  IN UINTN    ProcessorIndex\r
+  );\r
+\r
+/**\r
+  Event notification function called when the EFI_EVENT_GROUP_READY_TO_BOOT is\r
+  signaled. After this point, non-blocking mode is no longer allowed.\r
+\r
+  @param  Event     Event whose notification function is being invoked.\r
+  @param  Context   The pointer to the notification function's context,\r
+                    which is implementation-dependent.\r
+\r
+**/\r
+STATIC\r
+VOID\r
+EFIAPI\r
+ReadyToBootSignaled (\r
+  IN  EFI_EVENT  Event,\r
+  IN  VOID       *Context\r
+  );\r
+\r
+#endif /* MP_SERVICES_INTERNAL_H_ */\r