/** @file The implementation supports Capusle on Disk. Copyright (c) 2019, Intel Corporation. All rights reserved.
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; }