/** @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.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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);
}