1 /** @file
2   Publishes ESRT table from Firmware Management Protocol instances
3 
4   Copyright (c) 2016, Microsoft Corporation
5   Copyright (c) 2018, Intel Corporation. All rights reserved.<BR>
6 
7   All rights reserved.
8   SPDX-License-Identifier: BSD-2-Clause-Patent
9 
10 **/
11 
12 #include <Uefi.h>
13 #include <Library/BaseLib.h>
14 #include <Library/BaseMemoryLib.h>
15 #include <Library/MemoryAllocationLib.h>
16 #include <Library/UefiBootServicesTableLib.h>
17 #include <Library/DebugLib.h>
18 #include <Library/PcdLib.h>
19 #include <Library/UefiLib.h>
20 #include <Protocol/FirmwareManagement.h>
21 #include <Guid/EventGroup.h>
22 #include <Guid/SystemResourceTable.h>
23 
24 /**
25  Print ESRT to debug console.
26 
27  @param[in]  Table   Pointer to the ESRT table.
28 
29 **/
30 VOID
31 EFIAPI
32 PrintTable (
33   IN EFI_SYSTEM_RESOURCE_TABLE  *Table
34   );
35 
36 //
37 // Number of ESRT entries to grow by each time we run out of room
38 //
39 #define GROWTH_STEP  10
40 
41 /**
42   Install EFI System Resource Table into the UEFI Configuration Table
43 
44   @param[in] Table                  Pointer to the ESRT.
45 
46   @return  Status code.
47 
48 **/
49 EFI_STATUS
InstallEfiSystemResourceTableInUefiConfigurationTable(IN EFI_SYSTEM_RESOURCE_TABLE * Table)50 InstallEfiSystemResourceTableInUefiConfigurationTable (
51   IN EFI_SYSTEM_RESOURCE_TABLE      *Table
52   )
53 {
54   EFI_STATUS Status;
55 
56   Status = EFI_SUCCESS;
57   if (Table->FwResourceCount == 0) {
58     DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Can't install ESRT table because it has zero Entries. \n"));
59     Status = EFI_UNSUPPORTED;
60   } else {
61     //
62     // Install the pointer into config table
63     //
64     Status = gBS->InstallConfigurationTable (&gEfiSystemResourceTableGuid, Table);
65     if (EFI_ERROR (Status)) {
66       DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Can't install ESRT table.  Status: %r. \n", Status));
67     } else {
68       DEBUG ((DEBUG_INFO, "EsrtFmpDxe: Installed ESRT table. \n"));
69     }
70   }
71   return Status;
72 }
73 
74 /**
75   Return if this FMP is a system FMP or a device FMP, based upon FmpImageInfo.
76 
77   @param[in] FmpImageInfo A pointer to EFI_FIRMWARE_IMAGE_DESCRIPTOR
78 
79   @return TRUE  It is a system FMP.
80   @return FALSE It is a device FMP.
81 **/
82 BOOLEAN
IsSystemFmp(IN EFI_FIRMWARE_IMAGE_DESCRIPTOR * FmpImageInfo)83 IsSystemFmp (
84   IN EFI_FIRMWARE_IMAGE_DESCRIPTOR  *FmpImageInfo
85   )
86 {
87   GUID   *Guid;
88   UINTN  Count;
89   UINTN  Index;
90 
91   Guid  = PcdGetPtr (PcdSystemFmpCapsuleImageTypeIdGuid);
92   Count = PcdGetSize (PcdSystemFmpCapsuleImageTypeIdGuid) / sizeof(GUID);
93 
94   for (Index = 0; Index < Count; Index++, Guid++) {
95     if (CompareGuid (&FmpImageInfo->ImageTypeId, Guid)) {
96       return TRUE;
97     }
98   }
99 
100   return FALSE;
101 }
102 
103 /**
104   Function to create a single ESRT Entry and add it to the ESRT
105   given a FMP descriptor.  If the guid is already in the ESRT it
106   will be ignored.  The ESRT will grow if it does not have enough room.
107 
108   @param[in, out] Table             On input, pointer to the pointer to the ESRT.
109                                     On output, same as input or pointer to the pointer
110                                     to new enlarged ESRT.
111   @param[in]      FmpImageInfoBuf   Pointer to the EFI_FIRMWARE_IMAGE_DESCRIPTOR.
112   @param[in]      FmpVersion        FMP Version.
113 
114   @return  Status code.
115 
116 **/
117 EFI_STATUS
CreateEsrtEntry(IN OUT EFI_SYSTEM_RESOURCE_TABLE ** Table,IN EFI_FIRMWARE_IMAGE_DESCRIPTOR * FmpImageInfoBuf,IN UINT32 FmpVersion)118 CreateEsrtEntry (
119   IN OUT EFI_SYSTEM_RESOURCE_TABLE  **Table,
120   IN EFI_FIRMWARE_IMAGE_DESCRIPTOR  *FmpImageInfoBuf,
121   IN UINT32                         FmpVersion
122   )
123 {
124   UINTN                      Index;
125   EFI_SYSTEM_RESOURCE_ENTRY  *Entry;
126   UINTN                      NewSize;
127   EFI_SYSTEM_RESOURCE_TABLE  *NewTable;
128 
129   Index = 0;
130   Entry = NULL;
131 
132   Entry = (EFI_SYSTEM_RESOURCE_ENTRY *)((*Table) + 1);
133   //
134   // Make sure Guid isn't already in the list
135   //
136   for (Index = 0; Index < (*Table)->FwResourceCount; Index++) {
137     if (CompareGuid (&Entry->FwClass, &FmpImageInfoBuf->ImageTypeId)) {
138       DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: ESRT Entry already exists for FMP Instance with GUID %g\n", &Entry->FwClass));
139       return EFI_INVALID_PARAMETER;
140     }
141     Entry++;
142   }
143 
144   //
145   // Grow table if needed
146   //
147   if ((*Table)->FwResourceCount >= (*Table)->FwResourceCountMax) {
148     NewSize  = (((*Table)->FwResourceCountMax + GROWTH_STEP) * sizeof (EFI_SYSTEM_RESOURCE_ENTRY)) + sizeof (EFI_SYSTEM_RESOURCE_TABLE);
149     NewTable = AllocateZeroPool (NewSize);
150     if (NewTable == NULL) {
151       DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Failed to allocate memory larger table for ESRT. \n"));
152       return EFI_OUT_OF_RESOURCES;
153     }
154     //
155     // Copy the whole old table into new table buffer
156     //
157     CopyMem (
158       NewTable,
159       (*Table),
160       (((*Table)->FwResourceCountMax) * sizeof (EFI_SYSTEM_RESOURCE_ENTRY)) + sizeof (EFI_SYSTEM_RESOURCE_TABLE)
161       );
162     //
163     // Update max
164     //
165     NewTable->FwResourceCountMax = NewTable->FwResourceCountMax + GROWTH_STEP;
166     //
167     // Free old table
168     //
169     FreePool ((*Table));
170     //
171     // Reassign pointer to new table.
172     //
173     (*Table) = NewTable;
174   }
175 
176   //
177   // ESRT table has enough room for the new entry so add new entry
178   //
179   Entry = (EFI_SYSTEM_RESOURCE_ENTRY *)(((UINT8 *)(*Table)) + sizeof (EFI_SYSTEM_RESOURCE_TABLE));
180   //
181   // Move to the location of new entry
182   //
183   Entry = Entry + (*Table)->FwResourceCount;
184   //
185   // Increment resource count
186   //
187   (*Table)->FwResourceCount++;
188 
189   CopyGuid (&Entry->FwClass, &FmpImageInfoBuf->ImageTypeId);
190 
191   if (IsSystemFmp (FmpImageInfoBuf)) {
192     DEBUG ((DEBUG_INFO, "EsrtFmpDxe: Found an ESRT entry for a System Device.\n"));
193     Entry->FwType = (UINT32)(ESRT_FW_TYPE_SYSTEMFIRMWARE);
194   } else {
195     Entry->FwType = (UINT32)(ESRT_FW_TYPE_DEVICEFIRMWARE);
196   }
197 
198   Entry->FwVersion = FmpImageInfoBuf->Version;
199   Entry->LowestSupportedFwVersion = 0;
200   Entry->CapsuleFlags = 0;
201   Entry->LastAttemptVersion = 0;
202   Entry->LastAttemptStatus = 0;
203 
204   //
205   // VERSION 2 has Lowest Supported
206   //
207   if (FmpVersion >= 2) {
208     Entry->LowestSupportedFwVersion = FmpImageInfoBuf->LowestSupportedImageVersion;
209   }
210 
211   //
212   // VERSION 3 supports last attempt values
213   //
214   if (FmpVersion >= 3) {
215     Entry->LastAttemptVersion = FmpImageInfoBuf->LastAttemptVersion;
216     Entry->LastAttemptStatus = FmpImageInfoBuf->LastAttemptStatus;
217   }
218 
219   return EFI_SUCCESS;
220 }
221 
222 /**
223   Function to create ESRT based on FMP Instances.
224   Create ESRT table, get the descriptors from FMP Instance and
225   create ESRT entries (ESRE).
226 
227   @return Pointer to the ESRT created.
228 
229 **/
230 EFI_SYSTEM_RESOURCE_TABLE *
CreateFmpBasedEsrt(VOID)231 CreateFmpBasedEsrt (
232   VOID
233   )
234 {
235   EFI_STATUS                        Status;
236   EFI_SYSTEM_RESOURCE_TABLE         *Table;
237   UINTN                             NoProtocols;
238   VOID                              **Buffer;
239   UINTN                             Index;
240   EFI_FIRMWARE_MANAGEMENT_PROTOCOL  *Fmp;
241   UINTN                             DescriptorSize;
242   EFI_FIRMWARE_IMAGE_DESCRIPTOR     *FmpImageInfoBuf;
243   EFI_FIRMWARE_IMAGE_DESCRIPTOR     *FmpImageInfoBufOrg;
244   UINT8                             FmpImageInfoCount;
245   UINT32                            FmpImageInfoDescriptorVer;
246   UINTN                             ImageInfoSize;
247   UINT32                            PackageVersion;
248   CHAR16                            *PackageVersionName;
249 
250   Status             = EFI_SUCCESS;
251   Table              = NULL;
252   NoProtocols        = 0;
253   Buffer             = NULL;
254   PackageVersionName = NULL;
255   FmpImageInfoBuf    = NULL;
256   FmpImageInfoBufOrg = NULL;
257   Fmp                = NULL;
258 
259   Status = EfiLocateProtocolBuffer (
260              &gEfiFirmwareManagementProtocolGuid,
261              &NoProtocols,
262              &Buffer
263              );
264   if (EFI_ERROR(Status) || (Buffer == NULL)) {
265     return NULL;
266   }
267 
268   //
269   // Allocate Memory for table
270   //
271   Table = AllocateZeroPool (
272              (GROWTH_STEP * sizeof (EFI_SYSTEM_RESOURCE_ENTRY)) + sizeof (EFI_SYSTEM_RESOURCE_TABLE)
273              );
274   if (Table == NULL) {
275     DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Failed to allocate memory for ESRT.\n"));
276     gBS->FreePool (Buffer);
277     return NULL;
278   }
279 
280   Table->FwResourceCount    = 0;
281   Table->FwResourceCountMax = GROWTH_STEP;
282   Table->FwResourceVersion  = EFI_SYSTEM_RESOURCE_TABLE_FIRMWARE_RESOURCE_VERSION;
283 
284   for (Index = 0; Index < NoProtocols; Index++) {
285     Fmp = (EFI_FIRMWARE_MANAGEMENT_PROTOCOL *) Buffer[Index];
286 
287     ImageInfoSize = 0;
288     Status = Fmp->GetImageInfo (
289                     Fmp,                         // FMP Pointer
290                     &ImageInfoSize,              // Buffer Size (in this case 0)
291                     NULL,                        // NULL so we can get size
292                     &FmpImageInfoDescriptorVer,  // DescriptorVersion
293                     &FmpImageInfoCount,          // DescriptorCount
294                     &DescriptorSize,             // DescriptorSize
295                     &PackageVersion,             // PackageVersion
296                     &PackageVersionName          // PackageVersionName
297                     );
298 
299     if (Status != EFI_BUFFER_TOO_SMALL) {
300       DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Unexpected Failure in GetImageInfo.  Status = %r\n", Status));
301       continue;
302     }
303 
304     FmpImageInfoBuf = AllocateZeroPool (ImageInfoSize);
305     if (FmpImageInfoBuf == NULL) {
306       DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Failed to get memory for descriptors.\n"));
307       continue;
308     }
309 
310     FmpImageInfoBufOrg = FmpImageInfoBuf;
311     PackageVersionName = NULL;
312     Status = Fmp->GetImageInfo (
313                     Fmp,
314                     &ImageInfoSize,              // ImageInfoSize
315                     FmpImageInfoBuf,             // ImageInfo
316                     &FmpImageInfoDescriptorVer,  // DescriptorVersion
317                     &FmpImageInfoCount,          // DescriptorCount
318                     &DescriptorSize,             // DescriptorSize
319                     &PackageVersion,             // PackageVersion
320                     &PackageVersionName          // PackageVersionName
321                     );
322     if (EFI_ERROR (Status)) {
323       DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Failure in GetImageInfo.  Status = %r\n", Status));
324       FreePool (FmpImageInfoBufOrg);
325       FmpImageInfoBufOrg = NULL;
326       continue;
327     }
328 
329     //
330     // Check each descriptor and read from the one specified
331     //
332     while (FmpImageInfoCount > 0) {
333       //
334       // If the descriptor has the IN USE bit set, create ESRT entry otherwise ignore.
335       //
336       if ((FmpImageInfoBuf->AttributesSetting & FmpImageInfoBuf->AttributesSupported & IMAGE_ATTRIBUTE_IN_USE) == IMAGE_ATTRIBUTE_IN_USE) {
337         //
338         // Create ESRT entry
339         //
340         CreateEsrtEntry (&Table, FmpImageInfoBuf, FmpImageInfoDescriptorVer);
341       }
342       FmpImageInfoCount--;
343       //
344       // Increment the buffer pointer ahead by the size of the descriptor
345       //
346       FmpImageInfoBuf = (EFI_FIRMWARE_IMAGE_DESCRIPTOR *)(((UINT8 *)FmpImageInfoBuf) + DescriptorSize);
347     }
348 
349     if (PackageVersionName != NULL) {
350       FreePool (PackageVersionName);
351       PackageVersionName = NULL;
352     }
353     FreePool (FmpImageInfoBufOrg);
354     FmpImageInfoBufOrg = NULL;
355   }
356 
357   gBS->FreePool (Buffer);
358   return Table;
359 }
360 
361 /**
362   Notify function for event group EFI_EVENT_GROUP_READY_TO_BOOT. This is used to
363   install the Efi System Resource Table.
364 
365   @param[in]  Event    The Event that is being processed.
366   @param[in]  Context  The Event Context.
367 
368 **/
369 VOID
370 EFIAPI
EsrtReadyToBootEventNotify(IN EFI_EVENT Event,IN VOID * Context)371 EsrtReadyToBootEventNotify (
372   IN EFI_EVENT  Event,
373   IN VOID       *Context
374   )
375 {
376   EFI_STATUS                 Status;
377   EFI_SYSTEM_RESOURCE_TABLE  *Table;
378 
379   Table = CreateFmpBasedEsrt ();
380   if (Table != NULL) {
381     //
382     // Print table on debug builds
383     //
384     DEBUG_CODE_BEGIN ();
385     PrintTable (Table);
386     DEBUG_CODE_END ();
387 
388     Status = InstallEfiSystemResourceTableInUefiConfigurationTable (Table);
389     if (EFI_ERROR (Status)) {
390       FreePool (Table);
391     }
392   } else {
393     DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Can't install ESRT table because it is NULL. \n"));
394   }
395 
396   //
397   // Close the event to prevent it be signalled again.
398   //
399   gBS->CloseEvent (Event);
400 }
401 
402 /**
403   The module Entry Point of the Efi System Resource Table DXE driver.
404 
405   @param[in]  ImageHandle  The firmware allocated handle for the EFI image.
406   @param[in]  SystemTable  A pointer to the EFI System Table.
407 
408   @retval  EFI_SUCCESS  The entry point is executed successfully.
409   @retval  Other        Some error occurs when executing this entry point.
410 
411 **/
412 EFI_STATUS
413 EFIAPI
EsrtFmpEntryPoint(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)414 EsrtFmpEntryPoint (
415   IN EFI_HANDLE        ImageHandle,
416   IN EFI_SYSTEM_TABLE  *SystemTable
417   )
418 {
419   EFI_STATUS  Status;
420   EFI_EVENT   EsrtReadyToBootEvent;
421 
422   //
423   // Register notify function to install ESRT on ReadyToBoot Event.
424   //
425   Status = EfiCreateEventReadyToBootEx (
426              TPL_CALLBACK,
427              EsrtReadyToBootEventNotify,
428              NULL,
429              &EsrtReadyToBootEvent
430              );
431 
432   ASSERT_EFI_ERROR (Status);
433   if (EFI_ERROR (Status)) {
434     DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Failed to register for ready to boot\n"));
435   }
436 
437   return Status;
438 }
439