diff options
author | Michael Brown <mcb30@ipxe.org> | 2022-12-09 10:20:24 +0000 |
---|---|---|
committer | mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> | 2022-12-23 14:44:48 +0000 |
commit | a24fbd6061254ca74d6c6ed953dec6f796a45c76 (patch) | |
tree | c9631feec0d886192e3864c0a8bbd63695e3b331 /OvmfPkg/Include | |
parent | 9bf473da4c1d6d3656beca3a914861dcd1fe8a25 (diff) | |
download | edk2-a24fbd6061254ca74d6c6ed953dec6f796a45c76.tar.gz edk2-a24fbd6061254ca74d6c6ed953dec6f796a45c76.tar.bz2 edk2-a24fbd6061254ca74d6c6ed953dec6f796a45c76.zip |
OvmfPkg: Add library to handle TPL from within nested interrupt handlers
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 underrun, while continuing to allow nested
interrupts to occur.
This can be achieved by allowing, when provably safe to do so, an
inner interrupt handler to return from the interrupt without restoring
the TPL and with interrupts remaining disabled after IRET, with the
deferred call to RestoreTPL() then being issued from the outer
interrupt handler. This is necessarily messy and involves direct
manipulation of the interrupt stack frame, and so should not be
implemented as open-coded logic within each interrupt handler.
Add the Nested Interrupt TPL Library (NestedInterruptTplLib) to
provide helper functions that can be used by nested interrupt handlers
in place of RaiseTPL()/RestoreTPL().
Example call tree for a timer interrupt occurring at TPL_APPLICATION
with a nested timer interrupt that makes its own call to RestoreTPL():
outer TimerInterruptHandler()
InterruptedTPL == TPL_APPLICATION
...
IsrState->InProgressRestoreTPL = TPL_APPLICATION;
gBS->RestoreTPL (TPL_APPLICATION);
EnableInterrupts();
dispatch a TPL_CALLBACK event
gEfiCurrentTpl = TPL_CALLBACK;
nested timer interrupt occurs
inner TimerInterruptHandler()
InterruptedTPL == TPL_CALLBACK
...
IsrState->InProgressRestoreTPL = TPL_CALLBACK;
gBS->RestoreTPL (TPL_CALLBACK);
EnableInterrupts();
DisableInterrupts();
IsrState->InProgressRestoreTPL = TPL_APPLICATION;
IRET re-enables interrupts
... finish dispatching TPL_CALLBACK events ...
gEfiCurrentTpl = TPL_APPLICATION;
DisableInterrupts();
IsrState->InProgressRestoreTPL = 0;
sees IsrState->DeferredRestoreTPL == FALSE and returns
IRET re-enables interrupts
Example call tree for a timer interrupt occurring at TPL_APPLICATION
with a nested timer interrupt that defers its call to RestoreTPL() to
the outer instance of the interrupt handler:
outer TimerInterruptHandler()
InterruptedTPL == TPL_APPLICATION
...
IsrState->InProgressRestoreTPL = TPL_APPLICATION;
gBS->RestoreTPL (TPL_APPLICATION);
EnableInterrupts();
dispatch a TPL_CALLBACK event
... finish dispatching TPL_CALLBACK events ...
gEfiCurrentTpl = TPL_APPLICATION;
nested timer interrupt occurs
inner TimerInterruptHandler()
InterruptedTPL == TPL_APPLICATION;
...
sees InterruptedTPL == IsrState->InProgressRestoreTPL
IsrState->DeferredRestoreTPL = TRUE;
DisableInterruptsOnIret();
IRET returns without re-enabling interrupts
DisableInterrupts();
IsrState->InProgressRestoreTPL = 0;
sees IsrState->DeferredRestoreTPL == TRUE and loops
IsrState->InProgressRestoreTPL = TPL_APPLICATION;
gBS->RestoreTPL (TPL_APPLICATION); <-- deferred call
EnableInterrupts();
DisableInterrupts();
IsrState->InProgressRestoreTPL = 0;
sees IsrState->DeferredRestoreTPL == FALSE and returns
IRET re-enables interrupts
Cc: Paolo Bonzini <pbonzini@redhat.com>
Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=4162
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Acked-by: Laszlo Ersek <lersek@redhat.com>
Diffstat (limited to 'OvmfPkg/Include')
-rw-r--r-- | OvmfPkg/Include/Library/NestedInterruptTplLib.h | 87 |
1 files changed, 87 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__
|