From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from a7-10.smtp-out.eu-west-1.amazonses.com (a7-10.smtp-out.eu-west-1.amazonses.com [54.240.7.10]) by mx.groups.io with SMTP id smtpd.web11.9915.1670581225946442685 for ; Fri, 09 Dec 2022 02:20:26 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=@ipxe.org header.s=cphpx6z2rfcgehlykjjh3gknqe3hsoe2 header.b=VHRqpJ/t; spf=pass (domain: eu-west-1.amazonses.com, ip: 54.240.7.10, mailfrom: 01020184f66682f9-7696e0c5-f2f6-43e0-962f-38f1d5af4859-000000@eu-west-1.amazonses.com) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple; s=cphpx6z2rfcgehlykjjh3gknqe3hsoe2; d=ipxe.org; t=1670581224; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References:MIME-Version:Content-Transfer-Encoding; bh=JB1M2+6pf3Nk02sS52eweUfCXRFMTRzuh1irU4lOhFw=; b=VHRqpJ/tNBbM7K8ZoKWIRV6ZhzltKBe1yKH3ZbpLzYPHKm2KDdv2740ZVTs35RuT nIozFur3ZHCJPmQ+NLB2CsuRs4PeVtdCAM9NQ/bLXHulhaCPpva/jr63hkMGE6cFhyZ ++ze8UFMesHj9xkksLpiCWJCapyg4wkTRcObx9ufEvM6ScmqAFs6e6A/CGoyQM63fIY Sv1VrAbDWfmzsFhTbnnQTSr+5xreoJxH4CfBN3yNVqNAmJmN7RcPa4d7JiAW53zjTzv CVCLPDEZgqHdpAtuOmmtF92+zmFRDwDR9h/q5ytAJj22rppevXuzZzcQkdHFqbn51Rt Zx4mMdzXgA== DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple; s=ihchhvubuqgjsxyuhssfvqohv7z3u4hn; d=amazonses.com; t=1670581224; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References:MIME-Version:Content-Transfer-Encoding:Feedback-ID; bh=JB1M2+6pf3Nk02sS52eweUfCXRFMTRzuh1irU4lOhFw=; b=wgJjPQoLzcgE4JqNZ14VjGv04HrTMEdXyWzNM0s2ubGAw+rmol4k3aDwtkKi3PUG RFkNJKE1VloCskNYBt/OEN2QwxI09+5YTTo41o3xT9glKc2CaVfHuBPPiNNkOo9E7qp wGXciiQKB3QAybmawqUXyMa/0WDPbrZQf25TR86Q= From: "Michael Brown" To: devel@edk2.groups.io Cc: Michael Brown , Laszlo Ersek , Paolo Bonzini Subject: [PATCH 2/3] OvmfPkg: Add library to handle TPL from within nested interrupt handlers Date: Fri, 9 Dec 2022 10:20:24 +0000 Message-ID: <01020184f66682f9-7696e0c5-f2f6-43e0-962f-38f1d5af4859-000000@eu-west-1.amazonses.com> X-Mailer: git-send-email 2.38.1 In-Reply-To: References: MIME-Version: 1.0 X-Spam-Status: No, score=-2.9 required=5.0 tests=ALL_TRUSTED,BAYES_00, URIBL_DBL_BLOCKED_OPENDNS,URIBL_ZEN_BLOCKED_OPENDNS autolearn=ham autolearn_force=no version=3.4.2 X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on blyat.fensystems.co.uk Feedback-ID: 1.eu-west-1.fspj4M/5bzJ9NLRzJP0PaxRwxrpZqiDQJ1IF94CF2TA=:AmazonSES X-SES-Outgoing: 2022.12.09-54.240.7.10 Content-Transfer-Encoding: 8bit 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: Laszlo Ersek Cc: Paolo Bonzini Ref: https://bugzilla.tianocore.org/show_bug.cgi?id=4162 Signed-off-by: Michael Brown --- .../Include/Library/NestedInterruptTplLib.h | 87 +++++++ OvmfPkg/Library/NestedInterruptTplLib/Iret.c | 62 +++++ OvmfPkg/Library/NestedInterruptTplLib/Iret.h | 19 ++ .../NestedInterruptTplLib.inf | 35 +++ OvmfPkg/Library/NestedInterruptTplLib/Tpl.c | 216 ++++++++++++++++++ OvmfPkg/OvmfPkg.dec | 4 + 6 files changed, 423 insertions(+) create mode 100644 OvmfPkg/Include/Library/NestedInterruptTplLib.h create mode 100644 OvmfPkg/Library/NestedInterruptTplLib/Iret.c create mode 100644 OvmfPkg/Library/NestedInterruptTplLib/Iret.h create mode 100644 OvmfPkg/Library/NestedInterruptTplLib/NestedInterruptTplLib.inf create mode 100644 OvmfPkg/Library/NestedInterruptTplLib/Tpl.c 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 +#include +#include + +/// +/// 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 +#include + +#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 + +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 +#include +#include +#include + +#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 5f5556c67c..b2e0b7dfe2 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 -- 2.38.1