Currently, the GIC driver has a static dependency on the CPU arch protocol driver, so it can register its IRQ handler at init time. This means there is a window between dispatch of the CPU driver and dispatch of the GIC driver where any unexpected GIC state may trigger an interrupt which we are not set up to handle yet. Note that this is even the case if we enter UEFI with interrupts disabled at the CPU, given that any TPL manipulation involving TPL_HIGH_LEVEL will unconditionally enable IRQs at the CPU side regardless of whether they were enabled to begin with (but only as soon as the CPU arch protocol is actually installed) So let's reorder the GIC driver with the CPU driver, and let it run its initialization that puts the GIC into a known state before enabling interrupts. Move its installation of its IRQ handler to a protocol notify callback on the CPU arch protocol so that it runs as soon as it becomes available. Contributed-under: TianoCore Contribution Agreement 1.1 Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org> Reviewed-by: Leif Lindholm <leif.lindholm@linaro.org> Tested-by: Marc Zyngier <marc.zyngier@arm.com>
213 lines
6.0 KiB
C
213 lines
6.0 KiB
C
/*++
|
|
|
|
Copyright (c) 2013-2017, ARM Ltd. All rights reserved.<BR>
|
|
|
|
This program and the accompanying materials
|
|
are licensed and made available under the terms and conditions of the BSD License
|
|
which accompanies this distribution. The full text of the license may be found at
|
|
http://opensource.org/licenses/bsd-license.php
|
|
|
|
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
|
|
|
--*/
|
|
|
|
#include "ArmGicDxe.h"
|
|
|
|
VOID
|
|
EFIAPI
|
|
IrqInterruptHandler (
|
|
IN EFI_EXCEPTION_TYPE InterruptType,
|
|
IN EFI_SYSTEM_CONTEXT SystemContext
|
|
);
|
|
|
|
VOID
|
|
EFIAPI
|
|
ExitBootServicesEvent (
|
|
IN EFI_EVENT Event,
|
|
IN VOID *Context
|
|
);
|
|
|
|
// Making this global saves a few bytes in image size
|
|
EFI_HANDLE gHardwareInterruptHandle = NULL;
|
|
|
|
// Notifications
|
|
EFI_EVENT EfiExitBootServicesEvent = (EFI_EVENT)NULL;
|
|
|
|
// Maximum Number of Interrupts
|
|
UINTN mGicNumInterrupts = 0;
|
|
|
|
HARDWARE_INTERRUPT_HANDLER *gRegisteredInterruptHandlers = NULL;
|
|
|
|
|
|
/**
|
|
Calculate GICD_ICFGRn base address and corresponding bit
|
|
field Int_config[1] of the GIC distributor register.
|
|
|
|
@param Source Hardware source of the interrupt.
|
|
@param RegAddress Corresponding GICD_ICFGRn base address.
|
|
@param Config1Bit Bit number of F Int_config[1] bit in the register.
|
|
|
|
@retval EFI_SUCCESS Source interrupt supported.
|
|
@retval EFI_UNSUPPORTED Source interrupt is not supported.
|
|
**/
|
|
EFI_STATUS
|
|
GicGetDistributorIcfgBaseAndBit (
|
|
IN HARDWARE_INTERRUPT_SOURCE Source,
|
|
OUT UINTN *RegAddress,
|
|
OUT UINTN *Config1Bit
|
|
)
|
|
{
|
|
UINTN RegIndex;
|
|
UINTN Field;
|
|
|
|
if (Source >= mGicNumInterrupts) {
|
|
ASSERT(Source < mGicNumInterrupts);
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
RegIndex = Source / ARM_GIC_ICDICFR_F_STRIDE; // NOTE: truncation is significant
|
|
Field = Source % ARM_GIC_ICDICFR_F_STRIDE;
|
|
*RegAddress = PcdGet64 (PcdGicDistributorBase)
|
|
+ ARM_GIC_ICDICFR
|
|
+ (ARM_GIC_ICDICFR_BYTES * RegIndex);
|
|
*Config1Bit = ((Field * ARM_GIC_ICDICFR_F_WIDTH)
|
|
+ ARM_GIC_ICDICFR_F_CONFIG1_BIT);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
Register Handler for the specified interrupt source.
|
|
|
|
@param This Instance pointer for this protocol
|
|
@param Source Hardware source of the interrupt
|
|
@param Handler Callback for interrupt. NULL to unregister
|
|
|
|
@retval EFI_SUCCESS Source was updated to support Handler.
|
|
@retval EFI_DEVICE_ERROR Hardware could not be programmed.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
RegisterInterruptSource (
|
|
IN EFI_HARDWARE_INTERRUPT_PROTOCOL *This,
|
|
IN HARDWARE_INTERRUPT_SOURCE Source,
|
|
IN HARDWARE_INTERRUPT_HANDLER Handler
|
|
)
|
|
{
|
|
if (Source >= mGicNumInterrupts) {
|
|
ASSERT(FALSE);
|
|
return EFI_UNSUPPORTED;
|
|
}
|
|
|
|
if ((Handler == NULL) && (gRegisteredInterruptHandlers[Source] == NULL)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
if ((Handler != NULL) && (gRegisteredInterruptHandlers[Source] != NULL)) {
|
|
return EFI_ALREADY_STARTED;
|
|
}
|
|
|
|
gRegisteredInterruptHandlers[Source] = Handler;
|
|
|
|
// If the interrupt handler is unregistered then disable the interrupt
|
|
if (NULL == Handler){
|
|
return This->DisableInterruptSource (This, Source);
|
|
} else {
|
|
return This->EnableInterruptSource (This, Source);
|
|
}
|
|
}
|
|
|
|
STATIC VOID *mCpuArchProtocolNotifyEventRegistration;
|
|
|
|
STATIC
|
|
VOID
|
|
EFIAPI
|
|
CpuArchEventProtocolNotify (
|
|
IN EFI_EVENT Event,
|
|
IN VOID *Context
|
|
)
|
|
{
|
|
EFI_CPU_ARCH_PROTOCOL *Cpu;
|
|
EFI_STATUS Status;
|
|
|
|
// Get the CPU protocol that this driver requires.
|
|
Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **)&Cpu);
|
|
if (EFI_ERROR (Status)) {
|
|
return;
|
|
}
|
|
|
|
// Unregister the default exception handler.
|
|
Status = Cpu->RegisterInterruptHandler (Cpu, ARM_ARCH_EXCEPTION_IRQ, NULL);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_ERROR, "%a: Cpu->RegisterInterruptHandler() - %r\n",
|
|
__FUNCTION__, Status));
|
|
return;
|
|
}
|
|
|
|
// Register to receive interrupts
|
|
Status = Cpu->RegisterInterruptHandler (Cpu, ARM_ARCH_EXCEPTION_IRQ,
|
|
Context);
|
|
if (EFI_ERROR (Status)) {
|
|
DEBUG ((DEBUG_ERROR, "%a: Cpu->RegisterInterruptHandler() - %r\n",
|
|
__FUNCTION__, Status));
|
|
}
|
|
|
|
gBS->CloseEvent (Event);
|
|
}
|
|
|
|
EFI_STATUS
|
|
InstallAndRegisterInterruptService (
|
|
IN EFI_HARDWARE_INTERRUPT_PROTOCOL *InterruptProtocol,
|
|
IN EFI_HARDWARE_INTERRUPT2_PROTOCOL *Interrupt2Protocol,
|
|
IN EFI_CPU_INTERRUPT_HANDLER InterruptHandler,
|
|
IN EFI_EVENT_NOTIFY ExitBootServicesEvent
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
CONST UINTN RihArraySize =
|
|
(sizeof(HARDWARE_INTERRUPT_HANDLER) * mGicNumInterrupts);
|
|
|
|
// Initialize the array for the Interrupt Handlers
|
|
gRegisteredInterruptHandlers = AllocateZeroPool (RihArraySize);
|
|
if (gRegisteredInterruptHandlers == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
Status = gBS->InstallMultipleProtocolInterfaces (
|
|
&gHardwareInterruptHandle,
|
|
&gHardwareInterruptProtocolGuid,
|
|
InterruptProtocol,
|
|
&gHardwareInterrupt2ProtocolGuid,
|
|
Interrupt2Protocol,
|
|
NULL
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// Install the interrupt handler as soon as the CPU arch protocol appears.
|
|
//
|
|
EfiCreateProtocolNotifyEvent (
|
|
&gEfiCpuArchProtocolGuid,
|
|
TPL_CALLBACK,
|
|
CpuArchEventProtocolNotify,
|
|
InterruptHandler,
|
|
&mCpuArchProtocolNotifyEventRegistration);
|
|
|
|
// Register for an ExitBootServicesEvent
|
|
Status = gBS->CreateEvent (
|
|
EVT_SIGNAL_EXIT_BOOT_SERVICES,
|
|
TPL_NOTIFY,
|
|
ExitBootServicesEvent,
|
|
NULL,
|
|
&EfiExitBootServicesEvent
|
|
);
|
|
|
|
return Status;
|
|
}
|