summaryrefslogtreecommitdiffstats
path: root/OvmfPkg/Library/NestedInterruptTplLib/Tpl.c
blob: e19d98878eb75a270405f23468bb2576fe1d1642 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
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;
    }
  }
}