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