|
@@ -0,0 +1,216 @@
|
|
|
+/** @file
|
|
|
+ Handle raising and lowering TPL from within nested interrupt handlers.
|
|
|
+
|
|
|
+ Allows interrupt handlers to safely raise and lower the TPL to
|
|
|
+ dispatch event notifications, correctly allowing for nested
|
|
|
+ interrupts to occur without risking stack exhaustion.
|
|
|
+
|
|
|
+ Copyright (C) 2022, Fen Systems Ltd.
|
|
|
+
|
|
|
+ SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
|
+**/
|
|
|
+
|
|
|
+#include <Library/BaseLib.h>
|
|
|
+#include <Library/DebugLib.h>
|
|
|
+#include <Library/NestedInterruptTplLib.h>
|
|
|
+#include <Library/UefiBootServicesTableLib.h>
|
|
|
+
|
|
|
+#include "Iret.h"
|
|
|
+
|
|
|
+/**
|
|
|
+ Raise the task priority level to TPL_HIGH_LEVEL.
|
|
|
+
|
|
|
+ @param None.
|
|
|
+
|
|
|
+ @return The task priority level at which the interrupt occurred.
|
|
|
+**/
|
|
|
+EFI_TPL
|
|
|
+EFIAPI
|
|
|
+NestedInterruptRaiseTPL (
|
|
|
+ VOID
|
|
|
+ )
|
|
|
+{
|
|
|
+ EFI_TPL InterruptedTPL;
|
|
|
+
|
|
|
+ //
|
|
|
+ // Raise TPL and assert that we were called from within an interrupt
|
|
|
+ // handler (i.e. with TPL below TPL_HIGH_LEVEL but with interrupts
|
|
|
+ // disabled).
|
|
|
+ //
|
|
|
+ ASSERT (GetInterruptState () == FALSE);
|
|
|
+ InterruptedTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL);
|
|
|
+ ASSERT (InterruptedTPL < TPL_HIGH_LEVEL);
|
|
|
+
|
|
|
+ return InterruptedTPL;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ Lower the task priority back to the value at which the interrupt
|
|
|
+ occurred.
|
|
|
+
|
|
|
+ This is unfortunately messy. UEFI requires us to support nested
|
|
|
+ interrupts, but provides no way for an interrupt handler to call
|
|
|
+ RestoreTPL() without implicitly re-enabling interrupts. In a
|
|
|
+ virtual machine, it is possible for a large burst of interrupts to
|
|
|
+ arrive. We must prevent such a burst from leading to stack
|
|
|
+ exhaustion, while continuing to allow nested interrupts to occur.
|
|
|
+
|
|
|
+ Since nested interrupts are permitted, an interrupt handler may be
|
|
|
+ invoked as an inner interrupt handler while an outer instance of the
|
|
|
+ same interrupt handler is still inside its call to RestoreTPL().
|
|
|
+
|
|
|
+ To avoid stack exhaustion, this call may therefore (when provably
|
|
|
+ safe to do so) defer the actual TPL lowering to be performed by an
|
|
|
+ outer instance of the same interrupt handler.
|
|
|
+
|
|
|
+ @param InterruptedTPL The task priority level at which the interrupt
|
|
|
+ occurred, as previously returned from
|
|
|
+ NestedInterruptRaiseTPL().
|
|
|
+
|
|
|
+ @param SystemContext A pointer to the system context when the
|
|
|
+ interrupt occurred.
|
|
|
+
|
|
|
+ @param IsrState A pointer to the state shared between all
|
|
|
+ invocations of the nested interrupt handler.
|
|
|
+**/
|
|
|
+VOID
|
|
|
+EFIAPI
|
|
|
+NestedInterruptRestoreTPL (
|
|
|
+ IN EFI_TPL InterruptedTPL,
|
|
|
+ IN OUT EFI_SYSTEM_CONTEXT SystemContext,
|
|
|
+ IN OUT NESTED_INTERRUPT_STATE *IsrState
|
|
|
+ )
|
|
|
+{
|
|
|
+ EFI_TPL SavedInProgressRestoreTPL;
|
|
|
+ BOOLEAN DeferredRestoreTPL;
|
|
|
+
|
|
|
+ //
|
|
|
+ // If the TPL at which this interrupt occurred is equal to that of
|
|
|
+ // the in-progress RestoreTPL() for an outer instance of the same
|
|
|
+ // interrupt handler, then that outer handler's call to RestoreTPL()
|
|
|
+ // must have finished dispatching all event notifications. This
|
|
|
+ // interrupt must therefore have occurred at the point that the
|
|
|
+ // outer handler's call to RestoreTPL() had finished and was about
|
|
|
+ // to return to the outer handler.
|
|
|
+ //
|
|
|
+ // If we were to call RestoreTPL() at this point, then we would open
|
|
|
+ // up the possibility for unlimited stack consumption in the event
|
|
|
+ // of an interrupt storm. We therefore cannot safely call
|
|
|
+ // RestoreTPL() from within this stack frame (i.e. from within this
|
|
|
+ // instance of the interrupt handler).
|
|
|
+ //
|
|
|
+ // Instead, we arrange to return from this interrupt with the TPL
|
|
|
+ // still at TPL_HIGH_LEVEL and with interrupts disabled, and to
|
|
|
+ // defer our call to RestoreTPL() to the in-progress outer instance
|
|
|
+ // of the same interrupt handler.
|
|
|
+ //
|
|
|
+ if (InterruptedTPL == IsrState->InProgressRestoreTPL) {
|
|
|
+ //
|
|
|
+ // Trigger outer instance of this interrupt handler to perform the
|
|
|
+ // RestoreTPL() call that we cannot issue at this point without
|
|
|
+ // risking stack exhaustion.
|
|
|
+ //
|
|
|
+ ASSERT (IsrState->DeferredRestoreTPL == FALSE);
|
|
|
+ IsrState->DeferredRestoreTPL = TRUE;
|
|
|
+
|
|
|
+ //
|
|
|
+ // DEFERRAL INVOCATION POINT
|
|
|
+ //
|
|
|
+ // Return from this interrupt handler with interrupts still
|
|
|
+ // disabled (by clearing the "interrupts-enabled" bit in the CPU
|
|
|
+ // flags that will be restored by the IRET or equivalent
|
|
|
+ // instruction).
|
|
|
+ //
|
|
|
+ // This ensures that no further interrupts may occur before
|
|
|
+ // control reaches the outer interrupt handler's RestoreTPL() loop
|
|
|
+ // at the point marked "DEFERRAL RETURN POINT" (see below).
|
|
|
+ //
|
|
|
+ DisableInterruptsOnIret (SystemContext);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ //
|
|
|
+ // If the TPL at which this interrupt occurred is higher than that
|
|
|
+ // of the in-progress RestoreTPL() for an outer instance of the same
|
|
|
+ // interrupt handler, then that outer handler's call to RestoreTPL()
|
|
|
+ // must still be dispatching event notifications.
|
|
|
+ //
|
|
|
+ // We must therefore call RestoreTPL() at this point to allow more
|
|
|
+ // event notifications to be dispatched, since those event
|
|
|
+ // notification callback functions may themselves be waiting upon
|
|
|
+ // other events.
|
|
|
+ //
|
|
|
+ // We cannot avoid creating a new stack frame for this call to
|
|
|
+ // RestoreTPL(), but the total number of such stack frames is
|
|
|
+ // intrinsically limited by the number of distinct TPLs.
|
|
|
+ //
|
|
|
+ // We may need to issue the call to RestoreTPL() more than once, if
|
|
|
+ // an inner instance of the same interrupt handler needs to defer
|
|
|
+ // its RestoreTPL() call to be performed from within this stack
|
|
|
+ // frame (see above).
|
|
|
+ //
|
|
|
+ while (TRUE) {
|
|
|
+ //
|
|
|
+ // Check shared state loop invariants.
|
|
|
+ //
|
|
|
+ ASSERT (IsrState->InProgressRestoreTPL < InterruptedTPL);
|
|
|
+ ASSERT (IsrState->DeferredRestoreTPL == FALSE);
|
|
|
+
|
|
|
+ //
|
|
|
+ // Record the in-progress RestoreTPL() value in the shared state
|
|
|
+ // where it will be visible to an inner instance of the same
|
|
|
+ // interrupt handler, in case a nested interrupt occurs during our
|
|
|
+ // call to RestoreTPL().
|
|
|
+ //
|
|
|
+ SavedInProgressRestoreTPL = IsrState->InProgressRestoreTPL;
|
|
|
+ IsrState->InProgressRestoreTPL = InterruptedTPL;
|
|
|
+
|
|
|
+ //
|
|
|
+ // Call RestoreTPL() to allow event notifications to be
|
|
|
+ // dispatched. This will implicitly re-enable interrupts.
|
|
|
+ //
|
|
|
+ gBS->RestoreTPL (InterruptedTPL);
|
|
|
+
|
|
|
+ //
|
|
|
+ // Re-disable interrupts after the call to RestoreTPL() to ensure
|
|
|
+ // that we have exclusive access to the shared state.
|
|
|
+ //
|
|
|
+ DisableInterrupts ();
|
|
|
+
|
|
|
+ //
|
|
|
+ // DEFERRAL RETURN POINT
|
|
|
+ //
|
|
|
+ // An inner instance of the same interrupt handler may have chosen
|
|
|
+ // to defer its RestoreTPL() call to be performed from within this
|
|
|
+ // stack frame. If so, it is guaranteed that no further event
|
|
|
+ // notifications or interrupts have been processed between the
|
|
|
+ // DEFERRAL INVOCATION POINT (see above) and this DEFERRAL RETURN
|
|
|
+ // POINT.
|
|
|
+ //
|
|
|
+
|
|
|
+ //
|
|
|
+ // Restore the locally saved in-progress RestoreTPL() value in the
|
|
|
+ // shared state, now that our call to RestoreTPL() has returned
|
|
|
+ // and is therefore no longer in progress.
|
|
|
+ //
|
|
|
+ ASSERT (IsrState->InProgressRestoreTPL == InterruptedTPL);
|
|
|
+ IsrState->InProgressRestoreTPL = SavedInProgressRestoreTPL;
|
|
|
+
|
|
|
+ //
|
|
|
+ // Check (and clear) the shared state to see if an inner instance
|
|
|
+ // of the same interrupt handler deferred its call to
|
|
|
+ // RestoreTPL().
|
|
|
+ //
|
|
|
+ DeferredRestoreTPL = IsrState->DeferredRestoreTPL;
|
|
|
+ IsrState->DeferredRestoreTPL = FALSE;
|
|
|
+
|
|
|
+ //
|
|
|
+ // If no inner interrupt handler deferred its call to
|
|
|
+ // RestoreTPL(), then the TPL has been successfully restored and
|
|
|
+ // we may return from the interrupt handler.
|
|
|
+ //
|
|
|
+ if (DeferredRestoreTPL == FALSE) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|