summaryrefslogtreecommitdiffstats
path: root/NetworkPkg/DpcDxe/Dpc.c
blob: 7c85dd8757c329d6b9e1fbda4039206f3ee48a12 (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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/** @file

Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

Module Name:

  Dpc.c

Abstract:


**/

#include "Dpc.h"

//
// Handle for the EFI_DPC_PROTOCOL instance
//
EFI_HANDLE  mDpcHandle = NULL;

//
// The EFI_DPC_PROTOCOL instances that is installed onto mDpcHandle
//
EFI_DPC_PROTOCOL mDpc = {
  DpcQueueDpc,
  DpcDispatchDpc
};

//
// Global variables used to measure the DPC Queue Depths
//
UINTN  mDpcQueueDepth = 0;
UINTN  mMaxDpcQueueDepth = 0;

//
// Free list of DPC entries.  As DPCs are queued, entries are removed from this
// free list.  As DPC entries are dispatched, DPC entries are added to the free list.
// If the free list is empty and a DPC is queued, the free list is grown by allocating
// an additional set of DPC entries.
//
LIST_ENTRY      mDpcEntryFreeList = INITIALIZE_LIST_HEAD_VARIABLE(mDpcEntryFreeList);

//
// An array of DPC queues.  A DPC queue is allocated for every level EFI_TPL value.
// As DPCs are queued, they are added to the end of the linked list.
// As DPCs are dispatched, they are removed from the beginning of the linked list.
//
LIST_ENTRY      mDpcQueue[TPL_HIGH_LEVEL + 1];

/**
  Add a Deferred Procedure Call to the end of the DPC queue.

  @param  This          Protocol instance pointer.
  @param  DpcTpl        The EFI_TPL that the DPC should be invoked.
  @param  DpcProcedure  Pointer to the DPC's function.
  @param  DpcContext    Pointer to the DPC's context.  Passed to DpcProcedure
                        when DpcProcedure is invoked.

  @retval EFI_SUCCESS            The DPC was queued.
  @retval EFI_INVALID_PARAMETER  DpcTpl is not a valid EFI_TPL.
  @retval EFI_INVALID_PARAMETER  DpcProcedure is NULL.
  @retval EFI_OUT_OF_RESOURCES   There are not enough resources available to
                                 add the DPC to the queue.

**/
EFI_STATUS
EFIAPI
DpcQueueDpc (
  IN EFI_DPC_PROTOCOL   *This,
  IN EFI_TPL            DpcTpl,
  IN EFI_DPC_PROCEDURE  DpcProcedure,
  IN VOID               *DpcContext    OPTIONAL
  )
{
  EFI_STATUS  ReturnStatus;
  EFI_TPL     OriginalTpl;
  DPC_ENTRY   *DpcEntry;
  UINTN       Index;

  //
  // Make sure DpcTpl is valid
  //
  if (DpcTpl < TPL_APPLICATION || DpcTpl > TPL_HIGH_LEVEL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Make sure DpcProcedure is valid
  //
  if (DpcProcedure == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Assume this function will succeed
  //
  ReturnStatus = EFI_SUCCESS;

  //
  // Raise the TPL level to TPL_HIGH_LEVEL for DPC list operation and save the
  // current TPL value so it can be restored when this function returns.
  //
  OriginalTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);

  //
  // Check to see if there are any entries in the DPC free list
  //
  if (IsListEmpty (&mDpcEntryFreeList)) {
    //
    // If the current TPL is greater than TPL_NOTIFY, then memory allocations
    // can not be performed, so the free list can not be expanded.  In this case
    // return EFI_OUT_OF_RESOURCES.
    //
    if (OriginalTpl > TPL_NOTIFY) {
      ReturnStatus = EFI_OUT_OF_RESOURCES;
      goto Done;
    }

    //
    // Add 64 DPC entries to the free list
    //
    for (Index = 0; Index < 64; Index++) {
      //
      // Lower the TPL level to perform a memory allocation
      //
      gBS->RestoreTPL (OriginalTpl);

      //
      // Allocate a new DPC entry
      //
      DpcEntry = AllocatePool (sizeof (DPC_ENTRY));

      //
      // Raise the TPL level back to TPL_HIGH_LEVEL for DPC list operations
      //
      gBS->RaiseTPL (TPL_HIGH_LEVEL);

      //
      // If the allocation of a DPC entry fails, and the free list is empty,
      // then return EFI_OUT_OF_RESOURCES.
      //
      if (DpcEntry == NULL) {
        if (IsListEmpty (&mDpcEntryFreeList)) {
          ReturnStatus = EFI_OUT_OF_RESOURCES;
          goto Done;
        }
      }

      //
      // Add the newly allocated DPC entry to the DPC free list
      //
      InsertTailList (&mDpcEntryFreeList, &DpcEntry->ListEntry);
    }
  }

  //
  // Retrieve the first node from the free list of DPCs
  //
  DpcEntry = (DPC_ENTRY *)(GetFirstNode (&mDpcEntryFreeList));

  //
  // Remove the first node from the free list of DPCs
  //
  RemoveEntryList (&DpcEntry->ListEntry);

  //
  // Fill in the DPC entry with the DpcProcedure and DpcContext
  //
  DpcEntry->DpcProcedure = DpcProcedure;
  DpcEntry->DpcContext   = DpcContext;

  //
  // Add the DPC entry to the end of the list for the specified DplTpl.
  //
  InsertTailList (&mDpcQueue[DpcTpl], &DpcEntry->ListEntry);

  //
  // Increment the measured DPC queue depth across all TPLs
  //
  mDpcQueueDepth++;

  //
  // Measure the maximum DPC queue depth across all TPLs
  //
  if (mDpcQueueDepth > mMaxDpcQueueDepth) {
    mMaxDpcQueueDepth = mDpcQueueDepth;
  }

Done:
  //
  // Restore the original TPL level when this function was called
  //
  gBS->RestoreTPL (OriginalTpl);

  return ReturnStatus;
}

/**
  Dispatch the queue of DPCs.  ALL DPCs that have been queued with a DpcTpl
  value greater than or equal to the current TPL are invoked in the order that
  they were queued.  DPCs with higher DpcTpl values are invoked before DPCs with
  lower DpcTpl values.

  @param  This  Protocol instance pointer.

  @retval EFI_SUCCESS    One or more DPCs were invoked.
  @retval EFI_NOT_FOUND  No DPCs were invoked.

**/
EFI_STATUS
EFIAPI
DpcDispatchDpc (
  IN EFI_DPC_PROTOCOL  *This
  )
{
  EFI_STATUS  ReturnStatus;
  EFI_TPL     OriginalTpl;
  EFI_TPL     Tpl;
  DPC_ENTRY   *DpcEntry;

  //
  // Assume that no DPCs will be invoked
  //
  ReturnStatus = EFI_NOT_FOUND;

  //
  // Raise the TPL level to TPL_HIGH_LEVEL for DPC list operation and save the
  // current TPL value so it can be restored when this function returns.
  //
  OriginalTpl = gBS->RaiseTPL (TPL_HIGH_LEVEL);

  //
  // Check to see if there are 1 or more DPCs currently queued
  //
  if (mDpcQueueDepth > 0) {
    //
    // Loop from TPL_HIGH_LEVEL down to the current TPL value
    //
    for (Tpl = TPL_HIGH_LEVEL; Tpl >= OriginalTpl; Tpl--) {
      //
      // Check to see if the DPC queue is empty
      //
      while (!IsListEmpty (&mDpcQueue[Tpl])) {
        //
        // Retrieve the first DPC entry from the DPC queue specified by Tpl
        //
        DpcEntry = (DPC_ENTRY *)(GetFirstNode (&mDpcQueue[Tpl]));

        //
        // Remove the first DPC entry from the DPC queue specified by Tpl
        //
        RemoveEntryList (&DpcEntry->ListEntry);

        //
        // Decrement the measured DPC Queue Depth across all TPLs
        //
        mDpcQueueDepth--;

        //
        // Lower the TPL to TPL value of the current DPC queue
        //
        gBS->RestoreTPL (Tpl);

        //
        // Invoke the DPC passing in its context
        //
        (DpcEntry->DpcProcedure) (DpcEntry->DpcContext);

        //
        // At least one DPC has been invoked, so set the return status to EFI_SUCCESS
        //
        ReturnStatus = EFI_SUCCESS;

        //
        // Raise the TPL level back to TPL_HIGH_LEVEL for DPC list operations
        //
        gBS->RaiseTPL (TPL_HIGH_LEVEL);

        //
        // Add the invoked DPC entry to the DPC free list
        //
        InsertTailList (&mDpcEntryFreeList, &DpcEntry->ListEntry);
      }
    }
  }

  //
  // Restore the original TPL level when this function was called
  //
  gBS->RestoreTPL (OriginalTpl);

  return ReturnStatus;
}

/**
  The entry point for DPC driver which installs the EFI_DPC_PROTOCOL onto a new handle.

  @param  ImageHandle            The image handle of the driver.
  @param  SystemTable            The system table.

  @retval EFI_SUCCESS            The DPC queues were initialized and the EFI_DPC_PROTOCOL was
                                 installed onto a new handle.
  @retval Others                 Failed to install EFI_DPC_PROTOCOL.

**/
EFI_STATUS
EFIAPI
DpcDriverEntryPoint (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS  Status;
  UINTN       Index;

  //
  // ASSERT() if the EFI_DPC_PROTOCOL is already present in the handle database
  //
  ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gEfiDpcProtocolGuid);

  //
  // Initialize the DPC queue for all possible TPL values
  //
  for (Index = 0; Index <= TPL_HIGH_LEVEL; Index++) {
    InitializeListHead (&mDpcQueue[Index]);
  }

  //
  // Install the EFI_DPC_PROTOCOL instance onto a new handle
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &mDpcHandle,
                  &gEfiDpcProtocolGuid,
                  &mDpc,
                  NULL
                  );
  ASSERT_EFI_ERROR (Status);

  return Status;
}