summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--OvmfPkg/Include/Library/NestedInterruptTplLib.h87
-rw-r--r--OvmfPkg/Library/NestedInterruptTplLib/Iret.c62
-rw-r--r--OvmfPkg/Library/NestedInterruptTplLib/Iret.h19
-rw-r--r--OvmfPkg/Library/NestedInterruptTplLib/NestedInterruptTplLib.inf35
-rw-r--r--OvmfPkg/Library/NestedInterruptTplLib/Tpl.c216
-rw-r--r--OvmfPkg/OvmfPkg.dec4
6 files changed, 423 insertions, 0 deletions
diff --git a/OvmfPkg/Include/Library/NestedInterruptTplLib.h b/OvmfPkg/Include/Library/NestedInterruptTplLib.h
new file mode 100644
index 0000000000..0ead6e4b34
--- /dev/null
+++ b/OvmfPkg/Include/Library/NestedInterruptTplLib.h
@@ -0,0 +1,87 @@
+/** @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
+**/
+
+#ifndef __NESTED_INTERRUPT_TPL_LIB__
+#define __NESTED_INTERRUPT_TPL_LIB__
+
+#include <Uefi/UefiBaseType.h>
+#include <Uefi/UefiSpec.h>
+#include <Protocol/DebugSupport.h>
+
+///
+/// State shared between all invocations of a nested interrupt handler.
+///
+typedef struct {
+ ///
+ /// Highest TPL that is currently the target of a call to
+ /// RestoreTPL() by an instance of this interrupt handler.
+ ///
+ EFI_TPL InProgressRestoreTPL;
+ ///
+ /// Flag used to defer a call to RestoreTPL() from an inner instance
+ /// of the interrupt handler to an outer instance of the same
+ /// interrupt handler.
+ ///
+ BOOLEAN DeferredRestoreTPL;
+} NESTED_INTERRUPT_STATE;
+
+/**
+ 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
+ );
+
+/**
+ 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
+ );
+
+#endif // __NESTED_INTERRUPT_TPL_LIB__
diff --git a/OvmfPkg/Library/NestedInterruptTplLib/Iret.c b/OvmfPkg/Library/NestedInterruptTplLib/Iret.c
new file mode 100644
index 0000000000..f6b2c51b6c
--- /dev/null
+++ b/OvmfPkg/Library/NestedInterruptTplLib/Iret.c
@@ -0,0 +1,62 @@
+/** @file
+ Force interrupt handler to return with interrupts still disabled.
+
+ Copyright (C) 2022, Fen Systems Ltd.
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
+
+#include <Library/BaseLib.h>
+#include <Library/DebugLib.h>
+
+#include "Iret.h"
+
+/**
+ Force interrupt handler to return with interrupts still disabled.
+
+ @param SystemContext A pointer to the system context when the
+ interrupt occurred.
+**/
+VOID
+DisableInterruptsOnIret (
+ IN OUT EFI_SYSTEM_CONTEXT SystemContext
+ )
+{
+ #if defined (MDE_CPU_X64)
+
+ IA32_EFLAGS32 Rflags;
+
+ //
+ // Get flags from system context.
+ //
+ Rflags.UintN = SystemContext.SystemContextX64->Rflags;
+ ASSERT (Rflags.Bits.IF);
+
+ //
+ // Clear interrupts-enabled flag.
+ //
+ Rflags.Bits.IF = 0;
+ SystemContext.SystemContextX64->Rflags = Rflags.UintN;
+
+ #elif defined (MDE_CPU_IA32)
+
+ IA32_EFLAGS32 Eflags;
+
+ //
+ // Get flags from system context.
+ //
+ Eflags.UintN = SystemContext.SystemContextIa32->Eflags;
+ ASSERT (Eflags.Bits.IF);
+
+ //
+ // Clear interrupts-enabled flag.
+ //
+ Eflags.Bits.IF = 0;
+ SystemContext.SystemContextIa32->Eflags = Eflags.UintN;
+
+ #else
+
+ #error "Unsupported CPU"
+
+ #endif
+}
diff --git a/OvmfPkg/Library/NestedInterruptTplLib/Iret.h b/OvmfPkg/Library/NestedInterruptTplLib/Iret.h
new file mode 100644
index 0000000000..278c1e22b3
--- /dev/null
+++ b/OvmfPkg/Library/NestedInterruptTplLib/Iret.h
@@ -0,0 +1,19 @@
+/** @file
+ Force interrupt handler to return with interrupts still disabled.
+
+ Copyright (C) 2022, Fen Systems Ltd.
+
+ SPDX-License-Identifier: BSD-2-Clause-Patent
+**/
+
+#ifndef _IRET_H_
+#define _IRET_H_
+
+#include <Protocol/DebugSupport.h>
+
+VOID
+DisableInterruptsOnIret (
+ IN OUT EFI_SYSTEM_CONTEXT SystemContext
+ );
+
+#endif // _IRET_H_
diff --git a/OvmfPkg/Library/NestedInterruptTplLib/NestedInterruptTplLib.inf b/OvmfPkg/Library/NestedInterruptTplLib/NestedInterruptTplLib.inf
new file mode 100644
index 0000000000..5eafb41978
--- /dev/null
+++ b/OvmfPkg/Library/NestedInterruptTplLib/NestedInterruptTplLib.inf
@@ -0,0 +1,35 @@
+## @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
+##
+
+[Defines]
+ INF_VERSION = 1.29
+ BASE_NAME = NestedInterruptTplLib
+ FILE_GUID = 8df39823-2f9e-4ef2-b971-243b44c32c67
+ MODULE_TYPE = DXE_DRIVER
+ VERSION_STRING = 1.0
+ LIBRARY_CLASS = NestedInterruptTplLib|DXE_DRIVER
+
+[Sources]
+ Tpl.c
+ Iret.c
+
+[Packages]
+ MdePkg/MdePkg.dec
+ OvmfPkg/OvmfPkg.dec
+
+[LibraryClasses]
+ BaseLib
+ DebugLib
+ UefiBootServicesTableLib
+
+[Depex.common.DXE_DRIVER]
+ TRUE
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;
+ }
+ }
+}
diff --git a/OvmfPkg/OvmfPkg.dec b/OvmfPkg/OvmfPkg.dec
index a350bb8f84..693925a1dc 100644
--- a/OvmfPkg/OvmfPkg.dec
+++ b/OvmfPkg/OvmfPkg.dec
@@ -38,6 +38,10 @@
#
MemEncryptTdxLib|Include/Library/MemEncryptTdxLib.h
+ ## @libraryclass Handle TPL changes within nested interrupt handlers
+ #
+ NestedInterruptTplLib|Include/Library/NestedInterruptTplLib.h
+
## @libraryclass Save and restore variables using a file
#
NvVarsFileLib|Include/Library/NvVarsFileLib.h