1 /** @file
2   Report Status Code Router Driver which produces Report Stataus Code Handler Protocol
3   and Status Code Runtime Protocol.
4 
5   Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR>
6   Copyright (c) Microsoft Corporation.<BR>
7   SPDX-License-Identifier: BSD-2-Clause-Patent
8 
9 **/
10 
11 #include "ReportStatusCodeRouterRuntimeDxe.h"
12 
13 EFI_HANDLE   mHandle                    = NULL;
14 LIST_ENTRY   mCallbackListHead          = INITIALIZE_LIST_HEAD_VARIABLE (mCallbackListHead);
15 EFI_EVENT    mVirtualAddressChangeEvent = NULL;
16 
17 //
18 // Report operation nest status.
19 // If it is set, then the report operation has nested.
20 //
21 UINT32       mStatusCodeNestStatus = 0;
22 
23 EFI_STATUS_CODE_PROTOCOL  mStatusCodeProtocol  = {
24   ReportDispatcher
25 };
26 
27 EFI_RSC_HANDLER_PROTOCOL  mRscHandlerProtocol = {
28   Register,
29   Unregister
30   };
31 
32 /**
33   Event callback function to invoke status code handler in list.
34 
35   @param  Event         Event whose notification function is being invoked.
36   @param  Context       Pointer to the notification function's context, which is
37                         always zero in current implementation.
38 
39 **/
40 VOID
41 EFIAPI
RscHandlerNotification(IN EFI_EVENT Event,IN VOID * Context)42 RscHandlerNotification (
43   IN EFI_EVENT        Event,
44   IN VOID             *Context
45   )
46 {
47   RSC_HANDLER_CALLBACK_ENTRY  *CallbackEntry;
48   EFI_PHYSICAL_ADDRESS        Address;
49   RSC_DATA_ENTRY              *RscData;
50 
51   CallbackEntry = (RSC_HANDLER_CALLBACK_ENTRY *) Context;
52 
53   //
54   // Traverse the status code data buffer to parse all
55   // data to report.
56   //
57   Address = CallbackEntry->StatusCodeDataBuffer;
58   while (Address < CallbackEntry->EndPointer) {
59     RscData = (RSC_DATA_ENTRY *) (UINTN) Address;
60     CallbackEntry->RscHandlerCallback (
61                      RscData->Type,
62                      RscData->Value,
63                      RscData->Instance,
64                      &RscData->CallerId,
65                      &RscData->Data
66                      );
67 
68     Address += (OFFSET_OF (RSC_DATA_ENTRY, Data) + RscData->Data.HeaderSize + RscData->Data.Size);
69     Address  = ALIGN_VARIABLE (Address);
70   }
71 
72   CallbackEntry->EndPointer = CallbackEntry->StatusCodeDataBuffer;
73 }
74 
75 /**
76   Register the callback function for ReportStatusCode() notification.
77 
78   When this function is called the function pointer is added to an internal list and any future calls to
79   ReportStatusCode() will be forwarded to the Callback function. During the bootservices,
80   this is the callback for which this service can be invoked. The report status code router
81   will create an event such that the callback function is only invoked at the TPL for which it was
82   registered. The entity that registers for the callback should also register for an event upon
83   generation of exit boot services and invoke the unregister service.
84   If the handler does not have a TPL dependency, it should register for a callback at TPL high. The
85   router infrastructure will support making callbacks at runtime, but the caller for runtime invocation
86   must meet the following criteria:
87   1. must be a runtime driver type so that its memory is not reclaimed
88   2. not unregister at exit boot services so that the router will still have its callback address
89   3. the caller must be self-contained (eg. Not call out into any boot-service interfaces) and be
90   runtime safe, in general.
91 
92   @param[in] Callback   A pointer to a function of type EFI_RSC_HANDLER_CALLBACK that is called when
93                         a call to ReportStatusCode() occurs.
94   @param[in] Tpl        TPL at which callback can be safely invoked.
95 
96   @retval  EFI_SUCCESS              Function was successfully registered.
97   @retval  EFI_INVALID_PARAMETER    The callback function was NULL.
98   @retval  EFI_OUT_OF_RESOURCES     The internal buffer ran out of space. No more functions can be
99                                     registered.
100   @retval  EFI_ALREADY_STARTED      The function was already registered. It can't be registered again.
101 
102 **/
103 EFI_STATUS
104 EFIAPI
Register(IN EFI_RSC_HANDLER_CALLBACK Callback,IN EFI_TPL Tpl)105 Register (
106   IN EFI_RSC_HANDLER_CALLBACK   Callback,
107   IN EFI_TPL                    Tpl
108   )
109 {
110   EFI_STATUS                    Status;
111   LIST_ENTRY                    *Link;
112   RSC_HANDLER_CALLBACK_ENTRY    *CallbackEntry;
113 
114   if (Callback == NULL) {
115     return EFI_INVALID_PARAMETER;
116   }
117 
118   for (Link = GetFirstNode (&mCallbackListHead); !IsNull (&mCallbackListHead, Link); Link = GetNextNode (&mCallbackListHead, Link)) {
119     CallbackEntry = CR (Link, RSC_HANDLER_CALLBACK_ENTRY, Node, RSC_HANDLER_CALLBACK_ENTRY_SIGNATURE);
120     if (CallbackEntry->RscHandlerCallback == Callback) {
121       //
122       // If the function was already registered. It can't be registered again.
123       //
124       return EFI_ALREADY_STARTED;
125     }
126   }
127 
128   CallbackEntry = AllocateRuntimeZeroPool (sizeof (RSC_HANDLER_CALLBACK_ENTRY));
129   ASSERT (CallbackEntry != NULL);
130 
131   CallbackEntry->Signature          = RSC_HANDLER_CALLBACK_ENTRY_SIGNATURE;
132   CallbackEntry->RscHandlerCallback = Callback;
133   CallbackEntry->Tpl                = Tpl;
134 
135   //
136   // If TPL of registered callback funtion is not TPL_HIGH_LEVEL, then event should be created
137   // for it, and related buffer for status code data should be prepared.
138   // Here the data buffer must be prepared in advance, because Report Status Code Protocol might
139   // be invoked under TPL_HIGH_LEVEL and no memory allocation is allowed then.
140   // If TPL is TPL_HIGH_LEVEL, then all status code will be reported immediately, without data
141   // buffer and event trigger.
142   //
143   if (Tpl != TPL_HIGH_LEVEL) {
144     CallbackEntry->StatusCodeDataBuffer = (EFI_PHYSICAL_ADDRESS) (UINTN) AllocatePool (EFI_PAGE_SIZE);
145     CallbackEntry->BufferSize           = EFI_PAGE_SIZE;
146     CallbackEntry->EndPointer           = CallbackEntry->StatusCodeDataBuffer;
147     Status = gBS->CreateEvent (
148                     EVT_NOTIFY_SIGNAL,
149                     Tpl,
150                     RscHandlerNotification,
151                     CallbackEntry,
152                     &CallbackEntry->Event
153                     );
154     ASSERT_EFI_ERROR (Status);
155   }
156 
157   InsertTailList (&mCallbackListHead, &CallbackEntry->Node);
158 
159   return EFI_SUCCESS;
160 }
161 
162 /**
163   Remove a previously registered callback function from the notification list.
164 
165   A callback function must be unregistered before it is deallocated. It is important that any registered
166   callbacks that are not runtime complaint be unregistered when ExitBootServices() is called.
167 
168   @param[in]  Callback  A pointer to a function of type EFI_RSC_HANDLER_CALLBACK that is to be
169                         unregistered.
170 
171   @retval EFI_SUCCESS           The function was successfully unregistered.
172   @retval EFI_INVALID_PARAMETER The callback function was NULL.
173   @retval EFI_NOT_FOUND         The callback function was not found to be unregistered.
174 
175 **/
176 EFI_STATUS
177 EFIAPI
Unregister(IN EFI_RSC_HANDLER_CALLBACK Callback)178 Unregister (
179   IN EFI_RSC_HANDLER_CALLBACK Callback
180   )
181 {
182   LIST_ENTRY                    *Link;
183   RSC_HANDLER_CALLBACK_ENTRY    *CallbackEntry;
184 
185   if (Callback == NULL) {
186     return EFI_INVALID_PARAMETER;
187   }
188 
189   for (Link = GetFirstNode (&mCallbackListHead); !IsNull (&mCallbackListHead, Link); Link = GetNextNode (&mCallbackListHead, Link)) {
190     CallbackEntry = CR (Link, RSC_HANDLER_CALLBACK_ENTRY, Node, RSC_HANDLER_CALLBACK_ENTRY_SIGNATURE);
191     if (CallbackEntry->RscHandlerCallback == Callback) {
192       //
193       // If the function is found in list, delete it and return.
194       //
195       if (CallbackEntry->Tpl != TPL_HIGH_LEVEL) {
196         FreePool ((VOID *) (UINTN) CallbackEntry->StatusCodeDataBuffer);
197         gBS->CloseEvent (CallbackEntry->Event);
198       }
199       RemoveEntryList (&CallbackEntry->Node);
200       FreePool (CallbackEntry);
201       return EFI_SUCCESS;
202     }
203   }
204 
205   return EFI_NOT_FOUND;
206 }
207 
208 /**
209   Provides an interface that a software module can call to report a status code.
210 
211   @param  Type             Indicates the type of status code being reported.
212   @param  Value            Describes the current status of a hardware or software entity.
213                            This included information about the class and subclass that is used to
214                            classify the entity as well as an operation.
215   @param  Instance         The enumeration of a hardware or software entity within
216                            the system. Valid instance numbers start with 1.
217   @param  CallerId         This optional parameter may be used to identify the caller.
218                            This parameter allows the status code driver to apply different rules to
219                            different callers.
220   @param  Data             This optional parameter may be used to pass additional data.
221 
222   @retval EFI_SUCCESS           The function completed successfully
223   @retval EFI_DEVICE_ERROR      The function should not be completed due to a device error.
224 
225 **/
226 EFI_STATUS
227 EFIAPI
ReportDispatcher(IN EFI_STATUS_CODE_TYPE Type,IN EFI_STATUS_CODE_VALUE Value,IN UINT32 Instance,IN EFI_GUID * CallerId OPTIONAL,IN EFI_STATUS_CODE_DATA * Data OPTIONAL)228 ReportDispatcher (
229   IN EFI_STATUS_CODE_TYPE     Type,
230   IN EFI_STATUS_CODE_VALUE    Value,
231   IN UINT32                   Instance,
232   IN EFI_GUID                 *CallerId  OPTIONAL,
233   IN EFI_STATUS_CODE_DATA     *Data      OPTIONAL
234   )
235 {
236   LIST_ENTRY                    *Link;
237   RSC_HANDLER_CALLBACK_ENTRY    *CallbackEntry;
238   RSC_DATA_ENTRY                *RscData;
239   EFI_STATUS                    Status;
240   VOID                          *NewBuffer;
241   EFI_PHYSICAL_ADDRESS          FailSafeEndPointer;
242 
243   //
244   // Use atom operation to avoid the reentant of report.
245   // If current status is not zero, then the function is reentrancy.
246   //
247   if (InterlockedCompareExchange32 (&mStatusCodeNestStatus, 0, 1) == 1) {
248     return EFI_DEVICE_ERROR;
249   }
250 
251   for (Link = GetFirstNode (&mCallbackListHead); !IsNull (&mCallbackListHead, Link);) {
252     CallbackEntry = CR (Link, RSC_HANDLER_CALLBACK_ENTRY, Node, RSC_HANDLER_CALLBACK_ENTRY_SIGNATURE);
253     //
254     // The handler may remove itself, so get the next handler in advance.
255     //
256     Link = GetNextNode (&mCallbackListHead, Link);
257     if ((CallbackEntry->Tpl == TPL_HIGH_LEVEL) || EfiAtRuntime ()) {
258       CallbackEntry->RscHandlerCallback (
259                        Type,
260                        Value,
261                        Instance,
262                        CallerId,
263                        Data
264                        );
265       continue;
266     }
267 
268     //
269     // If callback is registered with TPL lower than TPL_HIGH_LEVEL, event must be signaled at boot time to possibly wait for
270     // allowed TPL to report status code. Related data should also be stored in data buffer.
271     //
272     FailSafeEndPointer = CallbackEntry->EndPointer;
273     CallbackEntry->EndPointer  = ALIGN_VARIABLE (CallbackEntry->EndPointer);
274     RscData = (RSC_DATA_ENTRY *) (UINTN) CallbackEntry->EndPointer;
275     CallbackEntry->EndPointer += sizeof (RSC_DATA_ENTRY);
276     if (Data != NULL) {
277       CallbackEntry->EndPointer += (Data->Size + Data->HeaderSize - sizeof (EFI_STATUS_CODE_DATA));
278     }
279 
280     //
281     // If data buffer is about to be used up (7/8 here), try to reallocate a buffer with double size, if not at TPL_HIGH_LEVEL.
282     //
283     if (CallbackEntry->EndPointer > (CallbackEntry->StatusCodeDataBuffer + (CallbackEntry->BufferSize / 8) * 7)) {
284       if (EfiGetCurrentTpl () < TPL_HIGH_LEVEL) {
285         NewBuffer = ReallocatePool (
286                       CallbackEntry->BufferSize,
287                       CallbackEntry->BufferSize * 2,
288                       (VOID *) (UINTN) CallbackEntry->StatusCodeDataBuffer
289                       );
290         if (NewBuffer != NULL) {
291           FailSafeEndPointer = (EFI_PHYSICAL_ADDRESS) (UINTN) NewBuffer + (FailSafeEndPointer - CallbackEntry->StatusCodeDataBuffer);
292           CallbackEntry->EndPointer = (EFI_PHYSICAL_ADDRESS) (UINTN) NewBuffer + (CallbackEntry->EndPointer - CallbackEntry->StatusCodeDataBuffer);
293           RscData = (RSC_DATA_ENTRY *) (UINTN) ((UINTN) NewBuffer + ((UINTN) RscData - CallbackEntry->StatusCodeDataBuffer));
294           CallbackEntry->StatusCodeDataBuffer = (EFI_PHYSICAL_ADDRESS) (UINTN) NewBuffer;
295           CallbackEntry->BufferSize *= 2;
296         }
297       }
298     }
299 
300     //
301     // If data buffer is used up, do not report for this time.
302     //
303     if (CallbackEntry->EndPointer > (CallbackEntry->StatusCodeDataBuffer + CallbackEntry->BufferSize)) {
304       CallbackEntry->EndPointer = FailSafeEndPointer;
305       continue;
306     }
307 
308     RscData->Type      = Type;
309     RscData->Value     = Value;
310     RscData->Instance  = Instance;
311     if (CallerId != NULL) {
312       CopyGuid (&RscData->CallerId, CallerId);
313     }
314     if (Data != NULL) {
315       CopyMem (&RscData->Data, Data, Data->HeaderSize + Data->Size);
316     } else {
317       ZeroMem (&RscData->Data, sizeof (RscData->Data));
318       RscData->Data.HeaderSize = sizeof (RscData->Data);
319     }
320 
321     Status = gBS->SignalEvent (CallbackEntry->Event);
322     ASSERT_EFI_ERROR (Status);
323   }
324 
325   //
326   // Restore the nest status of report
327   //
328   InterlockedCompareExchange32 (&mStatusCodeNestStatus, 1, 0);
329 
330   return EFI_SUCCESS;
331 }
332 
333 /**
334   Virtual address change notification call back. It converts global pointer
335   to virtual address.
336 
337   @param  Event         Event whose notification function is being invoked.
338   @param  Context       Pointer to the notification function's context, which is
339                         always zero in current implementation.
340 
341 **/
342 VOID
343 EFIAPI
VirtualAddressChangeCallBack(IN EFI_EVENT Event,IN VOID * Context)344 VirtualAddressChangeCallBack (
345   IN EFI_EVENT        Event,
346   IN VOID             *Context
347   )
348 {
349   EFI_STATUS          Status;
350   LIST_ENTRY                    *Link;
351   RSC_HANDLER_CALLBACK_ENTRY    *CallbackEntry;
352 
353   for (Link = GetFirstNode (&mCallbackListHead); !IsNull (&mCallbackListHead, Link); Link = GetNextNode (&mCallbackListHead, Link)) {
354     CallbackEntry = CR (Link, RSC_HANDLER_CALLBACK_ENTRY, Node, RSC_HANDLER_CALLBACK_ENTRY_SIGNATURE);
355     Status = EfiConvertFunctionPointer (0, (VOID **) &CallbackEntry->RscHandlerCallback);
356     ASSERT_EFI_ERROR (Status);
357   }
358 
359   Status = EfiConvertList (
360              0,
361              &mCallbackListHead
362              );
363   ASSERT_EFI_ERROR (Status);
364 }
365 
366 /**
367   Entry point of Generic Status Code Driver.
368 
369   This function is the entry point of this Generic Status Code Driver.
370   It installs eport Stataus Code Handler Protocol  and Status Code Runtime Protocol,
371   and registers event for EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE.
372 
373   @param  ImageHandle       The firmware allocated handle for the EFI image.
374   @param  SystemTable       A pointer to the EFI System Table.
375 
376   @retval EFI_SUCCESS       The entry point is executed successfully.
377 
378 **/
379 EFI_STATUS
380 EFIAPI
GenericStatusCodeRuntimeDxeEntry(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)381 GenericStatusCodeRuntimeDxeEntry (
382   IN EFI_HANDLE         ImageHandle,
383   IN EFI_SYSTEM_TABLE   *SystemTable
384   )
385 {
386   EFI_STATUS     Status;
387 
388   Status = gBS->InstallMultipleProtocolInterfaces (
389                   &mHandle,
390                   &gEfiRscHandlerProtocolGuid,
391                   &mRscHandlerProtocol,
392                   &gEfiStatusCodeRuntimeProtocolGuid,
393                   &mStatusCodeProtocol,
394                   NULL
395                   );
396   ASSERT_EFI_ERROR (Status);
397 
398   Status = gBS->CreateEventEx (
399                   EVT_NOTIFY_SIGNAL,
400                   TPL_NOTIFY,
401                   VirtualAddressChangeCallBack,
402                   NULL,
403                   &gEfiEventVirtualAddressChangeGuid,
404                   &mVirtualAddressChangeEvent
405                   );
406   ASSERT_EFI_ERROR (Status);
407 
408   return EFI_SUCCESS;
409 }
410