diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Devices/EFI/Firmware/MdeModulePkg/Library/DxeCapsuleLibFmp/CapsuleOnDisk.c | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/MdeModulePkg/Library/DxeCapsuleLibFmp/CapsuleOnDisk.c')
-rw-r--r-- | src/VBox/Devices/EFI/Firmware/MdeModulePkg/Library/DxeCapsuleLibFmp/CapsuleOnDisk.c | 1975 |
1 files changed, 1975 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Library/DxeCapsuleLibFmp/CapsuleOnDisk.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Library/DxeCapsuleLibFmp/CapsuleOnDisk.c new file mode 100644 index 00000000..6f28a92c --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Library/DxeCapsuleLibFmp/CapsuleOnDisk.c @@ -0,0 +1,1975 @@ +/** @file + The implementation supports Capusle on Disk. + + Copyright (c) 2019, Intel Corporation. All rights reserved.<BR> + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "CapsuleOnDisk.h" + +/** + Return if this capsule is a capsule name capsule, based upon CapsuleHeader. + + @param[in] CapsuleHeader A pointer to EFI_CAPSULE_HEADER + + @retval TRUE It is a capsule name capsule. + @retval FALSE It is not a capsule name capsule. +**/ +BOOLEAN +IsCapsuleNameCapsule ( + IN EFI_CAPSULE_HEADER *CapsuleHeader + ); + +/** + Check the integrity of the capsule name capsule. + If the capsule is vaild, return the physical address of each capsule name string. + + This routine assumes the capsule has been validated by IsValidCapsuleHeader(), so + capsule memory overflow is not going to happen in this routine. + + @param[in] CapsuleHeader Pointer to the capsule header of a capsule name capsule. + @param[out] CapsuleNameNum Number of capsule name. + + @retval NULL Capsule name capsule is not valid. + @retval CapsuleNameBuf Array of capsule name physical address. + +**/ +EFI_PHYSICAL_ADDRESS * +ValidateCapsuleNameCapsuleIntegrity ( + IN EFI_CAPSULE_HEADER *CapsuleHeader, + OUT UINTN *CapsuleNameNum + ) +{ + UINT8 *CapsuleNamePtr; + UINT8 *CapsuleNameBufStart; + UINT8 *CapsuleNameBufEnd; + UINTN Index; + UINTN StringSize; + EFI_PHYSICAL_ADDRESS *CapsuleNameBuf; + + if (!IsCapsuleNameCapsule (CapsuleHeader)) { + return NULL; + } + + // + // Total string size must be even. + // + if (((CapsuleHeader->CapsuleImageSize - CapsuleHeader->HeaderSize) & BIT0) != 0) { + return NULL; + } + + *CapsuleNameNum = 0; + Index = 0; + CapsuleNameBufStart = (UINT8 *) CapsuleHeader + CapsuleHeader->HeaderSize; + + // + // If strings are not aligned on a 16-bit boundary, reallocate memory for it. + // + if (((UINTN) CapsuleNameBufStart & BIT0) != 0) { + CapsuleNameBufStart = AllocateCopyPool (CapsuleHeader->CapsuleImageSize - CapsuleHeader->HeaderSize, CapsuleNameBufStart); + if (CapsuleNameBufStart == NULL) { + return NULL; + } + } + + CapsuleNameBufEnd = CapsuleNameBufStart + CapsuleHeader->CapsuleImageSize - CapsuleHeader->HeaderSize; + + CapsuleNamePtr = CapsuleNameBufStart; + while (CapsuleNamePtr < CapsuleNameBufEnd) { + StringSize= StrnSizeS ((CHAR16 *) CapsuleNamePtr, (CapsuleNameBufEnd - CapsuleNamePtr)/sizeof(CHAR16)); + CapsuleNamePtr += StringSize; + (*CapsuleNameNum) ++; + } + + // + // Integrity check. + // + if (CapsuleNamePtr != CapsuleNameBufEnd) { + if (CapsuleNameBufStart != (UINT8 *)CapsuleHeader + CapsuleHeader->HeaderSize) { + FreePool (CapsuleNameBufStart); + } + return NULL; + } + + CapsuleNameBuf = AllocatePool (*CapsuleNameNum * sizeof (EFI_PHYSICAL_ADDRESS)); + if (CapsuleNameBuf == NULL) { + if (CapsuleNameBufStart != (UINT8 *)CapsuleHeader + CapsuleHeader->HeaderSize) { + FreePool (CapsuleNameBufStart); + } + return NULL; + } + + CapsuleNamePtr = CapsuleNameBufStart; + while (CapsuleNamePtr < CapsuleNameBufEnd) { + StringSize= StrnSizeS ((CHAR16 *) CapsuleNamePtr, (CapsuleNameBufEnd - CapsuleNamePtr)/sizeof(CHAR16)); + CapsuleNameBuf[Index] = (EFI_PHYSICAL_ADDRESS)(UINTN) CapsuleNamePtr; + CapsuleNamePtr += StringSize; + Index ++; + } + + return CapsuleNameBuf; +} + +/** + This routine is called to upper case given unicode string. + + @param[in] Str String to upper case + + @retval upper cased string after process + +**/ +static +CHAR16 * +UpperCaseString ( + IN CHAR16 *Str + ) +{ + CHAR16 *Cptr; + + for (Cptr = Str; *Cptr != L'\0'; Cptr++) { + if (L'a' <= *Cptr && *Cptr <= L'z') { + *Cptr = *Cptr - L'a' + L'A'; + } + } + + return Str; +} + +/** + This routine is used to return substring before period '.' or '\0' + Caller should respsonsible of substr space allocation & free + + @param[in] Str String to check + @param[out] SubStr First part of string before period or '\0' + @param[out] SubStrLen Length of first part of string + +**/ +static +VOID +GetSubStringBeforePeriod ( + IN CHAR16 *Str, + OUT CHAR16 *SubStr, + OUT UINTN *SubStrLen + ) +{ + UINTN Index; + for (Index = 0; Str[Index] != L'.' && Str[Index] != L'\0'; Index++) { + SubStr[Index] = Str[Index]; + } + + SubStr[Index] = L'\0'; + *SubStrLen = Index; +} + +/** + This routine pad the string in tail with input character. + + @param[in] StrBuf Str buffer to be padded, should be enough room for + @param[in] PadLen Expected padding length + @param[in] Character Character used to pad + +**/ +static +VOID +PadStrInTail ( + IN CHAR16 *StrBuf, + IN UINTN PadLen, + IN CHAR16 Character + ) +{ + UINTN Index; + + for (Index = 0; StrBuf[Index] != L'\0'; Index++); + + while(PadLen != 0) { + StrBuf[Index] = Character; + Index++; + PadLen--; + } + + StrBuf[Index] = L'\0'; +} + +/** + This routine find the offset of the last period '.' of string. If No period exists + function FileNameExtension is set to L'\0' + + @param[in] FileName File name to split between last period + @param[out] FileNameFirst First FileName before last period + @param[out] FileNameExtension FileName after last period + +**/ +static +VOID +SplitFileNameExtension ( + IN CHAR16 *FileName, + OUT CHAR16 *FileNameFirst, + OUT CHAR16 *FileNameExtension + ) +{ + UINTN Index; + UINTN StringLen; + + StringLen = StrnLenS(FileName, MAX_FILE_NAME_SIZE); + for (Index = StringLen; Index > 0 && FileName[Index] != L'.'; Index--); + + // + // No period exists. No FileName Extension + // + if (Index == 0 && FileName[Index] != L'.') { + FileNameExtension[0] = L'\0'; + Index = StringLen; + } else { + StrCpyS(FileNameExtension, MAX_FILE_NAME_SIZE, &FileName[Index+1]); + } + + // + // Copy First file name + // + StrnCpyS(FileNameFirst, MAX_FILE_NAME_SIZE, FileName, Index); + FileNameFirst[Index] = L'\0'; +} + +/** + This routine is called to get all boot options in the order determnined by: + 1. "OptionBuf" + 2. "BootOrder" + + @param[out] OptionBuf BootList buffer to all boot options returned + @param[out] OptionCount BootList count of all boot options returned + + @retval EFI_SUCCESS There is no error when processing capsule + +**/ +EFI_STATUS +GetBootOptionInOrder( + OUT EFI_BOOT_MANAGER_LOAD_OPTION **OptionBuf, + OUT UINTN *OptionCount + ) +{ + EFI_STATUS Status; + UINTN DataSize; + UINT16 BootNext; + CHAR16 BootOptionName[20]; + EFI_BOOT_MANAGER_LOAD_OPTION *BootOrderOptionBuf; + UINTN BootOrderCount; + EFI_BOOT_MANAGER_LOAD_OPTION BootNextOptionEntry; + UINTN BootNextCount; + EFI_BOOT_MANAGER_LOAD_OPTION *TempBuf; + + BootOrderOptionBuf = NULL; + TempBuf = NULL; + BootNextCount = 0; + BootOrderCount = 0; + *OptionBuf = NULL; + *OptionCount = 0; + + // + // First Get BootOption from "BootNext" + // + DataSize = sizeof(BootNext); + Status = gRT->GetVariable ( + EFI_BOOT_NEXT_VARIABLE_NAME, + &gEfiGlobalVariableGuid, + NULL, + &DataSize, + (VOID *)&BootNext + ); + // + // BootNext variable is a single UINT16 + // + if (!EFI_ERROR(Status) && DataSize == sizeof(UINT16)) { + // + // Add the boot next boot option + // + UnicodeSPrint (BootOptionName, sizeof (BootOptionName), L"Boot%04x", BootNext); + ZeroMem(&BootNextOptionEntry, sizeof(EFI_BOOT_MANAGER_LOAD_OPTION)); + Status = EfiBootManagerVariableToLoadOption (BootOptionName, &BootNextOptionEntry); + + if (!EFI_ERROR(Status)) { + BootNextCount = 1; + } + } + + // + // Second get BootOption from "BootOrder" + // + BootOrderOptionBuf = EfiBootManagerGetLoadOptions (&BootOrderCount, LoadOptionTypeBoot); + if (BootNextCount == 0 && BootOrderCount == 0) { + return EFI_NOT_FOUND; + } + + // + // At least one BootOption is found + // + TempBuf = AllocatePool(sizeof(EFI_BOOT_MANAGER_LOAD_OPTION) * (BootNextCount + BootOrderCount)); + if (TempBuf != NULL) { + if (BootNextCount == 1) { + CopyMem(TempBuf, &BootNextOptionEntry, sizeof(EFI_BOOT_MANAGER_LOAD_OPTION)); + } + + if (BootOrderCount > 0) { + CopyMem(TempBuf + BootNextCount, BootOrderOptionBuf, sizeof(EFI_BOOT_MANAGER_LOAD_OPTION) * BootOrderCount); + } + + *OptionBuf = TempBuf; + *OptionCount = BootNextCount + BootOrderCount; + Status = EFI_SUCCESS; + } else { + Status = EFI_OUT_OF_RESOURCES; + } + + FreePool(BootOrderOptionBuf); + + return Status; +} + +/** + This routine is called to get boot option by OptionNumber. + + @param[in] Number The OptionNumber of boot option + @param[out] OptionBuf BootList buffer to all boot options returned + + @retval EFI_SUCCESS There is no error when getting boot option + +**/ +EFI_STATUS +GetBootOptionByNumber( + IN UINT16 Number, + OUT EFI_BOOT_MANAGER_LOAD_OPTION **OptionBuf + ) +{ + EFI_STATUS Status; + CHAR16 BootOptionName[20]; + EFI_BOOT_MANAGER_LOAD_OPTION BootOption; + + UnicodeSPrint (BootOptionName, sizeof (BootOptionName), L"Boot%04x", Number); + ZeroMem (&BootOption, sizeof (EFI_BOOT_MANAGER_LOAD_OPTION)); + Status = EfiBootManagerVariableToLoadOption (BootOptionName, &BootOption); + + if (!EFI_ERROR (Status)) { + *OptionBuf = AllocatePool (sizeof (EFI_BOOT_MANAGER_LOAD_OPTION)); + if (*OptionBuf != NULL) { + CopyMem (*OptionBuf, &BootOption, sizeof (EFI_BOOT_MANAGER_LOAD_OPTION)); + Status = EFI_SUCCESS; + } else { + Status = EFI_OUT_OF_RESOURCES; + } + } + + return Status; +} + +/** + Get Active EFI System Partition within GPT based on device path. + + @param[in] DevicePath Device path to find a active EFI System Partition + @param[out] FsHandle BootList points to all boot options returned + + @retval EFI_SUCCESS Active EFI System Partition is succesfully found + @retval EFI_NOT_FOUND No Active EFI System Partition is found + +**/ +EFI_STATUS +GetEfiSysPartitionFromDevPath( + IN EFI_DEVICE_PATH_PROTOCOL *DevicePath, + OUT EFI_HANDLE *FsHandle + ) +{ + EFI_STATUS Status; + EFI_DEVICE_PATH_PROTOCOL *TempDevicePath; + HARDDRIVE_DEVICE_PATH *Hd; + EFI_HANDLE Handle; + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Fs; + + // + // Check if the device path contains GPT node + // + TempDevicePath = DevicePath; + while (!IsDevicePathEnd (TempDevicePath)) { + if ((DevicePathType (TempDevicePath) == MEDIA_DEVICE_PATH) && + (DevicePathSubType (TempDevicePath) == MEDIA_HARDDRIVE_DP)) { + Hd = (HARDDRIVE_DEVICE_PATH *)TempDevicePath; + if (Hd->MBRType == MBR_TYPE_EFI_PARTITION_TABLE_HEADER) { + break; + } + } + TempDevicePath = NextDevicePathNode (TempDevicePath); + } + + if (!IsDevicePathEnd (TempDevicePath)) { + // + // Search for EFI system partition protocol on full device path in Boot Option + // + Status = gBS->LocateDevicePath (&gEfiPartTypeSystemPartGuid, &DevicePath, &Handle); + + // + // Search for simple file system on this handler + // + if (!EFI_ERROR(Status)) { + Status = gBS->HandleProtocol(Handle, &gEfiSimpleFileSystemProtocolGuid, (VOID **)&Fs); + if (!EFI_ERROR(Status)) { + *FsHandle = Handle; + return EFI_SUCCESS; + } + } + } + + return EFI_NOT_FOUND; +} + +/** + This routine is called to get Simple File System protocol on the first EFI system partition found in + active boot option. The boot option list is detemined in order by + 1. "BootNext" + 2. "BootOrder" + + @param[in] MaxRetry Max Connection Retry. Stall 100ms between each connection try to ensure + device like USB can get enumerated. + @param[in, out] LoadOptionNumber On input, specify the boot option to get EFI system partition. + On output, return the OptionNumber of the boot option where EFI + system partition is got from. + @param[out] FsFsHandle Simple File System Protocol found on first active EFI system partition + + @retval EFI_SUCCESS Simple File System protocol found for EFI system partition + @retval EFI_NOT_FOUND No Simple File System protocol found for EFI system partition + +**/ +EFI_STATUS +GetEfiSysPartitionFromActiveBootOption( + IN UINTN MaxRetry, + IN OUT UINT16 **LoadOptionNumber, + OUT EFI_HANDLE *FsHandle + ) +{ + EFI_STATUS Status; + EFI_BOOT_MANAGER_LOAD_OPTION *BootOptionBuf; + UINTN BootOptionNum; + UINTN Index; + EFI_DEVICE_PATH_PROTOCOL *DevicePath; + EFI_DEVICE_PATH_PROTOCOL *CurFullPath; + EFI_DEVICE_PATH_PROTOCOL *PreFullPath; + + *FsHandle = NULL; + CurFullPath = NULL; + + if (*LoadOptionNumber != NULL) { + BootOptionNum = 1; + Status = GetBootOptionByNumber(**LoadOptionNumber, &BootOptionBuf); + if (EFI_ERROR(Status)) { + DEBUG ((DEBUG_ERROR, "GetBootOptionByIndex Failed %x! No BootOption available for connection\n", Status)); + return Status; + } + } else { + Status = GetBootOptionInOrder(&BootOptionBuf, &BootOptionNum); + if (EFI_ERROR(Status)) { + DEBUG ((DEBUG_ERROR, "GetBootOptionInOrder Failed %x! No BootOption available for connection\n", Status)); + return Status; + } + } + + // + // Search BootOptionList to check if it is an active boot option with EFI system partition + // 1. Connect device path + // 2. expend short/plug in devicepath + // 3. LoadImage + // + for (Index = 0; Index < BootOptionNum; Index++) { + // + // Get the boot option from the link list + // + DevicePath = BootOptionBuf[Index].FilePath; + + // + // Skip inactive or legacy boot options + // + if ((BootOptionBuf[Index].Attributes & LOAD_OPTION_ACTIVE) == 0 || + DevicePathType (DevicePath) == BBS_DEVICE_PATH) { + continue; + } + + DEBUG_CODE ( + CHAR16 *DevicePathStr; + + DevicePathStr = ConvertDevicePathToText(DevicePath, TRUE, TRUE); + if (DevicePathStr != NULL){ + DEBUG((DEBUG_INFO, "Try BootOption %s\n", DevicePathStr)); + FreePool(DevicePathStr); + } else { + DEBUG((DEBUG_INFO, "DevicePathToStr failed\n")); + } + ); + + CurFullPath = NULL; + // + // Try every full device Path generated from bootoption + // + do { + PreFullPath = CurFullPath; + CurFullPath = EfiBootManagerGetNextLoadOptionDevicePath(DevicePath, CurFullPath); + + if (PreFullPath != NULL) { + FreePool (PreFullPath); + } + + if (CurFullPath == NULL) { + // + // No Active EFI system partition is found in BootOption device path + // + Status = EFI_NOT_FOUND; + break; + } + + DEBUG_CODE ( + CHAR16 *DevicePathStr1; + + DevicePathStr1 = ConvertDevicePathToText(CurFullPath, TRUE, TRUE); + if (DevicePathStr1 != NULL){ + DEBUG((DEBUG_INFO, "Full device path %s\n", DevicePathStr1)); + FreePool(DevicePathStr1); + } + ); + + // + // Make sure the boot option device path connected. + // Only handle first device in boot option. Other optional device paths are described as OSV specific + // FullDevice could contain extra directory & file info. So don't check connection status here. + // + EfiBootManagerConnectDevicePath (CurFullPath, NULL); + Status = GetEfiSysPartitionFromDevPath(CurFullPath, FsHandle); + + // + // Some relocation device like USB need more time to get enumerated + // + while (EFI_ERROR(Status) && MaxRetry > 0) { + EfiBootManagerConnectDevicePath(CurFullPath, NULL); + + // + // Search for EFI system partition protocol on full device path in Boot Option + // + Status = GetEfiSysPartitionFromDevPath(CurFullPath, FsHandle); + if (!EFI_ERROR(Status)) { + break; + } + DEBUG((DEBUG_ERROR, "GetEfiSysPartitionFromDevPath Loop %x\n", Status)); + // + // Stall 100ms if connection failed to ensure USB stack is ready + // + gBS->Stall(100000); + MaxRetry --; + } + } while(EFI_ERROR(Status)); + + // + // Find a qualified Simple File System + // + if (!EFI_ERROR(Status)) { + break; + } + + } + + // + // Return the OptionNumber of the boot option where EFI system partition is got from + // + if (*LoadOptionNumber == NULL) { + *LoadOptionNumber = AllocateCopyPool (sizeof(UINT16), (UINT16 *) &BootOptionBuf[Index].OptionNumber); + if (*LoadOptionNumber == NULL) { + Status = EFI_OUT_OF_RESOURCES; + } + } + + // + // No qualified EFI system partition found + // + if (*FsHandle == NULL) { + Status = EFI_NOT_FOUND; + } + + DEBUG_CODE ( + CHAR16 *DevicePathStr2; + if (*FsHandle != NULL) { + DevicePathStr2 = ConvertDevicePathToText(CurFullPath, TRUE, TRUE); + if (DevicePathStr2 != NULL){ + DEBUG((DEBUG_INFO, "Found Active EFI System Partion on %s\n", DevicePathStr2)); + FreePool(DevicePathStr2); + } + } else { + DEBUG((DEBUG_INFO, "Failed to found Active EFI System Partion\n")); + } + ); + + if (CurFullPath != NULL) { + FreePool(CurFullPath); + } + + // + // Free BootOption Buffer + // + for (Index = 0; Index < BootOptionNum; Index++) { + if (BootOptionBuf[Index].Description != NULL) { + FreePool(BootOptionBuf[Index].Description); + } + + if (BootOptionBuf[Index].FilePath != NULL) { + FreePool(BootOptionBuf[Index].FilePath); + } + + if (BootOptionBuf[Index].OptionalData != NULL) { + FreePool(BootOptionBuf[Index].OptionalData); + } + } + + FreePool(BootOptionBuf); + + return Status; +} + + +/** + This routine is called to get all file infos with in a given dir & with given file attribute, the file info is listed in + alphabetical order described in UEFI spec. + + @param[in] Dir Directory file handler + @param[in] FileAttr Attribute of file to be red from directory + @param[out] FileInfoList File images info list red from directory + @param[out] FileNum File images number red from directory + + @retval EFI_SUCCESS File FileInfo list in the given + +**/ +EFI_STATUS +GetFileInfoListInAlphabetFromDir( + IN EFI_FILE_HANDLE Dir, + IN UINT64 FileAttr, + OUT LIST_ENTRY *FileInfoList, + OUT UINTN *FileNum + ) +{ + EFI_STATUS Status; + FILE_INFO_ENTRY *NewFileInfoEntry; + FILE_INFO_ENTRY *TempFileInfoEntry; + EFI_FILE_INFO *FileInfo; + CHAR16 *NewFileName; + CHAR16 *ListedFileName; + CHAR16 *NewFileNameExtension; + CHAR16 *ListedFileNameExtension; + CHAR16 *TempNewSubStr; + CHAR16 *TempListedSubStr; + LIST_ENTRY *Link; + BOOLEAN NoFile; + UINTN FileCount; + UINTN IndexNew; + UINTN IndexListed; + UINTN NewSubStrLen; + UINTN ListedSubStrLen; + INTN SubStrCmpResult; + + Status = EFI_SUCCESS; + NewFileName = NULL; + ListedFileName = NULL; + NewFileNameExtension = NULL; + ListedFileNameExtension = NULL; + TempNewSubStr = NULL; + TempListedSubStr = NULL; + FileInfo = NULL; + NoFile = FALSE; + FileCount = 0; + + InitializeListHead(FileInfoList); + + TempNewSubStr = (CHAR16 *) AllocateZeroPool(MAX_FILE_NAME_SIZE); + TempListedSubStr = (CHAR16 *) AllocateZeroPool(MAX_FILE_NAME_SIZE); + + if (TempNewSubStr == NULL || TempListedSubStr == NULL ) { + Status = EFI_OUT_OF_RESOURCES; + goto EXIT; + } + + for ( Status = FileHandleFindFirstFile(Dir, &FileInfo) + ; !EFI_ERROR(Status) && !NoFile + ; Status = FileHandleFindNextFile(Dir, FileInfo, &NoFile) + ){ + if (FileInfo == NULL) { + goto EXIT; + } + + // + // Skip file with mismatching File attribute + // + if ((FileInfo->Attribute & (FileAttr)) == 0) { + continue; + } + + NewFileInfoEntry = NULL; + NewFileInfoEntry = (FILE_INFO_ENTRY*)AllocateZeroPool(sizeof(FILE_INFO_ENTRY)); + if (NewFileInfoEntry == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto EXIT; + } + NewFileInfoEntry->Signature = FILE_INFO_SIGNATURE; + NewFileInfoEntry->FileInfo = AllocateCopyPool((UINTN) FileInfo->Size, FileInfo); + if (NewFileInfoEntry->FileInfo == NULL) { + FreePool(NewFileInfoEntry); + Status = EFI_OUT_OF_RESOURCES; + goto EXIT; + } + + NewFileInfoEntry->FileNameFirstPart = (CHAR16 *) AllocateZeroPool(MAX_FILE_NAME_SIZE); + if (NewFileInfoEntry->FileNameFirstPart == NULL) { + FreePool(NewFileInfoEntry->FileInfo); + FreePool(NewFileInfoEntry); + Status = EFI_OUT_OF_RESOURCES; + goto EXIT; + } + NewFileInfoEntry->FileNameSecondPart = (CHAR16 *) AllocateZeroPool(MAX_FILE_NAME_SIZE); + if (NewFileInfoEntry->FileNameSecondPart == NULL) { + FreePool(NewFileInfoEntry->FileInfo); + FreePool(NewFileInfoEntry->FileNameFirstPart); + FreePool(NewFileInfoEntry); + Status = EFI_OUT_OF_RESOURCES; + goto EXIT; + } + + // + // Splitter the whole New file name into 2 parts between the last period L'.' into NewFileName NewFileExtension + // If no period in the whole file name. NewFileExtension is set to L'\0' + // + NewFileName = NewFileInfoEntry->FileNameFirstPart; + NewFileNameExtension = NewFileInfoEntry->FileNameSecondPart; + SplitFileNameExtension(FileInfo->FileName, NewFileName, NewFileNameExtension); + UpperCaseString(NewFileName); + UpperCaseString(NewFileNameExtension); + + // + // Insert capsule file in alphabetical ordered list + // + for (Link = FileInfoList->ForwardLink; Link != FileInfoList; Link = Link->ForwardLink) { + // + // Get the FileInfo from the link list + // + TempFileInfoEntry = CR (Link, FILE_INFO_ENTRY, Link, FILE_INFO_SIGNATURE); + ListedFileName = TempFileInfoEntry->FileNameFirstPart; + ListedFileNameExtension = TempFileInfoEntry->FileNameSecondPart; + + // + // Follow rule in UEFI spec 8.5.5 to compare file name + // + IndexListed = 0; + IndexNew = 0; + while (TRUE){ + // + // First compare each substrings in NewFileName & ListedFileName between periods + // + GetSubStringBeforePeriod(&NewFileName[IndexNew], TempNewSubStr, &NewSubStrLen); + GetSubStringBeforePeriod(&ListedFileName[IndexListed], TempListedSubStr, &ListedSubStrLen); + if (NewSubStrLen > ListedSubStrLen) { + // + // Substr in NewFileName is longer. Pad tail with SPACE + // + PadStrInTail(TempListedSubStr, NewSubStrLen - ListedSubStrLen, L' '); + } else if (NewSubStrLen < ListedSubStrLen){ + // + // Substr in ListedFileName is longer. Pad tail with SPACE + // + PadStrInTail(TempNewSubStr, ListedSubStrLen - NewSubStrLen, L' '); + } + + SubStrCmpResult = StrnCmp(TempNewSubStr, TempListedSubStr, MAX_FILE_NAME_LEN); + if (SubStrCmpResult != 0) { + break; + } + + // + // Move to skip this substring + // + IndexNew += NewSubStrLen; + IndexListed += ListedSubStrLen; + // + // Reach File First Name end + // + if (NewFileName[IndexNew] == L'\0' || ListedFileName[IndexListed] == L'\0') { + break; + } + + // + // Skip the period L'.' + // + IndexNew++; + IndexListed++; + } + + if (SubStrCmpResult < 0) { + // + // NewFileName is smaller. Find the right place to insert New file + // + break; + } else if (SubStrCmpResult == 0) { + // + // 2 cases whole NewFileName is smaller than ListedFileName + // 1. if NewFileName == ListedFileName. Continue to compare FileNameExtension + // 2. if NewFileName is shorter than ListedFileName + // + if (NewFileName[IndexNew] == L'\0') { + if (ListedFileName[IndexListed] != L'\0' || (StrnCmp(NewFileNameExtension, ListedFileNameExtension, MAX_FILE_NAME_LEN) < 0)) { + break; + } + } + } + + // + // Other case, ListedFileName is smaller. Continue to compare the next file in the list + // + } + + // + // If Find an entry in the list whose name is bigger than new FileInfo in alphabet order + // Insert it before this entry + // else + // Insert at the tail of this list (Link = FileInfoList) + // + InsertTailList(Link, &NewFileInfoEntry->Link); + + FileCount++; + } + + *FileNum = FileCount; + +EXIT: + + if (TempNewSubStr != NULL) { + FreePool(TempNewSubStr); + } + + if (TempListedSubStr != NULL) { + FreePool(TempListedSubStr); + } + + if (EFI_ERROR(Status)) { + while(!IsListEmpty(FileInfoList)) { + Link = FileInfoList->ForwardLink; + RemoveEntryList(Link); + + TempFileInfoEntry = CR (Link, FILE_INFO_ENTRY, Link, FILE_INFO_SIGNATURE); + + FreePool(TempFileInfoEntry->FileInfo); + FreePool(TempFileInfoEntry->FileNameFirstPart); + FreePool(TempFileInfoEntry->FileNameSecondPart); + FreePool(TempFileInfoEntry); + } + *FileNum = 0; + } + + return Status; +} + + +/** + This routine is called to get all qualified image from file from an given directory + in alphabetic order. All the file image is copied to allocated boottime memory. + Caller should free these memory + + @param[in] Dir Directory file handler + @param[in] FileAttr Attribute of file to be red from directory + @param[out] FilePtr File images Info buffer red from directory + @param[out] FileNum File images number red from directory + + @retval EFI_SUCCESS Succeed to get all capsules in alphabetic order. + +**/ +EFI_STATUS +GetFileImageInAlphabetFromDir( + IN EFI_FILE_HANDLE Dir, + IN UINT64 FileAttr, + OUT IMAGE_INFO **FilePtr, + OUT UINTN *FileNum + ) +{ + EFI_STATUS Status; + LIST_ENTRY *Link; + EFI_FILE_HANDLE FileHandle; + FILE_INFO_ENTRY *FileInfoEntry; + EFI_FILE_INFO *FileInfo; + UINTN FileCount; + IMAGE_INFO *TempFilePtrBuf; + UINTN Size; + LIST_ENTRY FileInfoList; + + FileHandle = NULL; + FileCount = 0; + TempFilePtrBuf = NULL; + *FilePtr = NULL; + + // + // Get file list in Dir in alphabetical order + // + Status = GetFileInfoListInAlphabetFromDir( + Dir, + FileAttr, + &FileInfoList, + &FileCount + ); + if (EFI_ERROR(Status)) { + DEBUG ((DEBUG_ERROR, "GetFileInfoListInAlphabetFromDir Failed!\n")); + goto EXIT; + } + + if (FileCount == 0) { + DEBUG ((DEBUG_ERROR, "No file found in Dir!\n")); + Status = EFI_NOT_FOUND; + goto EXIT; + } + + TempFilePtrBuf = (IMAGE_INFO *)AllocateZeroPool(sizeof(IMAGE_INFO) * FileCount); + if (TempFilePtrBuf == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto EXIT; + } + + // + // Read all files from FileInfoList to BS memory + // + FileCount = 0; + for (Link = FileInfoList.ForwardLink; Link != &FileInfoList; Link = Link->ForwardLink) { + // + // Get FileInfo from the link list + // + FileInfoEntry = CR (Link, FILE_INFO_ENTRY, Link, FILE_INFO_SIGNATURE); + FileInfo = FileInfoEntry->FileInfo; + + Status = Dir->Open( + Dir, + &FileHandle, + FileInfo->FileName, + EFI_FILE_MODE_READ, + 0 + ); + if (EFI_ERROR(Status)){ + continue; + } + + Size = (UINTN)FileInfo->FileSize; + TempFilePtrBuf[FileCount].ImageAddress = AllocateZeroPool(Size); + if (TempFilePtrBuf[FileCount].ImageAddress == NULL) { + DEBUG((DEBUG_ERROR, "Fail to allocate memory for capsule. Stop processing the rest.\n")); + break; + } + + Status = FileHandle->Read( + FileHandle, + &Size, + TempFilePtrBuf[FileCount].ImageAddress + ); + + FileHandle->Close(FileHandle); + + // + // Skip read error file + // + if (EFI_ERROR(Status) || Size != (UINTN)FileInfo->FileSize) { + // + // Remove this error file info accordingly + // & move Link to BackLink + // + Link = RemoveEntryList(Link); + Link = Link->BackLink; + + FreePool(FileInfoEntry->FileInfo); + FreePool(FileInfoEntry->FileNameFirstPart); + FreePool(FileInfoEntry->FileNameSecondPart); + FreePool(FileInfoEntry); + + FreePool(TempFilePtrBuf[FileCount].ImageAddress); + TempFilePtrBuf[FileCount].ImageAddress = NULL; + TempFilePtrBuf[FileCount].FileInfo = NULL; + + continue; + } + TempFilePtrBuf[FileCount].FileInfo = FileInfo; + FileCount++; + } + + DEBUG_CODE ( + for (Link = FileInfoList.ForwardLink; Link != &FileInfoList; Link = Link->ForwardLink) { + FileInfoEntry = CR (Link, FILE_INFO_ENTRY, Link, FILE_INFO_SIGNATURE); + FileInfo = FileInfoEntry->FileInfo; + DEBUG((DEBUG_INFO, "Successfully read capsule file %s from disk.\n", FileInfo->FileName)); + } + ); + +EXIT: + + *FilePtr = TempFilePtrBuf; + *FileNum = FileCount; + + // + // FileInfo will be freed by Calller + // + while(!IsListEmpty(&FileInfoList)) { + Link = FileInfoList.ForwardLink; + RemoveEntryList(Link); + + FileInfoEntry = CR (Link, FILE_INFO_ENTRY, Link, FILE_INFO_SIGNATURE); + + FreePool(FileInfoEntry->FileNameFirstPart); + FreePool(FileInfoEntry->FileNameSecondPart); + FreePool(FileInfoEntry); + } + + return Status; +} + +/** + This routine is called to remove all qualified image from file from an given directory. + + @param[in] Dir Directory file handler + @param[in] FileAttr Attribute of files to be deleted + + @retval EFI_SUCCESS Succeed to remove all files from an given directory. + +**/ +EFI_STATUS +RemoveFileFromDir( + IN EFI_FILE_HANDLE Dir, + IN UINT64 FileAttr + ) +{ + EFI_STATUS Status; + LIST_ENTRY *Link; + LIST_ENTRY FileInfoList; + EFI_FILE_HANDLE FileHandle; + FILE_INFO_ENTRY *FileInfoEntry; + EFI_FILE_INFO *FileInfo; + UINTN FileCount; + + FileHandle = NULL; + + // + // Get file list in Dir in alphabetical order + // + Status = GetFileInfoListInAlphabetFromDir( + Dir, + FileAttr, + &FileInfoList, + &FileCount + ); + if (EFI_ERROR(Status)) { + DEBUG ((DEBUG_ERROR, "GetFileInfoListInAlphabetFromDir Failed!\n")); + goto EXIT; + } + + if (FileCount == 0) { + DEBUG ((DEBUG_ERROR, "No file found in Dir!\n")); + Status = EFI_NOT_FOUND; + goto EXIT; + } + + // + // Delete all files with given attribute in Dir + // + for (Link = FileInfoList.ForwardLink; Link != &(FileInfoList); Link = Link->ForwardLink) { + // + // Get FileInfo from the link list + // + FileInfoEntry = CR (Link, FILE_INFO_ENTRY, Link, FILE_INFO_SIGNATURE); + FileInfo = FileInfoEntry->FileInfo; + + Status = Dir->Open( + Dir, + &FileHandle, + FileInfo->FileName, + EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, + 0 + ); + if (EFI_ERROR(Status)){ + continue; + } + + Status = FileHandle->Delete(FileHandle); + } + +EXIT: + + while(!IsListEmpty(&FileInfoList)) { + Link = FileInfoList.ForwardLink; + RemoveEntryList(Link); + + FileInfoEntry = CR (Link, FILE_INFO_ENTRY, Link, FILE_INFO_SIGNATURE); + + FreePool(FileInfoEntry->FileInfo); + FreePool(FileInfoEntry); + } + + return Status; +} + +/** + This routine is called to get all caspules from file. The capsule file image is + copied to BS memory. Caller is responsible to free them. + + @param[in] MaxRetry Max Connection Retry. Stall 100ms between each connection try to ensure + devices like USB can get enumerated. + @param[out] CapsulePtr Copied Capsule file Image Info buffer + @param[out] CapsuleNum CapsuleNumber + @param[out] FsHandle File system handle + @param[out] LoadOptionNumber OptionNumber of boot option + + @retval EFI_SUCCESS Succeed to get all capsules. + +**/ +EFI_STATUS +GetAllCapsuleOnDisk( + IN UINTN MaxRetry, + OUT IMAGE_INFO **CapsulePtr, + OUT UINTN *CapsuleNum, + OUT EFI_HANDLE *FsHandle, + OUT UINT16 *LoadOptionNumber + ) +{ + EFI_STATUS Status; + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Fs; + EFI_FILE_HANDLE RootDir; + EFI_FILE_HANDLE FileDir; + UINT16 *TempOptionNumber; + + TempOptionNumber = NULL; + *CapsuleNum = 0; + + Status = GetEfiSysPartitionFromActiveBootOption(MaxRetry, &TempOptionNumber, FsHandle); + if (EFI_ERROR(Status)) { + return Status; + } + + Status = gBS->HandleProtocol(*FsHandle, &gEfiSimpleFileSystemProtocolGuid, (VOID **)&Fs); + if (EFI_ERROR(Status)) { + return Status; + } + + Status = Fs->OpenVolume(Fs, &RootDir); + if (EFI_ERROR(Status)) { + return Status; + } + + Status = RootDir->Open( + RootDir, + &FileDir, + EFI_CAPSULE_FILE_DIRECTORY, + EFI_FILE_MODE_READ, + 0 + ); + if (EFI_ERROR(Status)) { + DEBUG((DEBUG_ERROR, "CodLibGetAllCapsuleOnDisk fail to open RootDir!\n")); + RootDir->Close (RootDir); + return Status; + } + RootDir->Close (RootDir); + + // + // Only Load files with EFI_FILE_SYSTEM or EFI_FILE_ARCHIVE attribute + // ignore EFI_FILE_READ_ONLY, EFI_FILE_HIDDEN, EFI_FILE_RESERVED, EFI_FILE_DIRECTORY + // + Status = GetFileImageInAlphabetFromDir( + FileDir, + EFI_FILE_SYSTEM | EFI_FILE_ARCHIVE, + CapsulePtr, + CapsuleNum + ); + DEBUG((DEBUG_INFO, "GetFileImageInAlphabetFromDir status %x\n", Status)); + + // + // Always remove file to avoid deadloop in capsule process + // + Status = RemoveFileFromDir(FileDir, EFI_FILE_SYSTEM | EFI_FILE_ARCHIVE); + DEBUG((DEBUG_INFO, "RemoveFileFromDir status %x\n", Status)); + + FileDir->Close (FileDir); + + if (LoadOptionNumber != NULL) { + *LoadOptionNumber = *TempOptionNumber; + } + + return Status; +} + +/** + Build Gather list for a list of capsule images. + + @param[in] CapsuleBuffer An array of pointer to capsule images + @param[in] CapsuleSize An array of UINTN to capsule images size + @param[in] CapsuleNum The count of capsule images + @param[out] BlockDescriptors The block descriptors for the capsule images + + @retval EFI_SUCCESS The block descriptors for the capsule images are constructed. + +**/ +EFI_STATUS +BuildGatherList ( + IN VOID **CapsuleBuffer, + IN UINTN *CapsuleSize, + IN UINTN CapsuleNum, + OUT EFI_CAPSULE_BLOCK_DESCRIPTOR **BlockDescriptors + ) +{ + EFI_STATUS Status; + EFI_CAPSULE_BLOCK_DESCRIPTOR *BlockDescriptors1; + EFI_CAPSULE_BLOCK_DESCRIPTOR *BlockDescriptorPre; + EFI_CAPSULE_BLOCK_DESCRIPTOR *BlockDescriptorsHeader; + UINTN Index; + + BlockDescriptors1 = NULL; + BlockDescriptorPre = NULL; + BlockDescriptorsHeader = NULL; + + for (Index = 0; Index < CapsuleNum; Index++) { + // + // Allocate memory for the descriptors. + // + BlockDescriptors1 = AllocateZeroPool (2 * sizeof (EFI_CAPSULE_BLOCK_DESCRIPTOR)); + if (BlockDescriptors1 == NULL) { + DEBUG ((DEBUG_ERROR, "BuildGatherList: failed to allocate memory for descriptors\n")); + Status = EFI_OUT_OF_RESOURCES; + goto ERREXIT; + } else { + DEBUG ((DEBUG_INFO, "BuildGatherList: creating capsule descriptors at 0x%X\n", (UINTN) BlockDescriptors1)); + } + + // + // Record descirptor header + // + if (Index == 0) { + BlockDescriptorsHeader = BlockDescriptors1; + } + + if (BlockDescriptorPre != NULL) { + BlockDescriptorPre->Union.ContinuationPointer = (UINTN) BlockDescriptors1; + BlockDescriptorPre->Length = 0; + } + + BlockDescriptors1->Union.DataBlock = (UINTN) CapsuleBuffer[Index]; + BlockDescriptors1->Length = CapsuleSize[Index]; + + BlockDescriptorPre = BlockDescriptors1 + 1; + BlockDescriptors1 = NULL; + } + + // + // Null-terminate. + // + if (BlockDescriptorPre != NULL) { + BlockDescriptorPre->Union.ContinuationPointer = (UINTN)NULL; + BlockDescriptorPre->Length = 0; + *BlockDescriptors = BlockDescriptorsHeader; + } + + return EFI_SUCCESS; + +ERREXIT: + if (BlockDescriptors1 != NULL) { + FreePool (BlockDescriptors1); + } + + return Status; +} + +/** + This routine is called to check if CapsuleOnDisk flag in OsIndications Variable + is enabled. + + @retval TRUE Flag is enabled + @retval FALSE Flag is not enabled + +**/ +BOOLEAN +EFIAPI +CoDCheckCapsuleOnDiskFlag( + VOID + ) +{ + EFI_STATUS Status; + UINT64 OsIndication; + UINTN DataSize; + + // + // Check File Capsule Delivery Supported Flag in OsIndication variable + // + OsIndication = 0; + DataSize = sizeof(UINT64); + Status = gRT->GetVariable ( + EFI_OS_INDICATIONS_VARIABLE_NAME, + &gEfiGlobalVariableGuid, + NULL, + &DataSize, + &OsIndication + ); + if (!EFI_ERROR(Status) && + (OsIndication & EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED) != 0) { + return TRUE; + } + + return FALSE; +} + + +/** + This routine is called to clear CapsuleOnDisk flags including OsIndications and BootNext variable. + + @retval EFI_SUCCESS All Capsule On Disk flags are cleared + +**/ +EFI_STATUS +EFIAPI +CoDClearCapsuleOnDiskFlag( + VOID + ) +{ + EFI_STATUS Status; + UINT64 OsIndication; + UINTN DataSize; + + // + // Reset File Capsule Delivery Supported Flag in OsIndication variable + // + OsIndication = 0; + DataSize = sizeof(UINT64); + Status = gRT->GetVariable ( + EFI_OS_INDICATIONS_VARIABLE_NAME, + &gEfiGlobalVariableGuid, + NULL, + &DataSize, + &OsIndication + ); + if (EFI_ERROR(Status) || + (OsIndication & EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED) == 0) { + return Status; + } + + OsIndication &= ~((UINT64)EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED); + Status = gRT->SetVariable ( + EFI_OS_INDICATIONS_VARIABLE_NAME, + &gEfiGlobalVariableGuid, + EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_NON_VOLATILE, + sizeof(UINT64), + &OsIndication + ); + ASSERT(!EFI_ERROR(Status)); + + // + // Delete BootNext variable. Capsule Process may reset system, so can't rely on Bds to clear this variable + // + Status = gRT->SetVariable ( + EFI_BOOT_NEXT_VARIABLE_NAME, + &gEfiGlobalVariableGuid, + 0, + 0, + NULL + ); + ASSERT (Status == EFI_SUCCESS || Status == EFI_NOT_FOUND); + + return EFI_SUCCESS; +} + +/** + This routine is called to clear CapsuleOnDisk Relocation Info variable. + Total Capsule On Disk length is recorded in this variable + + @retval EFI_SUCCESS Capsule On Disk flags are cleared + +**/ +EFI_STATUS +CoDClearCapsuleRelocationInfo( + VOID + ) +{ + return gRT->SetVariable ( + COD_RELOCATION_INFO_VAR_NAME, + &gEfiCapsuleVendorGuid, + 0, + 0, + NULL + ); +} + +/** + Relocate Capsule on Disk from EFI system partition to a platform-specific NV storage device + with BlockIo protocol. Relocation device path, identified by PcdCodRelocationDevPath, must + be a full device path. + Device enumeration like USB costs time, user can input MaxRetry to tell function to retry. + Function will stall 100ms between each retry. + + Side Effects: + Content corruption. Block IO write directly touches low level write. Orignal partitions, file systems + of the relocation device will be corrupted. + + @param[in] MaxRetry Max Connection Retry. Stall 100ms between each connection try to ensure + devices like USB can get enumerated. + + @retval EFI_SUCCESS Capsule on Disk images are sucessfully relocated to the platform-specific device. + +**/ +EFI_STATUS +RelocateCapsuleToDisk( + UINTN MaxRetry + ) +{ + EFI_STATUS Status; + UINTN CapsuleOnDiskNum; + UINTN Index; + UINTN DataSize; + UINT64 TotalImageSize; + UINT64 TotalImageNameSize; + IMAGE_INFO *CapsuleOnDiskBuf; + EFI_HANDLE Handle; + EFI_HANDLE TempHandle; + EFI_HANDLE *HandleBuffer; + UINTN NumberOfHandles; + EFI_BLOCK_IO_PROTOCOL *BlockIo; + UINT8 *CapsuleDataBuf; + UINT8 *CapsulePtr; + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Fs; + EFI_FILE_HANDLE RootDir; + EFI_FILE_HANDLE TempCodFile; + UINT64 TempCodFileSize; + EFI_DEVICE_PATH *TempDevicePath; + BOOLEAN RelocationInfo; + UINT16 LoadOptionNumber; + EFI_CAPSULE_HEADER FileNameCapsuleHeader; + + RootDir = NULL; + TempCodFile = NULL; + HandleBuffer = NULL; + CapsuleDataBuf = NULL; + CapsuleOnDiskBuf = NULL; + NumberOfHandles = 0; + + DEBUG ((DEBUG_INFO, "CapsuleOnDisk RelocateCapsule Enter\n")); + + // + // 1. Load all Capsule On Disks in to memory + // + Status = GetAllCapsuleOnDisk(MaxRetry, &CapsuleOnDiskBuf, &CapsuleOnDiskNum, &Handle, &LoadOptionNumber); + if (EFI_ERROR(Status) || CapsuleOnDiskNum == 0 || CapsuleOnDiskBuf == NULL) { + DEBUG ((DEBUG_INFO, "RelocateCapsule: GetAllCapsuleOnDisk Status - 0x%x\n", Status)); + return EFI_NOT_FOUND; + } + + // + // 2. Connect platform special device path as relocation device. + // If no platform special device path specified or the device path is invalid, use the EFI system partition where + // stores the capsules as relocation device. + // + if (IsDevicePathValid ((EFI_DEVICE_PATH *)PcdGetPtr(PcdCodRelocationDevPath), PcdGetSize(PcdCodRelocationDevPath))) { + Status = EfiBootManagerConnectDevicePath ((EFI_DEVICE_PATH *)PcdGetPtr(PcdCodRelocationDevPath), &TempHandle); + if (EFI_ERROR(Status)) { + DEBUG ((DEBUG_ERROR, "RelocateCapsule: EfiBootManagerConnectDevicePath Status - 0x%x\n", Status)); + goto EXIT; + } + + // + // Connect all the child handle. Partition & FAT drivers are allowed in this case + // + gBS->ConnectController (TempHandle, NULL, NULL, TRUE); + Status = gBS->LocateHandleBuffer( + ByProtocol, + &gEfiSimpleFileSystemProtocolGuid, + NULL, + &NumberOfHandles, + &HandleBuffer + ); + if (EFI_ERROR(Status)) { + DEBUG ((DEBUG_ERROR, "RelocateCapsule: LocateHandleBuffer Status - 0x%x\n", Status)); + goto EXIT; + } + + // + // Find first Simple File System Handle which can match PcdCodRelocationDevPath + // + for (Index = 0; Index < NumberOfHandles; Index++) { + Status = gBS->HandleProtocol(HandleBuffer[Index], &gEfiDevicePathProtocolGuid, (VOID **)&TempDevicePath); + if (EFI_ERROR(Status)) { + continue; + } + + DataSize = GetDevicePathSize((EFI_DEVICE_PATH *)PcdGetPtr(PcdCodRelocationDevPath)) - sizeof(EFI_DEVICE_PATH); + if (0 == CompareMem((EFI_DEVICE_PATH *)PcdGetPtr(PcdCodRelocationDevPath), TempDevicePath, DataSize)) { + Handle = HandleBuffer[Index]; + break; + } + } + + FreePool(HandleBuffer); + + if (Index == NumberOfHandles) { + DEBUG ((DEBUG_ERROR, "RelocateCapsule: No simple file system protocol found.\n")); + Status = EFI_NOT_FOUND; + } + } + + Status = gBS->HandleProtocol(Handle, &gEfiBlockIoProtocolGuid, (VOID **)&BlockIo); + if (EFI_ERROR(Status) || BlockIo->Media->ReadOnly) { + DEBUG((DEBUG_ERROR, "Fail to find Capsule on Disk relocation BlockIo device or device is ReadOnly!\n")); + goto EXIT; + } + + Status = gBS->HandleProtocol(Handle, &gEfiSimpleFileSystemProtocolGuid, (VOID **)&Fs); + if (EFI_ERROR(Status)) { + goto EXIT; + } + + // + // Check if device used to relocate Capsule On Disk is big enough + // + TotalImageSize = 0; + TotalImageNameSize = 0; + for (Index = 0; Index < CapsuleOnDiskNum; Index++) { + // + // Overflow check + // + if (MAX_ADDRESS - (UINTN)TotalImageSize <= CapsuleOnDiskBuf[Index].FileInfo->FileSize) { + Status = EFI_INVALID_PARAMETER; + goto EXIT; + } + + if (MAX_ADDRESS - (UINTN)TotalImageNameSize <= StrSize(CapsuleOnDiskBuf[Index].FileInfo->FileName)) { + Status = EFI_INVALID_PARAMETER; + goto EXIT; + } + + TotalImageSize += CapsuleOnDiskBuf[Index].FileInfo->FileSize; + TotalImageNameSize += StrSize(CapsuleOnDiskBuf[Index].FileInfo->FileName); + DEBUG((DEBUG_INFO, "RelocateCapsule: %x Size %x\n",CapsuleOnDiskBuf[Index].FileInfo->FileName, CapsuleOnDiskBuf[Index].FileInfo->FileSize)); + } + + DEBUG((DEBUG_INFO, "RelocateCapsule: TotalImageSize %x\n", TotalImageSize)); + DEBUG((DEBUG_INFO, "RelocateCapsule: TotalImageNameSize %x\n", TotalImageNameSize)); + + if (MAX_ADDRESS - (UINTN)TotalImageNameSize <= sizeof(UINT64) * 2 || + MAX_ADDRESS - (UINTN)TotalImageSize <= (UINTN)TotalImageNameSize + sizeof(UINT64) * 2) { + Status = EFI_INVALID_PARAMETER; + goto EXIT; + } + + TempCodFileSize = sizeof(UINT64) + TotalImageSize + sizeof(EFI_CAPSULE_HEADER) + TotalImageNameSize; + + // + // Check if CapsuleTotalSize. There could be reminder, so use LastBlock number directly + // + if (DivU64x32(TempCodFileSize, BlockIo->Media->BlockSize) > BlockIo->Media->LastBlock) { + DEBUG((DEBUG_ERROR, "RelocateCapsule: Relocation device isn't big enough to hold all Capsule on Disk!\n")); + DEBUG((DEBUG_ERROR, "TotalImageSize = %x\n", TotalImageSize)); + DEBUG((DEBUG_ERROR, "TotalImageNameSize = %x\n", TotalImageNameSize)); + DEBUG((DEBUG_ERROR, "RelocationDev BlockSize = %x LastBlock = %x\n", BlockIo->Media->BlockSize, BlockIo->Media->LastBlock)); + Status = EFI_OUT_OF_RESOURCES; + goto EXIT; + } + + CapsuleDataBuf = AllocatePool((UINTN) TempCodFileSize); + if (CapsuleDataBuf == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto EXIT; + } + + // + // First UINT64 reserved for total image size, including capsule name capsule. + // + *(UINT64 *) CapsuleDataBuf = TotalImageSize + sizeof(EFI_CAPSULE_HEADER) + TotalImageNameSize; + + // + // Line up all the Capsule on Disk and write to relocation disk at one time. It could save some time in disk write + // + for (Index = 0, CapsulePtr = CapsuleDataBuf + sizeof(UINT64); Index < CapsuleOnDiskNum; Index++) { + CopyMem(CapsulePtr, CapsuleOnDiskBuf[Index].ImageAddress, (UINTN) CapsuleOnDiskBuf[Index].FileInfo->FileSize); + CapsulePtr += CapsuleOnDiskBuf[Index].FileInfo->FileSize; + } + + // + // Line the capsule header for capsule name capsule. + // + CopyGuid(&FileNameCapsuleHeader.CapsuleGuid, &gEdkiiCapsuleOnDiskNameGuid); + FileNameCapsuleHeader.CapsuleImageSize = (UINT32) TotalImageNameSize + sizeof(EFI_CAPSULE_HEADER); + FileNameCapsuleHeader.Flags = CAPSULE_FLAGS_PERSIST_ACROSS_RESET; + FileNameCapsuleHeader.HeaderSize = sizeof(EFI_CAPSULE_HEADER); + CopyMem(CapsulePtr, &FileNameCapsuleHeader, FileNameCapsuleHeader.HeaderSize); + CapsulePtr += FileNameCapsuleHeader.HeaderSize; + + // + // Line up all the Capsule file names. + // + for (Index = 0; Index < CapsuleOnDiskNum; Index++) { + CopyMem(CapsulePtr, CapsuleOnDiskBuf[Index].FileInfo->FileName, StrSize(CapsuleOnDiskBuf[Index].FileInfo->FileName)); + CapsulePtr += StrSize(CapsuleOnDiskBuf[Index].FileInfo->FileName); + } + + // + // 5. Flash all Capsules on Disk to TempCoD.tmp under RootDir + // + Status = Fs->OpenVolume(Fs, &RootDir); + if (EFI_ERROR(Status)) { + DEBUG((DEBUG_ERROR, "RelocateCapsule: OpenVolume error. %x\n", Status)); + goto EXIT; + } + + Status = RootDir->Open( + RootDir, + &TempCodFile, + (CHAR16 *)PcdGetPtr(PcdCoDRelocationFileName), + EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, + 0 + ); + if (!EFI_ERROR(Status)) { + // + // Error handling code to prevent malicious code to hold this file to block capsule on disk + // + TempCodFile->Delete(TempCodFile); + } + Status = RootDir->Open( + RootDir, + &TempCodFile, + (CHAR16 *)PcdGetPtr(PcdCoDRelocationFileName), + EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | EFI_FILE_MODE_CREATE, + 0 + ); + if (EFI_ERROR(Status)) { + DEBUG((DEBUG_ERROR, "RelocateCapsule: Open TemCoD.tmp error. %x\n", Status)); + goto EXIT; + } + + // + // Always write at the begining of TempCap file + // + DataSize = (UINTN) TempCodFileSize; + Status = TempCodFile->Write( + TempCodFile, + &DataSize, + CapsuleDataBuf + ); + if (EFI_ERROR(Status)) { + DEBUG((DEBUG_ERROR, "RelocateCapsule: Write TemCoD.tmp error. %x\n", Status)); + goto EXIT; + } + + if (DataSize != TempCodFileSize) { + Status = EFI_DEVICE_ERROR; + goto EXIT; + } + + // + // Save Capsule On Disk relocation info to "CodRelocationInfo" Var + // It is used in next reboot by TCB + // + RelocationInfo = TRUE; + Status = gRT->SetVariable( + COD_RELOCATION_INFO_VAR_NAME, + &gEfiCapsuleVendorGuid, + EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS, + sizeof (BOOLEAN), + &RelocationInfo + ); + // + // Save the LoadOptionNumber of the boot option, where the capsule is relocated, + // into "CodRelocationLoadOption" var. It is used in next reboot after capsule is + // updated out of TCB to remove the TempCoDFile. + // + Status = gRT->SetVariable( + COD_RELOCATION_LOAD_OPTION_VAR_NAME, + &gEfiCapsuleVendorGuid, + EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS, + sizeof (UINT16), + &LoadOptionNumber + ); + +EXIT: + + if (CapsuleDataBuf != NULL) { + FreePool(CapsuleDataBuf); + } + + if (CapsuleOnDiskBuf != NULL) { + // + // Free resources allocated by CodLibGetAllCapsuleOnDisk + // + for (Index = 0; Index < CapsuleOnDiskNum; Index++ ) { + FreePool(CapsuleOnDiskBuf[Index].ImageAddress); + FreePool(CapsuleOnDiskBuf[Index].FileInfo); + } + FreePool(CapsuleOnDiskBuf); + } + + if (TempCodFile != NULL) { + if (EFI_ERROR(Status)) { + TempCodFile->Delete (TempCodFile); + } else { + TempCodFile->Close (TempCodFile); + } + } + + if (RootDir != NULL) { + RootDir->Close (RootDir); + } + + return Status; +} + +/** + For the platforms that support Capsule In Ram, reuse the Capsule In Ram to deliver capsule. + Relocate Capsule On Disk to memory and call UpdateCapsule(). + Device enumeration like USB costs time, user can input MaxRetry to tell function to retry. + Function will stall 100ms between each retry. + + @param[in] MaxRetry Max Connection Retry. Stall 100ms between each connection try to ensure + devices like USB can get enumerated. + + @retval EFI_SUCCESS Deliver capsule through Capsule In Ram successfully. + +**/ +EFI_STATUS +RelocateCapsuleToRam ( + UINTN MaxRetry + ) +{ + EFI_STATUS Status; + UINTN CapsuleOnDiskNum; + IMAGE_INFO *CapsuleOnDiskBuf; + EFI_HANDLE Handle; + EFI_CAPSULE_BLOCK_DESCRIPTOR *BlockDescriptors; + VOID **CapsuleBuffer; + UINTN *CapsuleSize; + EFI_CAPSULE_HEADER *FileNameCapsule; + UINTN Index; + UINT8 *StringBuf; + UINTN StringSize; + UINTN TotalStringSize; + + CapsuleOnDiskBuf = NULL; + BlockDescriptors = NULL; + CapsuleBuffer = NULL; + CapsuleSize = NULL; + FileNameCapsule = NULL; + TotalStringSize = 0; + + // + // 1. Load all Capsule On Disks into memory + // + Status = GetAllCapsuleOnDisk (MaxRetry, &CapsuleOnDiskBuf, &CapsuleOnDiskNum, &Handle, NULL); + if (EFI_ERROR (Status) || CapsuleOnDiskNum == 0 || CapsuleOnDiskBuf == NULL) { + DEBUG ((DEBUG_ERROR, "GetAllCapsuleOnDisk Status - 0x%x\n", Status)); + return EFI_NOT_FOUND; + } + + // + // 2. Add a capsule for Capsule file name strings + // + CapsuleBuffer = AllocateZeroPool ((CapsuleOnDiskNum + 1) * sizeof (VOID *)); + if (CapsuleBuffer == NULL) { + DEBUG ((DEBUG_ERROR, "Fail to allocate memory for capsules.\n")); + return EFI_OUT_OF_RESOURCES; + } + + CapsuleSize = AllocateZeroPool ((CapsuleOnDiskNum + 1) * sizeof (UINTN)); + if (CapsuleSize == NULL) { + DEBUG ((DEBUG_ERROR, "Fail to allocate memory for capsules.\n")); + FreePool (CapsuleBuffer); + return EFI_OUT_OF_RESOURCES; + } + + for (Index = 0; Index < CapsuleOnDiskNum; Index++) { + CapsuleBuffer[Index] = (VOID *)(UINTN) CapsuleOnDiskBuf[Index].ImageAddress; + CapsuleSize[Index] = (UINTN) CapsuleOnDiskBuf[Index].FileInfo->FileSize; + TotalStringSize += StrSize (CapsuleOnDiskBuf[Index].FileInfo->FileName); + } + + FileNameCapsule = AllocateZeroPool (sizeof (EFI_CAPSULE_HEADER) + TotalStringSize); + if (FileNameCapsule == NULL) { + DEBUG ((DEBUG_ERROR, "Fail to allocate memory for name capsule.\n")); + FreePool (CapsuleBuffer); + FreePool (CapsuleSize); + return EFI_OUT_OF_RESOURCES; + } + + FileNameCapsule->CapsuleImageSize = (UINT32) (sizeof (EFI_CAPSULE_HEADER) + TotalStringSize); + FileNameCapsule->Flags = CAPSULE_FLAGS_PERSIST_ACROSS_RESET; + FileNameCapsule->HeaderSize = sizeof (EFI_CAPSULE_HEADER); + CopyGuid (&(FileNameCapsule->CapsuleGuid), &gEdkiiCapsuleOnDiskNameGuid); + + StringBuf = (UINT8 *)FileNameCapsule + FileNameCapsule->HeaderSize; + for (Index = 0; Index < CapsuleOnDiskNum; Index ++) { + StringSize = StrSize (CapsuleOnDiskBuf[Index].FileInfo->FileName); + CopyMem (StringBuf, CapsuleOnDiskBuf[Index].FileInfo->FileName, StringSize); + StringBuf += StringSize; + } + + CapsuleBuffer[CapsuleOnDiskNum] = FileNameCapsule; + CapsuleSize[CapsuleOnDiskNum] = TotalStringSize + sizeof (EFI_CAPSULE_HEADER); + + // + // 3. Build Gather list for the capsules + // + Status = BuildGatherList (CapsuleBuffer, CapsuleSize, CapsuleOnDiskNum + 1, &BlockDescriptors); + if (EFI_ERROR (Status) || BlockDescriptors == NULL) { + FreePool (CapsuleBuffer); + FreePool (CapsuleSize); + FreePool (FileNameCapsule); + return EFI_OUT_OF_RESOURCES; + } + + // + // 4. Call UpdateCapsule() service + // + Status = gRT->UpdateCapsule((EFI_CAPSULE_HEADER **) CapsuleBuffer, CapsuleOnDiskNum + 1, (UINTN) BlockDescriptors); + + return Status; +} + +/** + Relocate Capsule on Disk from EFI system partition. + + Two solution to deliver Capsule On Disk: + Solution A: If PcdCapsuleInRamSupport is enabled, relocate Capsule On Disk to memory and call UpdateCapsule(). + Solution B: If PcdCapsuleInRamSupport is disabled, relocate Capsule On Disk to a platform-specific NV storage + device with BlockIo protocol. + + Device enumeration like USB costs time, user can input MaxRetry to tell function to retry. + Function will stall 100ms between each retry. + + Side Effects: + Capsule Delivery Supported Flag in OsIndication variable and BootNext variable will be cleared. + Solution B: Content corruption. Block IO write directly touches low level write. Orignal partitions, file + systems of the relocation device will be corrupted. + + @param[in] MaxRetry Max Connection Retry. Stall 100ms between each connection try to ensure + devices like USB can get enumerated. Input 0 means no retry. + + @retval EFI_SUCCESS Capsule on Disk images are successfully relocated. + +**/ +EFI_STATUS +EFIAPI +CoDRelocateCapsule( + UINTN MaxRetry + ) +{ + if (!PcdGetBool (PcdCapsuleOnDiskSupport)) { + return EFI_UNSUPPORTED; + } + + // + // Clear CapsuleOnDisk Flag firstly. + // + CoDClearCapsuleOnDiskFlag (); + + // + // If Capsule In Ram is supported, delivery capsules through memory + // + if (PcdGetBool (PcdCapsuleInRamSupport)) { + DEBUG ((DEBUG_INFO, "Capsule In Ram is supported, call gRT->UpdateCapsule().\n")); + return RelocateCapsuleToRam (MaxRetry); + } else { + DEBUG ((DEBUG_INFO, "Reallcoate all Capsule on Disks to %s in RootDir.\n", (CHAR16 *)PcdGetPtr(PcdCoDRelocationFileName))); + return RelocateCapsuleToDisk (MaxRetry); + } +} + +/** + Remove the temp file from the root of EFI System Partition. + Device enumeration like USB costs time, user can input MaxRetry to tell function to retry. + Function will stall 100ms between each retry. + + @param[in] MaxRetry Max Connection Retry. Stall 100ms between each connection try to ensure + devices like USB can get enumerated. Input 0 means no retry. + + @retval EFI_SUCCESS Remove the temp file successfully. + +**/ +EFI_STATUS +EFIAPI +CoDRemoveTempFile ( + UINTN MaxRetry + ) +{ + EFI_STATUS Status; + UINTN DataSize; + UINT16 *LoadOptionNumber; + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Fs; + EFI_HANDLE FsHandle; + EFI_FILE_HANDLE RootDir; + EFI_FILE_HANDLE TempCodFile; + + RootDir = NULL; + TempCodFile = NULL; + DataSize = sizeof(UINT16); + + LoadOptionNumber = AllocatePool (sizeof(UINT16)); + if (LoadOptionNumber == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + // + // Check if capsule files are relocated + // + Status = gRT->GetVariable ( + COD_RELOCATION_LOAD_OPTION_VAR_NAME, + &gEfiCapsuleVendorGuid, + NULL, + &DataSize, + (VOID *)LoadOptionNumber + ); + if (EFI_ERROR(Status) || DataSize != sizeof(UINT16)) { + goto EXIT; + } + + // + // Get the EFI file system from the boot option where the capsules are relocated + // + Status = GetEfiSysPartitionFromActiveBootOption(MaxRetry, &LoadOptionNumber, &FsHandle); + if (EFI_ERROR(Status)) { + goto EXIT; + } + + Status = gBS->HandleProtocol(FsHandle, &gEfiSimpleFileSystemProtocolGuid, (VOID **)&Fs); + if (EFI_ERROR(Status)) { + goto EXIT; + } + + Status = Fs->OpenVolume(Fs, &RootDir); + if (EFI_ERROR(Status)) { + goto EXIT; + } + + // + // Delete the TempCoDFile + // + Status = RootDir->Open( + RootDir, + &TempCodFile, + (CHAR16 *)PcdGetPtr(PcdCoDRelocationFileName), + EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, + 0 + ); + if (EFI_ERROR(Status)) { + goto EXIT; + } + + TempCodFile->Delete(TempCodFile); + + // + // Clear "CoDRelocationLoadOption" variable + // + Status = gRT->SetVariable ( + COD_RELOCATION_LOAD_OPTION_VAR_NAME, + &gEfiCapsuleVendorGuid, + 0, + 0, + NULL + ); + +EXIT: + if (LoadOptionNumber != NULL) { + FreePool (LoadOptionNumber); + } + + if (RootDir != NULL) { + RootDir->Close(RootDir); + } + + return Status; +} |