summaryrefslogtreecommitdiffstats
path: root/OvmfPkg/Library/NestedInterruptTplLib/Tpl.c
diff options
context:
space:
mode:
Diffstat (limited to 'OvmfPkg/Library/NestedInterruptTplLib/Tpl.c')
-rw-r--r--OvmfPkg/Library/NestedInterruptTplLib/Tpl.c216
1 files changed, 216 insertions, 0 deletions
diff --git a/OvmfPkg/Library/NestedInterruptTplLib/Tpl.c b/OvmfPkg/Library/NestedInterruptTplLib/Tpl.c
new file mode 100644
index 0000000000..e19d98878e
--- /dev/null
+++ b/OvmfPkg/Library/NestedInterruptTplLib/Tpl.c
@@ -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;
+ }
+ }
+}