/** @file | |
This driver installs gEdkiiFaultTolerantWriteGuid PPI to inform | |
the check for FTW last write data has been done. | |
Copyright (c) 2013 - 2018, Intel Corporation. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <PiPei.h> | |
#include <Guid/SystemNvDataGuid.h> | |
#include <Guid/FaultTolerantWrite.h> | |
#include <Library/PeiServicesLib.h> | |
#include <Library/PcdLib.h> | |
#include <Library/DebugLib.h> | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/HobLib.h> | |
#include <Library/SafeIntLib.h> | |
#include <Library/VariableFlashInfoLib.h> | |
EFI_PEI_PPI_DESCRIPTOR mPpiListVariable = { | |
(EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST), | |
&gEdkiiFaultTolerantWriteGuid, | |
NULL | |
}; | |
/** | |
Get the last Write Header pointer. | |
The last write header is the header whose 'complete' state hasn't been set. | |
After all, this header may be a EMPTY header entry for next Allocate. | |
@param FtwWorkSpaceHeader Pointer of the working block header | |
@param FtwWorkSpaceSize Size of the work space | |
@param FtwWriteHeader Pointer to retrieve the last write header | |
@retval EFI_SUCCESS Get the last write record successfully | |
@retval EFI_ABORTED The FTW work space is damaged | |
**/ | |
EFI_STATUS | |
FtwGetLastWriteHeader ( | |
IN EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *FtwWorkSpaceHeader, | |
IN UINTN FtwWorkSpaceSize, | |
OUT EFI_FAULT_TOLERANT_WRITE_HEADER **FtwWriteHeader | |
) | |
{ | |
UINTN Offset; | |
EFI_FAULT_TOLERANT_WRITE_HEADER *FtwHeader; | |
*FtwWriteHeader = NULL; | |
FtwHeader = (EFI_FAULT_TOLERANT_WRITE_HEADER *)(FtwWorkSpaceHeader + 1); | |
Offset = sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER); | |
while (FtwHeader->Complete == FTW_VALID_STATE) { | |
Offset += FTW_WRITE_TOTAL_SIZE (FtwHeader->NumberOfWrites, FtwHeader->PrivateDataSize); | |
// | |
// If Offset exceed the FTW work space boudary, return error. | |
// | |
if (Offset >= FtwWorkSpaceSize) { | |
*FtwWriteHeader = FtwHeader; | |
return EFI_ABORTED; | |
} | |
FtwHeader = (EFI_FAULT_TOLERANT_WRITE_HEADER *)((UINT8 *)FtwWorkSpaceHeader + Offset); | |
} | |
// | |
// Last write header is found | |
// | |
*FtwWriteHeader = FtwHeader; | |
return EFI_SUCCESS; | |
} | |
/** | |
Get the last Write Record pointer. The last write Record is the Record | |
whose DestinationCompleted state hasn't been set. After all, this Record | |
may be a EMPTY record entry for next write. | |
@param FtwWriteHeader Pointer to the write record header | |
@param FtwWriteRecord Pointer to retrieve the last write record | |
@retval EFI_SUCCESS Get the last write record successfully | |
@retval EFI_ABORTED The FTW work space is damaged | |
**/ | |
EFI_STATUS | |
FtwGetLastWriteRecord ( | |
IN EFI_FAULT_TOLERANT_WRITE_HEADER *FtwWriteHeader, | |
OUT EFI_FAULT_TOLERANT_WRITE_RECORD **FtwWriteRecord | |
) | |
{ | |
UINTN Index; | |
EFI_FAULT_TOLERANT_WRITE_RECORD *FtwRecord; | |
*FtwWriteRecord = NULL; | |
FtwRecord = (EFI_FAULT_TOLERANT_WRITE_RECORD *)(FtwWriteHeader + 1); | |
// | |
// Try to find the last write record "that has not completed" | |
// | |
for (Index = 0; Index < FtwWriteHeader->NumberOfWrites; Index += 1) { | |
if (FtwRecord->DestinationComplete != FTW_VALID_STATE) { | |
// | |
// The last write record is found | |
// | |
*FtwWriteRecord = FtwRecord; | |
return EFI_SUCCESS; | |
} | |
FtwRecord++; | |
if (FtwWriteHeader->PrivateDataSize != 0) { | |
FtwRecord = (EFI_FAULT_TOLERANT_WRITE_RECORD *)((UINTN)FtwRecord + (UINTN)FtwWriteHeader->PrivateDataSize); | |
} | |
} | |
// | |
// if Index == NumberOfWrites, then | |
// the last record has been written successfully, | |
// but the Header->Complete Flag has not been set. | |
// also return the last record. | |
// | |
if (Index == FtwWriteHeader->NumberOfWrites) { | |
*FtwWriteRecord = (EFI_FAULT_TOLERANT_WRITE_RECORD *)((UINTN)FtwRecord - FTW_RECORD_SIZE (FtwWriteHeader->PrivateDataSize)); | |
return EFI_SUCCESS; | |
} | |
return EFI_ABORTED; | |
} | |
/** | |
Check to see if it is a valid work space. | |
@param WorkingHeader Pointer of working block header | |
@param WorkingLength Working block length | |
@retval TRUE The work space is valid. | |
@retval FALSE The work space is invalid. | |
**/ | |
BOOLEAN | |
IsValidWorkSpace ( | |
IN EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *WorkingHeader, | |
IN UINTN WorkingLength | |
) | |
{ | |
UINT8 Data; | |
if (WorkingHeader == NULL) { | |
return FALSE; | |
} | |
if ((WorkingHeader->WorkingBlockValid != FTW_VALID_STATE) || (WorkingHeader->WorkingBlockInvalid == FTW_VALID_STATE)) { | |
DEBUG ((DEBUG_ERROR, "FtwPei: Work block header valid bit check error\n")); | |
return FALSE; | |
} | |
if (WorkingHeader->WriteQueueSize != (WorkingLength - sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER))) { | |
DEBUG ((DEBUG_ERROR, "FtwPei: Work block header WriteQueueSize check error\n")); | |
return FALSE; | |
} | |
// | |
// Check signature with gEdkiiWorkingBlockSignatureGuid | |
// | |
if (!CompareGuid (&gEdkiiWorkingBlockSignatureGuid, &WorkingHeader->Signature)) { | |
DEBUG ((DEBUG_ERROR, "FtwPei: Work block header signature check error, it should be gEdkiiWorkingBlockSignatureGuid\n")); | |
// | |
// To be compatible with old signature gEfiSystemNvDataFvGuid. | |
// | |
if (!CompareGuid (&gEfiSystemNvDataFvGuid, &WorkingHeader->Signature)) { | |
return FALSE; | |
} else { | |
Data = *(UINT8 *)(WorkingHeader + 1); | |
if (Data != 0xff) { | |
DEBUG ((DEBUG_ERROR, "FtwPei: Old format FTW structure can't be handled\n")); | |
ASSERT (FALSE); | |
return FALSE; | |
} | |
} | |
} | |
return TRUE; | |
} | |
/** | |
Main entry for Fault Tolerant Write PEIM. | |
@param[in] FileHandle Handle of the file being invoked. | |
@param[in] PeiServices Pointer to PEI Services table. | |
@retval EFI_SUCCESS If the interface could be successfully installed | |
@retval Others Returned from PeiServicesInstallPpi() | |
**/ | |
EFI_STATUS | |
EFIAPI | |
PeimFaultTolerantWriteInitialize ( | |
IN EFI_PEI_FILE_HANDLE FileHandle, | |
IN CONST EFI_PEI_SERVICES **PeiServices | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *FtwWorkingBlockHeader; | |
EFI_FAULT_TOLERANT_WRITE_HEADER *FtwLastWriteHeader; | |
EFI_FAULT_TOLERANT_WRITE_RECORD *FtwLastWriteRecord; | |
EFI_PHYSICAL_ADDRESS WorkSpaceAddress; | |
UINTN WorkSpaceLength; | |
EFI_PHYSICAL_ADDRESS SpareAreaAddress; | |
UINTN SpareAreaLength; | |
EFI_PHYSICAL_ADDRESS WorkSpaceInSpareArea; | |
UINT64 Size; | |
FAULT_TOLERANT_WRITE_LAST_WRITE_DATA FtwLastWrite; | |
FtwWorkingBlockHeader = NULL; | |
FtwLastWriteHeader = NULL; | |
FtwLastWriteRecord = NULL; | |
SpareAreaAddress = 0; | |
SpareAreaLength = 0; | |
WorkSpaceAddress = 0; | |
WorkSpaceLength = 0; | |
Status = GetVariableFlashFtwWorkingInfo (&WorkSpaceAddress, &Size); | |
ASSERT_EFI_ERROR (Status); | |
Status = SafeUint64ToUintn (Size, &WorkSpaceLength); | |
// This driver currently assumes the size will be UINTN so assert the value is safe for now. | |
ASSERT_EFI_ERROR (Status); | |
Status = GetVariableFlashFtwSpareInfo (&SpareAreaAddress, &Size); | |
ASSERT_EFI_ERROR (Status); | |
Status = SafeUint64ToUintn (Size, &SpareAreaLength); | |
// This driver currently assumes the size will be UINTN so assert the value is safe for now. | |
ASSERT_EFI_ERROR (Status); | |
// | |
// The address of FTW working base and spare base must not be 0. | |
// | |
ASSERT ((WorkSpaceAddress != 0) && (SpareAreaAddress != 0)); | |
FtwWorkingBlockHeader = (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *)(UINTN)WorkSpaceAddress; | |
if (IsValidWorkSpace (FtwWorkingBlockHeader, WorkSpaceLength)) { | |
Status = FtwGetLastWriteHeader ( | |
FtwWorkingBlockHeader, | |
WorkSpaceLength, | |
&FtwLastWriteHeader | |
); | |
if (!EFI_ERROR (Status)) { | |
Status = FtwGetLastWriteRecord ( | |
FtwLastWriteHeader, | |
&FtwLastWriteRecord | |
); | |
} | |
if (!EFI_ERROR (Status)) { | |
ASSERT (FtwLastWriteRecord != NULL); | |
if ((FtwLastWriteRecord->SpareComplete == FTW_VALID_STATE) && (FtwLastWriteRecord->DestinationComplete != FTW_VALID_STATE)) { | |
// | |
// If FTW last write was still in progress with SpareComplete set and DestinationComplete not set. | |
// It means the target buffer has been backed up in spare block, then target block has been erased, | |
// but the target buffer has not been writen in target block from spare block, we need to build | |
// FAULT_TOLERANT_WRITE_LAST_WRITE_DATA GUID hob to hold the FTW last write data. | |
// | |
FtwLastWrite.TargetAddress = (EFI_PHYSICAL_ADDRESS)(UINTN)((INT64)SpareAreaAddress + FtwLastWriteRecord->RelativeOffset); | |
FtwLastWrite.SpareAddress = SpareAreaAddress; | |
FtwLastWrite.Length = SpareAreaLength; | |
DEBUG (( | |
DEBUG_INFO, | |
"FtwPei last write data: TargetAddress - 0x%x SpareAddress - 0x%x Length - 0x%x\n", | |
(UINTN)FtwLastWrite.TargetAddress, | |
(UINTN)FtwLastWrite.SpareAddress, | |
(UINTN)FtwLastWrite.Length | |
)); | |
BuildGuidDataHob (&gEdkiiFaultTolerantWriteGuid, (VOID *)&FtwLastWrite, sizeof (FAULT_TOLERANT_WRITE_LAST_WRITE_DATA)); | |
} | |
} | |
} else { | |
FtwWorkingBlockHeader = NULL; | |
// | |
// If the working block workspace is not valid, try to find workspace in the spare block. | |
// | |
WorkSpaceInSpareArea = SpareAreaAddress + SpareAreaLength - WorkSpaceLength; | |
while (WorkSpaceInSpareArea >= SpareAreaAddress) { | |
if (CompareGuid (&gEdkiiWorkingBlockSignatureGuid, (EFI_GUID *)(UINTN)WorkSpaceInSpareArea)) { | |
// | |
// Found the workspace. | |
// | |
DEBUG ((DEBUG_INFO, "FtwPei: workspace in spare block is at 0x%x.\n", (UINTN)WorkSpaceInSpareArea)); | |
FtwWorkingBlockHeader = (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *)(UINTN)WorkSpaceInSpareArea; | |
break; | |
} | |
WorkSpaceInSpareArea = WorkSpaceInSpareArea - sizeof (EFI_GUID); | |
} | |
if ((FtwWorkingBlockHeader != NULL) && IsValidWorkSpace (FtwWorkingBlockHeader, WorkSpaceLength)) { | |
// | |
// It was workspace self reclaim, build FAULT_TOLERANT_WRITE_LAST_WRITE_DATA GUID hob for it. | |
// | |
FtwLastWrite.TargetAddress = WorkSpaceAddress - (WorkSpaceInSpareArea - SpareAreaAddress); | |
FtwLastWrite.SpareAddress = SpareAreaAddress; | |
FtwLastWrite.Length = SpareAreaLength; | |
DEBUG (( | |
DEBUG_INFO, | |
"FtwPei last write data: TargetAddress - 0x%x SpareAddress - 0x%x Length - 0x%x\n", | |
(UINTN)FtwLastWrite.TargetAddress, | |
(UINTN)FtwLastWrite.SpareAddress, | |
(UINTN)FtwLastWrite.Length | |
)); | |
BuildGuidDataHob (&gEdkiiFaultTolerantWriteGuid, (VOID *)&FtwLastWrite, sizeof (FAULT_TOLERANT_WRITE_LAST_WRITE_DATA)); | |
} else { | |
// | |
// Both are invalid. | |
// | |
DEBUG ((DEBUG_ERROR, "FtwPei: Both working and spare block are invalid.\n")); | |
} | |
} | |
// | |
// Install gEdkiiFaultTolerantWriteGuid PPI to inform the check for FTW last write data has been done. | |
// | |
return PeiServicesInstallPpi (&mPpiListVariable); | |
} |