/** @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; }