/*++ | |
Copyright (c) 2009, Hewlett-Packard Company. All rights reserved.<BR> | |
Portions copyright (c) 2010, Apple Inc. All rights reserved.<BR> | |
Portions copyright (c) 2011-2012, ARM Ltd. All rights reserved.<BR> | |
Copyright (c) Huawei Technologies Co., Ltd. 2013. All rights reserved. | |
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. | |
Module Name: | |
Gic.c | |
Abstract: | |
Driver implementing the GIC interrupt controller protocol | |
--*/ | |
#include <PiDxe.h> | |
#include <Library/BaseLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/UefiBootServicesTableLib.h> | |
#include <Library/UefiLib.h> | |
#include <Library/PcdLib.h> | |
#include <Library/IoLib.h> | |
#include <Library/ArmGicLib.h> | |
#include <Protocol/Cpu.h> | |
#include <Protocol/HardwareInterrupt.h> | |
#define ARM_GIC_DEFAULT_PRIORITY 0x80 | |
extern EFI_HARDWARE_INTERRUPT_PROTOCOL gHardwareInterruptProtocol; | |
// | |
// Notifications | |
// | |
EFI_EVENT EfiExitBootServicesEvent = (EFI_EVENT)NULL; | |
// Maximum Number of Interrupts | |
UINTN mGicNumInterrupts = 0; | |
HARDWARE_INTERRUPT_HANDLER *gRegisteredInterruptHandlers = NULL; | |
/** | |
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); | |
} | |
} | |
/** | |
Enable interrupt source Source. | |
@param This Instance pointer for this protocol | |
@param Source Hardware source of the interrupt | |
@retval EFI_SUCCESS Source interrupt enabled. | |
@retval EFI_DEVICE_ERROR Hardware could not be programmed. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
EnableInterruptSource ( | |
IN EFI_HARDWARE_INTERRUPT_PROTOCOL *This, | |
IN HARDWARE_INTERRUPT_SOURCE Source | |
) | |
{ | |
UINT32 RegOffset; | |
UINTN RegShift; | |
if (Source > mGicNumInterrupts) { | |
ASSERT(FALSE); | |
return EFI_UNSUPPORTED; | |
} | |
// Calculate enable register offset and bit position | |
RegOffset = Source / 32; | |
RegShift = Source % 32; | |
// Write set-enable register | |
MmioWrite32 (PcdGet32(PcdGicDistributorBase) + ARM_GIC_ICDISER + (4*RegOffset), 1 << RegShift); | |
return EFI_SUCCESS; | |
} | |
/** | |
Disable interrupt source Source. | |
@param This Instance pointer for this protocol | |
@param Source Hardware source of the interrupt | |
@retval EFI_SUCCESS Source interrupt disabled. | |
@retval EFI_DEVICE_ERROR Hardware could not be programmed. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
DisableInterruptSource ( | |
IN EFI_HARDWARE_INTERRUPT_PROTOCOL *This, | |
IN HARDWARE_INTERRUPT_SOURCE Source | |
) | |
{ | |
UINT32 RegOffset; | |
UINTN RegShift; | |
if (Source > mGicNumInterrupts) { | |
ASSERT(FALSE); | |
return EFI_UNSUPPORTED; | |
} | |
// Calculate enable register offset and bit position | |
RegOffset = Source / 32; | |
RegShift = Source % 32; | |
// Write set-enable register | |
MmioWrite32 (PcdGet32(PcdGicDistributorBase) + ARM_GIC_ICDICER + (4*RegOffset), 1 << RegShift); | |
return EFI_SUCCESS; | |
} | |
/** | |
Return current state of interrupt source Source. | |
@param This Instance pointer for this protocol | |
@param Source Hardware source of the interrupt | |
@param InterruptState TRUE: source enabled, FALSE: source disabled. | |
@retval EFI_SUCCESS InterruptState is valid | |
@retval EFI_DEVICE_ERROR InterruptState is not valid | |
**/ | |
EFI_STATUS | |
EFIAPI | |
GetInterruptSourceState ( | |
IN EFI_HARDWARE_INTERRUPT_PROTOCOL *This, | |
IN HARDWARE_INTERRUPT_SOURCE Source, | |
IN BOOLEAN *InterruptState | |
) | |
{ | |
UINT32 RegOffset; | |
UINTN RegShift; | |
if (Source > mGicNumInterrupts) { | |
ASSERT(FALSE); | |
return EFI_UNSUPPORTED; | |
} | |
// calculate enable register offset and bit position | |
RegOffset = Source / 32; | |
RegShift = Source % 32; | |
if ((MmioRead32 (PcdGet32(PcdGicDistributorBase) + ARM_GIC_ICDISER + (4*RegOffset)) & (1<<RegShift)) == 0) { | |
*InterruptState = FALSE; | |
} else { | |
*InterruptState = TRUE; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Signal to the hardware that the End Of Intrrupt state | |
has been reached. | |
@param This Instance pointer for this protocol | |
@param Source Hardware source of the interrupt | |
@retval EFI_SUCCESS Source interrupt EOI'ed. | |
@retval EFI_DEVICE_ERROR Hardware could not be programmed. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
EndOfInterrupt ( | |
IN EFI_HARDWARE_INTERRUPT_PROTOCOL *This, | |
IN HARDWARE_INTERRUPT_SOURCE Source | |
) | |
{ | |
if (Source > mGicNumInterrupts) { | |
ASSERT(FALSE); | |
return EFI_UNSUPPORTED; | |
} | |
MmioWrite32 (PcdGet32(PcdGicInterruptInterfaceBase) + ARM_GIC_ICCEIOR, Source); | |
return EFI_SUCCESS; | |
} | |
/** | |
EFI_CPU_INTERRUPT_HANDLER that is called when a processor interrupt occurs. | |
@param InterruptType Defines the type of interrupt or exception that | |
occurred on the processor.This parameter is processor architecture specific. | |
@param SystemContext A pointer to the processor context when | |
the interrupt occurred on the processor. | |
@return None | |
**/ | |
VOID | |
EFIAPI | |
IrqInterruptHandler ( | |
IN EFI_EXCEPTION_TYPE InterruptType, | |
IN EFI_SYSTEM_CONTEXT SystemContext | |
) | |
{ | |
UINT32 GicInterrupt; | |
HARDWARE_INTERRUPT_HANDLER InterruptHandler; | |
GicInterrupt = MmioRead32 (PcdGet32(PcdGicInterruptInterfaceBase) + ARM_GIC_ICCIAR); | |
// Special Interrupts (ID1020-ID1023) have an Interrupt ID greater than the number of interrupt (ie: Spurious interrupt). | |
if (GicInterrupt >= mGicNumInterrupts) { | |
// The special interrupt do not need to be acknowledge | |
return; | |
} | |
InterruptHandler = gRegisteredInterruptHandlers[GicInterrupt]; | |
if (InterruptHandler != NULL) { | |
// Call the registered interrupt handler. | |
InterruptHandler (GicInterrupt, SystemContext); | |
} else { | |
DEBUG ((EFI_D_ERROR, "Spurious GIC interrupt: 0x%x\n", GicInterrupt)); | |
} | |
EndOfInterrupt (&gHardwareInterruptProtocol, GicInterrupt); | |
} | |
// | |
// Making this global saves a few bytes in image size | |
// | |
EFI_HANDLE gHardwareInterruptHandle = NULL; | |
// | |
// The protocol instance produced by this driver | |
// | |
EFI_HARDWARE_INTERRUPT_PROTOCOL gHardwareInterruptProtocol = { | |
RegisterInterruptSource, | |
EnableInterruptSource, | |
DisableInterruptSource, | |
GetInterruptSourceState, | |
EndOfInterrupt | |
}; | |
/** | |
Shutdown our hardware | |
DXE Core will disable interrupts and turn off the timer and disable interrupts | |
after all the event handlers have run. | |
@param[in] Event The Event that is being processed | |
@param[in] Context Event Context | |
**/ | |
VOID | |
EFIAPI | |
ExitBootServicesEvent ( | |
IN EFI_EVENT Event, | |
IN VOID *Context | |
) | |
{ | |
UINTN Index; | |
// Acknowledge all pending interrupts | |
for (Index = 0; Index < mGicNumInterrupts; Index++) { | |
DisableInterruptSource (&gHardwareInterruptProtocol, Index); | |
} | |
for (Index = 0; Index < mGicNumInterrupts; Index++) { | |
EndOfInterrupt (&gHardwareInterruptProtocol, Index); | |
} | |
// Disable Gic Interface | |
MmioWrite32 (PcdGet32(PcdGicInterruptInterfaceBase) + ARM_GIC_ICCICR, 0x0); | |
MmioWrite32 (PcdGet32(PcdGicInterruptInterfaceBase) + ARM_GIC_ICCPMR, 0x0); | |
// Disable Gic Distributor | |
MmioWrite32 (PcdGet32(PcdGicDistributorBase) + ARM_GIC_ICDDCR, 0x0); | |
} | |
/** | |
Initialize the state information for the CPU Architectural Protocol | |
@param ImageHandle of the loaded driver | |
@param SystemTable Pointer to the System Table | |
@retval EFI_SUCCESS Protocol registered | |
@retval EFI_OUT_OF_RESOURCES Cannot allocate protocol data structure | |
@retval EFI_DEVICE_ERROR Hardware problems | |
**/ | |
EFI_STATUS | |
InterruptDxeInitialize ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE *SystemTable | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN Index; | |
UINT32 RegOffset; | |
UINTN RegShift; | |
EFI_CPU_ARCH_PROTOCOL *Cpu; | |
UINT32 CpuTarget; | |
// Check PcdGicPrimaryCoreId has been set in case the Primary Core is not the core 0 of Cluster 0 | |
DEBUG_CODE_BEGIN(); | |
if ((PcdGet32(PcdArmPrimaryCore) != 0) && (PcdGet32 (PcdGicPrimaryCoreId) == 0)) { | |
DEBUG((EFI_D_WARN,"Warning: the PCD PcdGicPrimaryCoreId does not seem to be set up for the configuration.\n")); | |
} | |
DEBUG_CODE_END(); | |
// Make sure the Interrupt Controller Protocol is not already installed in the system. | |
ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gHardwareInterruptProtocolGuid); | |
mGicNumInterrupts = ArmGicGetMaxNumInterrupts (PcdGet32(PcdGicDistributorBase)); | |
for (Index = 0; Index < mGicNumInterrupts; Index++) { | |
DisableInterruptSource (&gHardwareInterruptProtocol, Index); | |
// Set Priority | |
RegOffset = Index / 4; | |
RegShift = (Index % 4) * 8; | |
MmioAndThenOr32 ( | |
PcdGet32(PcdGicDistributorBase) + ARM_GIC_ICDIPR + (4*RegOffset), | |
~(0xff << RegShift), | |
ARM_GIC_DEFAULT_PRIORITY << RegShift | |
); | |
} | |
// Configure interrupts for Primary Cpu | |
CpuTarget = (1 << PcdGet32 (PcdGicPrimaryCoreId)); | |
CpuTarget |= CpuTarget << 16; | |
for (Index = 0; Index < (mGicNumInterrupts / 2); Index++) { | |
MmioWrite32 (PcdGet32(PcdGicDistributorBase) + ARM_GIC_ICDIPTR + (Index*4), CpuTarget); | |
} | |
// Set binary point reg to 0x7 (no preemption) | |
MmioWrite32 (PcdGet32(PcdGicInterruptInterfaceBase) + ARM_GIC_ICCBPR, 0x7); | |
// Set priority mask reg to 0xff to allow all priorities through | |
MmioWrite32 (PcdGet32(PcdGicInterruptInterfaceBase) + ARM_GIC_ICCPMR, 0xff); | |
// Enable gic cpu interface | |
MmioWrite32 (PcdGet32(PcdGicInterruptInterfaceBase) + ARM_GIC_ICCICR, 0x1); | |
// Enable gic distributor | |
MmioWrite32 (PcdGet32(PcdGicDistributorBase) + ARM_GIC_ICDDCR, 0x1); | |
// Initialize the array for the Interrupt Handlers | |
gRegisteredInterruptHandlers = (HARDWARE_INTERRUPT_HANDLER*)AllocateZeroPool (sizeof(HARDWARE_INTERRUPT_HANDLER) * mGicNumInterrupts); | |
Status = gBS->InstallMultipleProtocolInterfaces ( | |
&gHardwareInterruptHandle, | |
&gHardwareInterruptProtocolGuid, &gHardwareInterruptProtocol, | |
NULL | |
); | |
ASSERT_EFI_ERROR (Status); | |
// | |
// Get the CPU protocol that this driver requires. | |
// | |
Status = gBS->LocateProtocol(&gEfiCpuArchProtocolGuid, NULL, (VOID **)&Cpu); | |
ASSERT_EFI_ERROR(Status); | |
// | |
// Unregister the default exception handler. | |
// | |
Status = Cpu->RegisterInterruptHandler(Cpu, EXCEPT_ARM_IRQ, NULL); | |
ASSERT_EFI_ERROR(Status); | |
// | |
// Register to receive interrupts | |
// | |
Status = Cpu->RegisterInterruptHandler(Cpu, EXCEPT_ARM_IRQ, IrqInterruptHandler); | |
ASSERT_EFI_ERROR(Status); | |
// Register for an ExitBootServicesEvent | |
Status = gBS->CreateEvent (EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_NOTIFY, ExitBootServicesEvent, NULL, &EfiExitBootServicesEvent); | |
ASSERT_EFI_ERROR (Status); | |
return Status; | |
} |