/** @file
Publishes ESRT table from Firmware Management Protocol instances
Copyright (c) 2016, Microsoft Corporation
Copyright (c) 2018 - 2019, Intel Corporation. All rights reserved.
All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
///
/// Structure for array of unique GUID/HardwareInstance pairs from the
/// current set of EFI_FIRMWARE_IMAGE_DESCRIPTORs from all FMP Protocols.
///
typedef struct {
///
/// A unique GUID identifying the firmware image type.
///
EFI_GUID ImageTypeGuid;
///
/// An optional number to identify the unique hardware instance within the
/// system for devices that may have multiple instances whenever possible.
///
UINT64 HardwareInstance;
} GUID_HARDWAREINSTANCE_PAIR;
/**
Print ESRT to debug console.
@param[in] Table Pointer to the ESRT table.
**/
VOID
EFIAPI
PrintTable (
IN EFI_SYSTEM_RESOURCE_TABLE *Table
);
/**
Install EFI System Resource Table into the UEFI Configuration Table
@param[in] Table Pointer to the ESRT.
@return Status code.
**/
EFI_STATUS
InstallEfiSystemResourceTableInUefiConfigurationTable (
IN EFI_SYSTEM_RESOURCE_TABLE *Table
)
{
EFI_STATUS Status;
Status = EFI_SUCCESS;
if (Table->FwResourceCount == 0) {
DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Can't install ESRT table because it has zero Entries. \n"));
Status = EFI_UNSUPPORTED;
} else {
//
// Install the pointer into config table
//
Status = gBS->InstallConfigurationTable (&gEfiSystemResourceTableGuid, Table);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Can't install ESRT table. Status: %r. \n", Status));
} else {
DEBUG ((DEBUG_INFO, "EsrtFmpDxe: Installed ESRT table. \n"));
}
}
return Status;
}
/**
Return if this FMP is a system FMP or a device FMP, based upon FmpImageInfo.
@param[in] FmpImageInfo A pointer to EFI_FIRMWARE_IMAGE_DESCRIPTOR
@return TRUE It is a system FMP.
@return FALSE It is a device FMP.
**/
BOOLEAN
IsSystemFmp (
IN EFI_FIRMWARE_IMAGE_DESCRIPTOR *FmpImageInfo
)
{
GUID *Guid;
UINTN Count;
UINTN Index;
Guid = PcdGetPtr (PcdSystemFmpCapsuleImageTypeIdGuid);
Count = PcdGetSize (PcdSystemFmpCapsuleImageTypeIdGuid) / sizeof (GUID);
for (Index = 0; Index < Count; Index++, Guid++) {
if (CompareGuid (&FmpImageInfo->ImageTypeId, Guid)) {
return TRUE;
}
}
return FALSE;
}
/**
Function to create a single ESRT Entry and add it to the ESRT with
a given FMP descriptor. If the GUID is already in the ESRT, then the ESRT
entry is updated.
@param[in,out] Table Pointer to the ESRT Table.
@param[in,out] HardwareInstances Pointer to the GUID_HARDWAREINSTANCE_PAIR.
@param[in,out] NumberOfDescriptors The number of EFI_FIRMWARE_IMAGE_DESCRIPTORs.
@param[in] FmpImageInfoBuf Pointer to the EFI_FIRMWARE_IMAGE_DESCRIPTOR.
@param[in] FmpVersion FMP Version.
@retval EFI_SUCCESS FmpImageInfoBuf was use to fill in a new ESRT entry
in Table.
@retval EFI_SUCCESS The ImageTypeId GUID in FmpImageInfoBuf matches an
existing ESRT entry in Table, and the information
from FmpImageInfoBuf was merged into the the existing
ESRT entry.
@retval EFI_UNSPOORTED The GUID/HardareInstance in FmpImageInfoBuf has is a
duplicate.
**/
EFI_STATUS
CreateEsrtEntry (
IN OUT EFI_SYSTEM_RESOURCE_TABLE *Table,
IN OUT GUID_HARDWAREINSTANCE_PAIR *HardwareInstances,
IN OUT UINT32 *NumberOfDescriptors,
IN EFI_FIRMWARE_IMAGE_DESCRIPTOR *FmpImageInfoBuf,
IN UINT32 FmpVersion
)
{
UINTN Index;
EFI_SYSTEM_RESOURCE_ENTRY *Entry;
UINT64 FmpHardwareInstance;
FmpHardwareInstance = 0;
if (FmpVersion >= 3) {
FmpHardwareInstance = FmpImageInfoBuf->HardwareInstance;
}
//
// Check to see of FmpImageInfoBuf GUID/HardwareInstance is unique
// Skip if HardwareInstance is 0 as this is the case if FmpVersion < 3
// or the device can not create a unique ID per UEFI specification
//
if (FmpHardwareInstance != 0) {
for (Index = 0; Index < *NumberOfDescriptors; Index++) {
if (CompareGuid (&HardwareInstances[Index].ImageTypeGuid, &FmpImageInfoBuf->ImageTypeId)) {
if (HardwareInstances[Index].HardwareInstance == FmpHardwareInstance) {
DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Duplicate firmware image descriptor with GUID %g HardwareInstance:0x%x\n", &FmpImageInfoBuf->ImageTypeId, FmpHardwareInstance));
ASSERT (
!CompareGuid (&HardwareInstances[Index].ImageTypeGuid, &FmpImageInfoBuf->ImageTypeId) ||
HardwareInstances[Index].HardwareInstance != FmpHardwareInstance
);
return EFI_UNSUPPORTED;
}
}
}
}
//
// Record new GUID/HardwareInstance pair
//
CopyGuid (&HardwareInstances[*NumberOfDescriptors].ImageTypeGuid, &FmpImageInfoBuf->ImageTypeId);
HardwareInstances[*NumberOfDescriptors].HardwareInstance = FmpHardwareInstance;
*NumberOfDescriptors = *NumberOfDescriptors + 1;
DEBUG ((DEBUG_INFO, "EsrtFmpDxe: Add new image descriptor with GUID %g HardwareInstance:0x%x\n", &FmpImageInfoBuf->ImageTypeId, FmpHardwareInstance));
//
// Check to see if GUID is already in the ESRT table
//
Entry = (EFI_SYSTEM_RESOURCE_ENTRY *)(Table + 1);
for (Index = 0; Index < Table->FwResourceCount; Index++, Entry++) {
if (!CompareGuid (&Entry->FwClass, &FmpImageInfoBuf->ImageTypeId)) {
continue;
}
DEBUG ((DEBUG_INFO, "EsrtFmpDxe: ESRT Entry already exists for FMP Instance with GUID %g\n", &Entry->FwClass));
//
// Set ESRT FwVersion to the smaller of the two values
//
Entry->FwVersion = MIN (FmpImageInfoBuf->Version, Entry->FwVersion);
//
// VERSION 2 has Lowest Supported
//
if (FmpVersion >= 2) {
//
// Set ESRT LowestSupportedFwVersion to the smaller of the two values
//
Entry->LowestSupportedFwVersion =
MIN (
FmpImageInfoBuf->LowestSupportedImageVersion,
Entry->LowestSupportedFwVersion
);
}
//
// VERSION 3 supports last attempt values
//
if (FmpVersion >= 3) {
//
// Update the ESRT entry with the last attempt status and last attempt
// version from the first FMP instance whose last attempt status is not
// SUCCESS. If all FMP instances are SUCCESS, then set version to the
// smallest value from all FMP instances.
//
if (Entry->LastAttemptStatus == LAST_ATTEMPT_STATUS_SUCCESS) {
if (FmpImageInfoBuf->LastAttemptStatus != LAST_ATTEMPT_STATUS_SUCCESS) {
Entry->LastAttemptStatus = FmpImageInfoBuf->LastAttemptStatus;
Entry->LastAttemptVersion = FmpImageInfoBuf->LastAttemptVersion;
} else {
Entry->LastAttemptVersion =
MIN (
FmpImageInfoBuf->LastAttemptVersion,
Entry->LastAttemptVersion
);
}
}
}
return EFI_SUCCESS;
}
//
// Add a new ESRT Table Entry
//
Entry = (EFI_SYSTEM_RESOURCE_ENTRY *)(Table + 1) + Table->FwResourceCount;
CopyGuid (&Entry->FwClass, &FmpImageInfoBuf->ImageTypeId);
if (IsSystemFmp (FmpImageInfoBuf)) {
DEBUG ((DEBUG_INFO, "EsrtFmpDxe: Found an ESRT entry for a System Device.\n"));
Entry->FwType = (UINT32)(ESRT_FW_TYPE_SYSTEMFIRMWARE);
} else {
Entry->FwType = (UINT32)(ESRT_FW_TYPE_DEVICEFIRMWARE);
}
Entry->FwVersion = FmpImageInfoBuf->Version;
Entry->LowestSupportedFwVersion = 0;
Entry->CapsuleFlags = 0;
Entry->LastAttemptVersion = 0;
Entry->LastAttemptStatus = 0;
//
// VERSION 2 has Lowest Supported
//
if (FmpVersion >= 2) {
Entry->LowestSupportedFwVersion = FmpImageInfoBuf->LowestSupportedImageVersion;
}
//
// VERSION 3 supports last attempt values
//
if (FmpVersion >= 3) {
Entry->LastAttemptVersion = FmpImageInfoBuf->LastAttemptVersion;
Entry->LastAttemptStatus = FmpImageInfoBuf->LastAttemptStatus;
}
//
// Increment the number of active ESRT Table Entries
//
Table->FwResourceCount++;
return EFI_SUCCESS;
}
/**
Function to retrieve the EFI_FIRMWARE_IMAGE_DESCRIPTOR from an FMP Instance.
The returned buffer is allocated using AllocatePool() and must be freed by the
caller using FreePool().
@param[in] Fmp Pointer to an EFI_FIRMWARE_MANAGEMENT_PROTOCOL.
@param[out] FmpImageInfoDescriptorVer Pointer to the version number associated
with the returned EFI_FIRMWARE_IMAGE_DESCRIPTOR.
@param[out] FmpImageInfoCount Pointer to the number of the returned
EFI_FIRMWARE_IMAGE_DESCRIPTORs.
@param[out] DescriptorSize Pointer to the size, in bytes, of each
returned EFI_FIRMWARE_IMAGE_DESCRIPTOR.
@return Pointer to the retrieved EFI_FIRMWARE_IMAGE_DESCRIPTOR. If the
descriptor can not be retrieved, then NULL is returned.
**/
EFI_FIRMWARE_IMAGE_DESCRIPTOR *
FmpGetFirmwareImageDescriptor (
IN EFI_FIRMWARE_MANAGEMENT_PROTOCOL *Fmp,
OUT UINT32 *FmpImageInfoDescriptorVer,
OUT UINT8 *FmpImageInfoCount,
OUT UINTN *DescriptorSize
)
{
EFI_STATUS Status;
UINTN ImageInfoSize;
UINT32 PackageVersion;
CHAR16 *PackageVersionName;
EFI_FIRMWARE_IMAGE_DESCRIPTOR *FmpImageInfoBuf;
ImageInfoSize = 0;
Status = Fmp->GetImageInfo (
Fmp, // FMP Pointer
&ImageInfoSize, // Buffer Size (in this case 0)
NULL, // NULL so we can get size
FmpImageInfoDescriptorVer, // DescriptorVersion
FmpImageInfoCount, // DescriptorCount
DescriptorSize, // DescriptorSize
&PackageVersion, // PackageVersion
&PackageVersionName // PackageVersionName
);
if (Status != EFI_BUFFER_TOO_SMALL) {
DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Unexpected Failure in GetImageInfo. Status = %r\n", Status));
return NULL;
}
FmpImageInfoBuf = AllocateZeroPool (ImageInfoSize);
if (FmpImageInfoBuf == NULL) {
DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Failed to get memory for FMP descriptor.\n"));
return NULL;
}
PackageVersionName = NULL;
Status = Fmp->GetImageInfo (
Fmp, // FMP Pointer
&ImageInfoSize, // ImageInfoSize
FmpImageInfoBuf, // ImageInfo
FmpImageInfoDescriptorVer, // DescriptorVersion
FmpImageInfoCount, // DescriptorCount
DescriptorSize, // DescriptorSize
&PackageVersion, // PackageVersion
&PackageVersionName // PackageVersionName
);
if (PackageVersionName != NULL) {
FreePool (PackageVersionName);
}
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Failure in GetImageInfo. Status = %r\n", Status));
FreePool (FmpImageInfoBuf);
return NULL;
}
return FmpImageInfoBuf;
}
/**
Function to create ESRT based on FMP Instances.
Create ESRT table, get the descriptors from FMP Instance and
create ESRT entries (ESRE).
@return Pointer to the ESRT created.
**/
EFI_SYSTEM_RESOURCE_TABLE *
CreateFmpBasedEsrt (
VOID
)
{
EFI_STATUS Status;
UINTN NoProtocols;
VOID **Buffer;
UINTN Index;
UINT32 FmpImageInfoDescriptorVer;
UINT8 FmpImageInfoCount;
UINTN DescriptorSize;
UINT32 NumberOfDescriptors;
EFI_FIRMWARE_IMAGE_DESCRIPTOR *FmpImageInfoBuf;
EFI_FIRMWARE_IMAGE_DESCRIPTOR *OrgFmpImageInfoBuf;
EFI_SYSTEM_RESOURCE_TABLE *Table;
GUID_HARDWAREINSTANCE_PAIR *HardwareInstances;
Status = EFI_SUCCESS;
NoProtocols = 0;
Buffer = NULL;
FmpImageInfoBuf = NULL;
OrgFmpImageInfoBuf = NULL;
Table = NULL;
HardwareInstances = NULL;
Status = EfiLocateProtocolBuffer (
&gEfiFirmwareManagementProtocolGuid,
&NoProtocols,
&Buffer
);
if (EFI_ERROR (Status) || (Buffer == NULL)) {
return NULL;
}
//
// Count the total number of EFI_FIRMWARE_IMAGE_DESCRIPTORs
//
for (Index = 0, NumberOfDescriptors = 0; Index < NoProtocols; Index++) {
FmpImageInfoBuf = FmpGetFirmwareImageDescriptor (
(EFI_FIRMWARE_MANAGEMENT_PROTOCOL *)Buffer[Index],
&FmpImageInfoDescriptorVer,
&FmpImageInfoCount,
&DescriptorSize
);
if (FmpImageInfoBuf != NULL) {
NumberOfDescriptors += FmpImageInfoCount;
FreePool (FmpImageInfoBuf);
}
}
//
// Allocate ESRT Table and GUID/HardwareInstance table
//
Table = AllocateZeroPool (
(NumberOfDescriptors * sizeof (EFI_SYSTEM_RESOURCE_ENTRY)) + sizeof (EFI_SYSTEM_RESOURCE_TABLE)
);
if (Table == NULL) {
DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Failed to allocate memory for ESRT.\n"));
FreePool (Buffer);
return NULL;
}
HardwareInstances = AllocateZeroPool (NumberOfDescriptors * sizeof (GUID_HARDWAREINSTANCE_PAIR));
if (HardwareInstances == NULL) {
DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Failed to allocate memory for HW Instance Table.\n"));
FreePool (Table);
FreePool (Buffer);
return NULL;
}
//
// Initialize ESRT Table
//
Table->FwResourceCount = 0;
Table->FwResourceCountMax = NumberOfDescriptors;
Table->FwResourceVersion = EFI_SYSTEM_RESOURCE_TABLE_FIRMWARE_RESOURCE_VERSION;
NumberOfDescriptors = 0;
for (Index = 0; Index < NoProtocols; Index++) {
FmpImageInfoBuf = FmpGetFirmwareImageDescriptor (
(EFI_FIRMWARE_MANAGEMENT_PROTOCOL *)Buffer[Index],
&FmpImageInfoDescriptorVer,
&FmpImageInfoCount,
&DescriptorSize
);
if (FmpImageInfoBuf == NULL) {
continue;
}
//
// Check each descriptor and read from the one specified
//
OrgFmpImageInfoBuf = FmpImageInfoBuf;
while (FmpImageInfoCount > 0) {
//
// If the descriptor has the IN USE bit set, create ESRT entry otherwise ignore.
//
if ((FmpImageInfoBuf->AttributesSetting & FmpImageInfoBuf->AttributesSupported & IMAGE_ATTRIBUTE_IN_USE) == IMAGE_ATTRIBUTE_IN_USE) {
//
// Create ESRT entry
//
CreateEsrtEntry (Table, HardwareInstances, &NumberOfDescriptors, FmpImageInfoBuf, FmpImageInfoDescriptorVer);
}
FmpImageInfoCount--;
//
// Increment the buffer pointer ahead by the size of the descriptor
//
FmpImageInfoBuf = (EFI_FIRMWARE_IMAGE_DESCRIPTOR *)(((UINT8 *)FmpImageInfoBuf) + DescriptorSize);
}
FreePool (OrgFmpImageInfoBuf);
OrgFmpImageInfoBuf = NULL;
}
FreePool (Buffer);
FreePool (HardwareInstances);
return Table;
}
/**
Notify function for event group EFI_EVENT_GROUP_READY_TO_BOOT. This is used to
install the Efi System Resource Table.
@param[in] Event The Event that is being processed.
@param[in] Context The Event Context.
**/
VOID
EFIAPI
EsrtReadyToBootEventNotify (
IN EFI_EVENT Event,
IN VOID *Context
)
{
EFI_STATUS Status;
EFI_SYSTEM_RESOURCE_TABLE *Table;
Table = CreateFmpBasedEsrt ();
if (Table != NULL) {
//
// Print table on debug builds
//
DEBUG_CODE_BEGIN ();
PrintTable (Table);
DEBUG_CODE_END ();
Status = InstallEfiSystemResourceTableInUefiConfigurationTable (Table);
if (EFI_ERROR (Status)) {
FreePool (Table);
}
} else {
DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Can't install ESRT table because it is NULL. \n"));
}
//
// Close the event to prevent it be signalled again.
//
gBS->CloseEvent (Event);
}
/**
The module Entry Point of the Efi System Resource Table DXE driver.
@param[in] ImageHandle The firmware allocated handle for the EFI image.
@param[in] SystemTable A pointer to the EFI System Table.
@retval EFI_SUCCESS The entry point is executed successfully.
@retval Other Some error occurs when executing this entry point.
**/
EFI_STATUS
EFIAPI
EsrtFmpEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_EVENT EsrtReadyToBootEvent;
//
// Register notify function to install ESRT on ReadyToBoot Event.
//
Status = EfiCreateEventReadyToBootEx (
TPL_CALLBACK,
EsrtReadyToBootEventNotify,
NULL,
&EsrtReadyToBootEvent
);
ASSERT_EFI_ERROR (Status);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "EsrtFmpDxe: Failed to register for ready to boot\n"));
}
return Status;
}