/** @file Esrt management module. Copyright (c) 2015 - 2018, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "EsrtImpl.h" // // Module globals. // ESRT_PRIVATE_DATA mPrivate; ESRT_MANAGEMENT_PROTOCOL mEsrtManagementProtocolTemplate = { EsrtDxeGetEsrtEntry, EsrtDxeUpdateEsrtEntry, EsrtDxeRegisterEsrtEntry, EsrtDxeUnRegisterEsrtEntry, EsrtDxeSyncFmp, EsrtDxeLockEsrtRepository }; /** Get ESRT entry from ESRT Cache by FwClass Guid @param[in] FwClass FwClass of Esrt entry to get @param[in, out] Entry Esrt entry returned @retval EFI_SUCCESS The variable saving this Esrt Entry exists. @retval EF_NOT_FOUND No correct variable found. @retval EFI_WRITE_PROTECTED ESRT Cache repository is locked **/ EFI_STATUS EFIAPI EsrtDxeGetEsrtEntry( IN EFI_GUID *FwClass, IN OUT EFI_SYSTEM_RESOURCE_ENTRY *Entry ) { EFI_STATUS Status; if (FwClass == NULL || Entry == NULL) { return EFI_INVALID_PARAMETER; } Status = EfiAcquireLockOrFail (&mPrivate.NonFmpLock); if (EFI_ERROR (Status)) { return Status; } // // Find in Non-FMP Cached Esrt Repository // Status = GetEsrtEntry( FwClass, ESRT_FROM_NONFMP, Entry ); EfiReleaseLock(&mPrivate.NonFmpLock); if (EFI_ERROR(Status)) { Status = EfiAcquireLockOrFail (&mPrivate.FmpLock); if (EFI_ERROR (Status)) { return Status; } // // Find in FMP Cached Esrt NV Variable // Status = GetEsrtEntry( FwClass, ESRT_FROM_FMP, Entry ); EfiReleaseLock(&mPrivate.FmpLock); } return Status; } /** Update one ESRT entry in ESRT Cache. @param[in] Entry Esrt entry to be updated @retval EFI_SUCCESS Successfully update an ESRT entry in cache. @retval EFI_INVALID_PARAMETER Entry does't exist in ESRT Cache @retval EFI_WRITE_PROTECTED ESRT Cache repositoy is locked **/ EFI_STATUS EFIAPI EsrtDxeUpdateEsrtEntry( IN EFI_SYSTEM_RESOURCE_ENTRY *Entry ) { EFI_STATUS Status; if (Entry == NULL) { return EFI_INVALID_PARAMETER; } Status = EfiAcquireLockOrFail (&mPrivate.FmpLock); if (EFI_ERROR (Status)) { return Status; } Status = UpdateEsrtEntry(Entry, ESRT_FROM_FMP); if (!EFI_ERROR(Status)) { EfiReleaseLock(&mPrivate.FmpLock); return Status; } EfiReleaseLock(&mPrivate.FmpLock); Status = EfiAcquireLockOrFail (&mPrivate.NonFmpLock); if (EFI_ERROR (Status)) { return Status; } Status = UpdateEsrtEntry(Entry, ESRT_FROM_NONFMP); EfiReleaseLock(&mPrivate.NonFmpLock); return Status; } /** Non-FMP instance to unregister Esrt Entry from ESRT Cache. @param[in] FwClass FwClass of Esrt entry to Unregister @retval EFI_SUCCESS Insert all entries Successfully @retval EFI_NOT_FOUND Entry of FwClass does not exsit **/ EFI_STATUS EFIAPI EsrtDxeUnRegisterEsrtEntry( IN EFI_GUID *FwClass ) { EFI_STATUS Status; if (FwClass == NULL) { return EFI_INVALID_PARAMETER; } Status = EfiAcquireLockOrFail (&mPrivate.NonFmpLock); if (EFI_ERROR (Status)) { return Status; } Status = DeleteEsrtEntry(FwClass, ESRT_FROM_NONFMP); EfiReleaseLock(&mPrivate.NonFmpLock); return Status; } /** Non-FMP instance to register one ESRT entry into ESRT Cache. @param[in] Entry Esrt entry to be set @retval EFI_SUCCESS Successfully set a variable. @retval EFI_INVALID_PARAMETER ESRT Entry is already exist @retval EFI_OUT_OF_RESOURCES Non-FMP ESRT repository is full **/ EFI_STATUS EFIAPI EsrtDxeRegisterEsrtEntry( IN EFI_SYSTEM_RESOURCE_ENTRY *Entry ) { EFI_STATUS Status; EFI_SYSTEM_RESOURCE_ENTRY EsrtEntryTmp; if (Entry == NULL) { return EFI_INVALID_PARAMETER; } Status = EfiAcquireLockOrFail (&mPrivate.NonFmpLock); if (EFI_ERROR (Status)) { return Status; } Status = GetEsrtEntry( &Entry->FwClass, ESRT_FROM_NONFMP, &EsrtEntryTmp ); if (Status == EFI_NOT_FOUND) { Status = InsertEsrtEntry(Entry, ESRT_FROM_NONFMP); } EfiReleaseLock(&mPrivate.NonFmpLock); return Status; } /** This function syn up Cached ESRT with data from FMP instances Function should be called after Connect All in order to locate all FMP protocols installed. @retval EFI_SUCCESS Successfully sync cache repository from FMP instances @retval EFI_NOT_FOUND No FMP Instance are found @retval EFI_OUT_OF_RESOURCES Resource allocaton fail **/ EFI_STATUS EFIAPI EsrtDxeSyncFmp( VOID ) { EFI_STATUS Status; UINTN Index1; UINTN Index2; UINTN Index3; EFI_HANDLE *HandleBuffer; EFI_FIRMWARE_MANAGEMENT_PROTOCOL **FmpBuf; UINTN NumberOfHandles; UINTN *DescriptorSizeBuf; EFI_FIRMWARE_IMAGE_DESCRIPTOR **FmpImageInfoBuf; EFI_FIRMWARE_IMAGE_DESCRIPTOR *TempFmpImageInfo; UINT8 *FmpImageInfoCountBuf; UINT32 *FmpImageInfoDescriptorVerBuf; UINTN ImageInfoSize; UINT32 PackageVersion; CHAR16 *PackageVersionName; EFI_SYSTEM_RESOURCE_ENTRY *EsrtRepositoryNew; UINTN EntryNumNew; NumberOfHandles = 0; EntryNumNew = 0; FmpBuf = NULL; HandleBuffer = NULL; FmpImageInfoBuf = NULL; FmpImageInfoCountBuf = NULL; PackageVersionName = NULL; DescriptorSizeBuf = NULL; FmpImageInfoDescriptorVerBuf = NULL; EsrtRepositoryNew = NULL; // // Get image information from all FMP protocol // Status = gBS->LocateHandleBuffer ( ByProtocol, &gEfiFirmwareManagementProtocolGuid, NULL, &NumberOfHandles, &HandleBuffer ); if (Status == EFI_NOT_FOUND) { EntryNumNew = 0; goto UPDATE_REPOSITORY; } else if (EFI_ERROR(Status)){ goto END; } // // Allocate buffer to hold new FMP ESRT Cache repository // EsrtRepositoryNew = AllocateZeroPool(PcdGet32(PcdMaxFmpEsrtCacheNum) * sizeof(EFI_SYSTEM_RESOURCE_ENTRY)); if (EsrtRepositoryNew == NULL) { Status = EFI_OUT_OF_RESOURCES; goto END; } FmpBuf = AllocatePool(sizeof(EFI_FIRMWARE_MANAGEMENT_PROTOCOL *) * NumberOfHandles); if (FmpBuf == NULL) { Status = EFI_OUT_OF_RESOURCES; goto END; } FmpImageInfoBuf = AllocateZeroPool(sizeof(EFI_FIRMWARE_IMAGE_DESCRIPTOR *) * NumberOfHandles); if (FmpImageInfoBuf == NULL) { Status = EFI_OUT_OF_RESOURCES; goto END; } FmpImageInfoCountBuf = AllocateZeroPool(sizeof(UINT8) * NumberOfHandles); if (FmpImageInfoCountBuf == NULL) { Status = EFI_OUT_OF_RESOURCES; goto END; } DescriptorSizeBuf = AllocateZeroPool(sizeof(UINTN) * NumberOfHandles); if (DescriptorSizeBuf == NULL) { Status = EFI_OUT_OF_RESOURCES; goto END; } FmpImageInfoDescriptorVerBuf = AllocateZeroPool(sizeof(UINT32) * NumberOfHandles); if (FmpImageInfoDescriptorVerBuf == NULL) { Status = EFI_OUT_OF_RESOURCES; goto END; } // // Get all FmpImageInfo Descriptor into FmpImageInfoBuf // for (Index1 = 0; Index1 < NumberOfHandles; Index1++){ Status = gBS->HandleProtocol( HandleBuffer[Index1], &gEfiFirmwareManagementProtocolGuid, (VOID **)&FmpBuf[Index1] ); if (EFI_ERROR(Status)) { continue; } ImageInfoSize = 0; Status = FmpBuf[Index1]->GetImageInfo ( FmpBuf[Index1], &ImageInfoSize, NULL, NULL, NULL, NULL, NULL, NULL ); if (Status == EFI_BUFFER_TOO_SMALL) { FmpImageInfoBuf[Index1] = AllocateZeroPool(ImageInfoSize); if (FmpImageInfoBuf[Index1] == NULL) { Status = EFI_OUT_OF_RESOURCES; goto END; } } else { continue; } PackageVersionName = NULL; Status = FmpBuf[Index1]->GetImageInfo ( FmpBuf[Index1], &ImageInfoSize, FmpImageInfoBuf[Index1], &FmpImageInfoDescriptorVerBuf[Index1], &FmpImageInfoCountBuf[Index1], &DescriptorSizeBuf[Index1], &PackageVersion, &PackageVersionName ); // // If FMP GetInformation interface failed, skip this resource // if (EFI_ERROR(Status)){ FmpImageInfoCountBuf[Index1] = 0; continue; } if (PackageVersionName != NULL) { FreePool(PackageVersionName); } } // // Create new FMP cache repository based on FmpImageInfoBuf // for (Index2 = 0; Index2 < NumberOfHandles; Index2++){ TempFmpImageInfo = FmpImageInfoBuf[Index2]; for (Index3 = 0; Index3 < FmpImageInfoCountBuf[Index2]; Index3++){ if ((TempFmpImageInfo->AttributesSupported & IMAGE_ATTRIBUTE_IN_USE) != 0 && (TempFmpImageInfo->AttributesSetting & IMAGE_ATTRIBUTE_IN_USE) != 0){ // // Always put the first smallest version of Image info into ESRT cache // for(Index1 = 0; Index1 < EntryNumNew; Index1++) { if (CompareGuid(&EsrtRepositoryNew[Index1].FwClass, &TempFmpImageInfo->ImageTypeId)) { if(EsrtRepositoryNew[Index1].FwVersion > TempFmpImageInfo->Version) { SetEsrtEntryFromFmpInfo(&EsrtRepositoryNew[Index1], TempFmpImageInfo, FmpImageInfoDescriptorVerBuf[Index2]); } break; } } // // New ImageTypeId can't be found in EsrtRepositoryNew. Create a new one // if (Index1 == EntryNumNew){ SetEsrtEntryFromFmpInfo(&EsrtRepositoryNew[EntryNumNew], TempFmpImageInfo, FmpImageInfoDescriptorVerBuf[Index2]); EntryNumNew++; if (EntryNumNew >= PcdGet32(PcdMaxFmpEsrtCacheNum)) { break; } } } // // Use DescriptorSize to move ImageInfo Pointer to stay compatible with different ImageInfo version // TempFmpImageInfo = (EFI_FIRMWARE_IMAGE_DESCRIPTOR *)((UINT8 *)TempFmpImageInfo + DescriptorSizeBuf[Index2]); } } UPDATE_REPOSITORY: Status = EfiAcquireLockOrFail (&mPrivate.FmpLock); if (EFI_ERROR (Status)) { return Status; } Status = gRT->SetVariable( EFI_ESRT_FMP_VARIABLE_NAME, &gEfiCallerIdGuid, EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS, EntryNumNew * sizeof(EFI_SYSTEM_RESOURCE_ENTRY), EsrtRepositoryNew ); EfiReleaseLock(&mPrivate.FmpLock); END: if (EsrtRepositoryNew != NULL) { FreePool(EsrtRepositoryNew); } if (HandleBuffer != NULL) { FreePool(HandleBuffer); } if (FmpBuf != NULL) { FreePool(FmpBuf); } if (FmpImageInfoCountBuf != NULL) { FreePool(FmpImageInfoCountBuf); } if (DescriptorSizeBuf != NULL) { FreePool(DescriptorSizeBuf); } if (FmpImageInfoDescriptorVerBuf != NULL) { FreePool(FmpImageInfoDescriptorVerBuf); } if (FmpImageInfoBuf != NULL) { for (Index1 = 0; Index1 < NumberOfHandles; Index1++){ if (FmpImageInfoBuf[Index1] != NULL) { FreePool(FmpImageInfoBuf[Index1]); } } FreePool(FmpImageInfoBuf); } return Status; } /** This function locks up Esrt repository to be readonly. It should be called before gEfiEndOfDxeEventGroupGuid event signaled @retval EFI_SUCCESS Locks up FMP Non-FMP repository successfully **/ EFI_STATUS EFIAPI EsrtDxeLockEsrtRepository( VOID ) { EFI_STATUS Status; EDKII_VARIABLE_LOCK_PROTOCOL *VariableLock; // // Mark ACPI_GLOBAL_VARIABLE variable to read-only if the Variable Lock protocol exists // Status = gBS->LocateProtocol (&gEdkiiVariableLockProtocolGuid, NULL, (VOID **) &VariableLock); if (!EFI_ERROR (Status)) { Status = VariableLock->RequestToLock (VariableLock, EFI_ESRT_FMP_VARIABLE_NAME, &gEfiCallerIdGuid); DEBUG((EFI_D_INFO, "EsrtDxe Lock EsrtFmp Variable Status 0x%x", Status)); Status = VariableLock->RequestToLock (VariableLock, EFI_ESRT_NONFMP_VARIABLE_NAME, &gEfiCallerIdGuid); DEBUG((EFI_D_INFO, "EsrtDxe Lock EsrtNonFmp Variable Status 0x%x", Status)); } return Status; } /** Notify function for event group EFI_EVENT_GROUP_READY_TO_BOOT. This is used to install the Esrt Table into system configuration 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 *EsrtTable; EFI_SYSTEM_RESOURCE_ENTRY *FmpEsrtRepository; EFI_SYSTEM_RESOURCE_ENTRY *NonFmpEsrtRepository; UINTN FmpRepositorySize; UINTN NonFmpRepositorySize; FmpEsrtRepository = NULL; NonFmpEsrtRepository = NULL; FmpRepositorySize = 0; NonFmpRepositorySize = 0; Status = EfiAcquireLockOrFail (&mPrivate.NonFmpLock); if (EFI_ERROR (Status)) { return; } Status = GetVariable2 ( EFI_ESRT_NONFMP_VARIABLE_NAME, &gEfiCallerIdGuid, (VOID **) &NonFmpEsrtRepository, &NonFmpRepositorySize ); if (EFI_ERROR(Status)) { NonFmpRepositorySize = 0; } if (NonFmpRepositorySize % sizeof(EFI_SYSTEM_RESOURCE_ENTRY) != 0) { DEBUG((EFI_D_ERROR, "NonFmp Repository Corrupt. Need to rebuild NonFmp Repository.\n")); NonFmpRepositorySize = 0; } EfiReleaseLock(&mPrivate.NonFmpLock); Status = EfiAcquireLockOrFail (&mPrivate.FmpLock); Status = GetVariable2 ( EFI_ESRT_FMP_VARIABLE_NAME, &gEfiCallerIdGuid, (VOID **) &FmpEsrtRepository, &FmpRepositorySize ); if (EFI_ERROR(Status)) { FmpRepositorySize = 0; } if (FmpRepositorySize % sizeof(EFI_SYSTEM_RESOURCE_ENTRY) != 0) { DEBUG((EFI_D_ERROR, "Fmp Repository Corrupt. Need to rebuild Fmp Repository.\n")); FmpRepositorySize = 0; } EfiReleaseLock(&mPrivate.FmpLock); // // Skip ESRT table publish if no ESRT entry exists // if (NonFmpRepositorySize + FmpRepositorySize == 0) { goto EXIT; } EsrtTable = AllocatePool(sizeof(EFI_SYSTEM_RESOURCE_TABLE) + NonFmpRepositorySize + FmpRepositorySize); if (EsrtTable == NULL) { DEBUG ((EFI_D_ERROR, "Esrt table memory allocation failure\n")); goto EXIT; } EsrtTable->FwResourceVersion = EFI_SYSTEM_RESOURCE_TABLE_FIRMWARE_RESOURCE_VERSION; EsrtTable->FwResourceCount = (UINT32)((NonFmpRepositorySize + FmpRepositorySize) / sizeof(EFI_SYSTEM_RESOURCE_ENTRY)); EsrtTable->FwResourceCountMax = PcdGet32(PcdMaxNonFmpEsrtCacheNum) + PcdGet32(PcdMaxFmpEsrtCacheNum); if (NonFmpRepositorySize != 0 && NonFmpEsrtRepository != NULL) { CopyMem(EsrtTable + 1, NonFmpEsrtRepository, NonFmpRepositorySize); } if (FmpRepositorySize != 0 && FmpEsrtRepository != NULL) { CopyMem((UINT8 *)(EsrtTable + 1) + NonFmpRepositorySize, FmpEsrtRepository, FmpRepositorySize); } // // Publish Esrt to system config table // Status = gBS->InstallConfigurationTable (&gEfiSystemResourceTableGuid, EsrtTable); // // Only one successful install // gBS->CloseEvent(Event); EXIT: if (FmpEsrtRepository != NULL) { FreePool(FmpEsrtRepository); } if (NonFmpEsrtRepository != NULL) { FreePool(NonFmpEsrtRepository); } } /** The module Entry Point of the Esrt DXE driver that manages cached ESRT repository & publishes ESRT table @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 EsrtDxeEntryPoint ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; EfiInitializeLock (&mPrivate.FmpLock, TPL_CALLBACK); EfiInitializeLock (&mPrivate.NonFmpLock, TPL_CALLBACK); // // Install Esrt management Protocol // Status = gBS->InstallMultipleProtocolInterfaces ( &mPrivate.Handle, &gEsrtManagementProtocolGuid, &mEsrtManagementProtocolTemplate, NULL ); ASSERT_EFI_ERROR (Status); // // Register notify function to install Esrt Table on ReadyToBoot Event. // Status = gBS->CreateEventEx ( EVT_NOTIFY_SIGNAL, TPL_CALLBACK, EsrtReadyToBootEventNotify, NULL, &gEfiEventReadyToBootGuid, &mPrivate.Event ); ASSERT_EFI_ERROR (Status); return EFI_SUCCESS; }