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