/** @file * * Copyright (c) 2016, Linaro, Ltd. All rights reserved. * * SPDX-License-Identifier: BSD-2-Clause-Patent * **/ #include #include #include #include #include #include #include #include #include // // This currently only implements support for the architected timer interrupts // on the per-CPU interrupt controllers. // #define NUM_IRQS (4) #ifdef MDE_CPU_AARCH64 #define ARM_ARCH_EXCEPTION_IRQ EXCEPT_AARCH64_IRQ #else #define ARM_ARCH_EXCEPTION_IRQ EXCEPT_ARM_IRQ #endif STATIC CONST EFI_PHYSICAL_ADDRESS RegBase = FixedPcdGet32 (PcdInterruptBaseAddress); // // Notifications // STATIC EFI_EVENT mExitBootServicesEvent; STATIC HARDWARE_INTERRUPT_HANDLER mRegisteredInterruptHandlers[NUM_IRQS]; /** 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 **/ STATIC VOID EFIAPI ExitBootServicesEvent ( IN EFI_EVENT Event, IN VOID *Context ) { // Disable all interrupts MmioWrite32 (RegBase + BCM2836_INTC_TIMER_CONTROL_OFFSET, 0); } /** 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. **/ STATIC EFI_STATUS EFIAPI EnableInterruptSource ( IN EFI_HARDWARE_INTERRUPT_PROTOCOL *This, IN HARDWARE_INTERRUPT_SOURCE Source ) { if (Source >= NUM_IRQS) { ASSERT (FALSE); return EFI_UNSUPPORTED; } MmioOr32 (RegBase + BCM2836_INTC_TIMER_CONTROL_OFFSET, 1 << Source); 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. **/ STATIC EFI_STATUS EFIAPI DisableInterruptSource ( IN EFI_HARDWARE_INTERRUPT_PROTOCOL *This, IN HARDWARE_INTERRUPT_SOURCE Source ) { if (Source >= NUM_IRQS) { ASSERT (FALSE); return EFI_UNSUPPORTED; } MmioAnd32 (RegBase + BCM2836_INTC_TIMER_CONTROL_OFFSET, ~(1 << Source)); 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. **/ STATIC EFI_STATUS EFIAPI RegisterInterruptSource ( IN EFI_HARDWARE_INTERRUPT_PROTOCOL *This, IN HARDWARE_INTERRUPT_SOURCE Source, IN HARDWARE_INTERRUPT_HANDLER Handler ) { if (Source >= NUM_IRQS) { ASSERT (FALSE); return EFI_UNSUPPORTED; } if (Handler == NULL && mRegisteredInterruptHandlers[Source] == NULL) { return EFI_INVALID_PARAMETER; } if (Handler != NULL && mRegisteredInterruptHandlers[Source] != NULL) { return EFI_ALREADY_STARTED; } mRegisteredInterruptHandlers[Source] = Handler; return EnableInterruptSource (This, Source); } /** 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 **/ STATIC EFI_STATUS EFIAPI GetInterruptSourceState ( IN EFI_HARDWARE_INTERRUPT_PROTOCOL *This, IN HARDWARE_INTERRUPT_SOURCE Source, IN BOOLEAN *InterruptState ) { if (InterruptState == NULL) { return EFI_INVALID_PARAMETER; } if (Source >= NUM_IRQS) { ASSERT (FALSE); return EFI_UNSUPPORTED; } *InterruptState = (MmioRead32 (RegBase + BCM2836_INTC_TIMER_CONTROL_OFFSET) & (1 << Source)) != 0; 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. **/ STATIC EFI_STATUS EFIAPI EndOfInterrupt ( IN EFI_HARDWARE_INTERRUPT_PROTOCOL *This, IN HARDWARE_INTERRUPT_SOURCE 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 **/ STATIC VOID EFIAPI IrqInterruptHandler ( IN EFI_EXCEPTION_TYPE InterruptType, IN EFI_SYSTEM_CONTEXT SystemContext ) { HARDWARE_INTERRUPT_HANDLER InterruptHandler; HARDWARE_INTERRUPT_SOURCE Source; UINT32 RegVal; RegVal = MmioRead32 (RegBase + BCM2836_INTC_TIMER_PENDING_OFFSET) & ((1 << NUM_IRQS) - 1); Source = HighBitSet32 (RegVal); if (Source < 0) { return; } InterruptHandler = mRegisteredInterruptHandlers[Source]; if (InterruptHandler != NULL) { // Call the registered interrupt handler. InterruptHandler (Source, SystemContext); } } // // The protocol instance produced by this driver // STATIC EFI_HARDWARE_INTERRUPT_PROTOCOL gHardwareInterruptProtocol = { RegisterInterruptSource, EnableInterruptSource, DisableInterruptSource, GetInterruptSourceState, EndOfInterrupt }; 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)); ASSERT (FALSE); return; } // // Register to receive interrupts // Status = Cpu->RegisterInterruptHandler (Cpu, ARM_ARCH_EXCEPTION_IRQ, IrqInterruptHandler); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "%a: Cpu->RegisterInterruptHandler() - %r\n", __FUNCTION__, Status)); ASSERT (FALSE); return; } } /** 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; EFI_EVENT CpuArchEvent; // Make sure the Interrupt Controller Protocol is not already installed in the system. ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gHardwareInterruptProtocolGuid); Status = gBS->InstallMultipleProtocolInterfaces ( &ImageHandle, &gHardwareInterruptProtocolGuid, &gHardwareInterruptProtocol, NULL ); ASSERT_EFI_ERROR (Status); // // Install the interrupt handler as soon as the CPU arch protocol appears. // CpuArchEvent = EfiCreateProtocolNotifyEvent ( &gEfiCpuArchProtocolGuid, TPL_CALLBACK, CpuArchEventProtocolNotify, NULL, &mCpuArchProtocolNotifyEventRegistration ); ASSERT (CpuArchEvent != NULL); // Register for an ExitBootServicesEvent Status = gBS->CreateEvent (EVT_SIGNAL_EXIT_BOOT_SERVICES, TPL_NOTIFY, ExitBootServicesEvent, NULL, &mExitBootServicesEvent); ASSERT_EFI_ERROR (Status); return Status; }