blob: fd563643eb6346c639d4d7bb86cf833d101cc20f [file] [log] [blame]
/** @file
Internal functions to operate Working Block Space.
Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "FaultTolerantWrite.h"
EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER mWorkingBlockHeader = { ZERO_GUID, 0, 0, 0, 0, { 0, 0, 0 }, 0 };
/**
Initialize a local work space header.
Since Signature and WriteQueueSize have been known, Crc can be calculated out,
then the work space header will be fixed.
@param[in] WorkSpaceLength Length in bytes of the FTW workspace area.
**/
VOID
InitializeLocalWorkSpaceHeader (
IN UINTN WorkSpaceLength
)
{
//
// Check signature with gEdkiiWorkingBlockSignatureGuid.
//
if (CompareGuid (&gEdkiiWorkingBlockSignatureGuid, &mWorkingBlockHeader.Signature)) {
//
// The local work space header has been initialized.
//
return;
}
SetMem (
&mWorkingBlockHeader,
sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER),
FTW_ERASED_BYTE
);
//
// Here using gEdkiiWorkingBlockSignatureGuid as the signature.
//
CopyMem (
&mWorkingBlockHeader.Signature,
&gEdkiiWorkingBlockSignatureGuid,
sizeof (EFI_GUID)
);
mWorkingBlockHeader.WriteQueueSize = WorkSpaceLength - sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER);
//
// Crc is calculated with all the fields except Crc and STATE, so leave them as FTW_ERASED_BYTE.
//
//
// Calculate the Crc of woking block header
//
mWorkingBlockHeader.Crc = FtwCalculateCrc32 (
&mWorkingBlockHeader,
sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER)
);
mWorkingBlockHeader.WorkingBlockValid = FTW_VALID_STATE;
mWorkingBlockHeader.WorkingBlockInvalid = FTW_INVALID_STATE;
}
/**
Check to see if it is a valid work space.
@param WorkingHeader Pointer of working block header
@retval TRUE The work space is valid.
@retval FALSE The work space is invalid.
**/
BOOLEAN
IsValidWorkSpace (
IN EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *WorkingHeader
)
{
if (WorkingHeader == NULL) {
return FALSE;
}
if (CompareMem (WorkingHeader, &mWorkingBlockHeader, sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER)) == 0) {
return TRUE;
}
DEBUG ((DEBUG_INFO, "Ftw: Work block header check mismatch\n"));
return FALSE;
}
/**
Initialize a work space when there is no work space.
@param WorkingHeader Pointer of working block header
@retval EFI_SUCCESS The function completed successfully
@retval EFI_ABORTED The function could not complete successfully.
**/
EFI_STATUS
InitWorkSpaceHeader (
IN EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *WorkingHeader
)
{
if (WorkingHeader == NULL) {
return EFI_INVALID_PARAMETER;
}
CopyMem (WorkingHeader, &mWorkingBlockHeader, sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER));
return EFI_SUCCESS;
}
/**
Read work space data from work block or spare block.
@param FvBlock FVB Protocol interface to access the block.
@param BlockSize The size of the block.
@param Lba Lba of the block.
@param Offset The offset within the block.
@param Length The number of bytes to read from the block.
@param Buffer The data is read.
@retval EFI_SUCCESS The function completed successfully.
@retval EFI_ABORTED The function could not complete successfully.
**/
EFI_STATUS
ReadWorkSpaceData (
IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvBlock,
IN UINTN BlockSize,
IN EFI_LBA Lba,
IN UINTN Offset,
IN UINTN Length,
OUT UINT8 *Buffer
)
{
EFI_STATUS Status;
UINT8 *Ptr;
UINTN MyLength;
//
// Calculate the real Offset and Lba to write.
//
while (Offset >= BlockSize) {
Offset -= BlockSize;
Lba++;
}
Ptr = Buffer;
while (Length > 0) {
if ((Offset + Length) > BlockSize) {
MyLength = BlockSize - Offset;
} else {
MyLength = Length;
}
Status = FvBlock->Read (
FvBlock,
Lba,
Offset,
&MyLength,
Ptr
);
if (EFI_ERROR (Status)) {
return EFI_ABORTED;
}
Offset = 0;
Length -= MyLength;
Ptr += MyLength;
Lba++;
}
return EFI_SUCCESS;
}
/**
Write work space data to work block.
@param FvBlock FVB Protocol interface to access the block.
@param BlockSize The size of the block.
@param Lba Lba of the block.
@param Offset The offset within the block to place the data.
@param Length The number of bytes to write to the block.
@param Buffer The data to write.
@retval EFI_SUCCESS The function completed successfully.
@retval EFI_ABORTED The function could not complete successfully.
**/
EFI_STATUS
WriteWorkSpaceData (
IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvBlock,
IN UINTN BlockSize,
IN EFI_LBA Lba,
IN UINTN Offset,
IN UINTN Length,
IN UINT8 *Buffer
)
{
EFI_STATUS Status;
UINT8 *Ptr;
UINTN MyLength;
//
// Calculate the real Offset and Lba to write.
//
while (Offset >= BlockSize) {
Offset -= BlockSize;
Lba++;
}
Ptr = Buffer;
while (Length > 0) {
if ((Offset + Length) > BlockSize) {
MyLength = BlockSize - Offset;
} else {
MyLength = Length;
}
Status = FvBlock->Write (
FvBlock,
Lba,
Offset,
&MyLength,
Ptr
);
if (EFI_ERROR (Status)) {
return EFI_ABORTED;
}
Offset = 0;
Length -= MyLength;
Ptr += MyLength;
Lba++;
}
return EFI_SUCCESS;
}
/**
Read from working block to refresh the work space in memory.
@param FtwDevice Point to private data of FTW driver
@retval EFI_SUCCESS The function completed successfully
@retval EFI_ABORTED The function could not complete successfully.
**/
EFI_STATUS
WorkSpaceRefresh (
IN EFI_FTW_DEVICE *FtwDevice
)
{
EFI_STATUS Status;
UINTN RemainingSpaceSize;
//
// Initialize WorkSpace as FTW_ERASED_BYTE
//
SetMem (
FtwDevice->FtwWorkSpace,
FtwDevice->FtwWorkSpaceSize,
FTW_ERASED_BYTE
);
//
// Read from working block
//
Status = ReadWorkSpaceData (
FtwDevice->FtwFvBlock,
FtwDevice->WorkBlockSize,
FtwDevice->FtwWorkSpaceLba,
FtwDevice->FtwWorkSpaceBase,
FtwDevice->FtwWorkSpaceSize,
FtwDevice->FtwWorkSpace
);
if (EFI_ERROR (Status)) {
return EFI_ABORTED;
}
//
// Refresh the FtwLastWriteHeader
//
Status = FtwGetLastWriteHeader (
FtwDevice->FtwWorkSpaceHeader,
FtwDevice->FtwWorkSpaceSize,
&FtwDevice->FtwLastWriteHeader
);
RemainingSpaceSize = FtwDevice->FtwWorkSpaceSize - ((UINTN)FtwDevice->FtwLastWriteHeader - (UINTN)FtwDevice->FtwWorkSpace);
DEBUG ((DEBUG_INFO, "Ftw: Remaining work space size - %x\n", RemainingSpaceSize));
//
// If FtwGetLastWriteHeader() returns error, or the remaining space size is even not enough to contain
// one EFI_FAULT_TOLERANT_WRITE_HEADER + one EFI_FAULT_TOLERANT_WRITE_RECORD(It will cause that the header
// pointed by FtwDevice->FtwLastWriteHeader or record pointed by FtwDevice->FtwLastWriteRecord may contain invalid data),
// it needs to reclaim work space.
//
if (EFI_ERROR (Status) || (RemainingSpaceSize < sizeof (EFI_FAULT_TOLERANT_WRITE_HEADER) + sizeof (EFI_FAULT_TOLERANT_WRITE_RECORD))) {
//
// reclaim work space in working block.
//
Status = FtwReclaimWorkSpace (FtwDevice, TRUE);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "Ftw: Reclaim workspace - %r\n", Status));
return EFI_ABORTED;
}
//
// Read from working block again
//
Status = ReadWorkSpaceData (
FtwDevice->FtwFvBlock,
FtwDevice->WorkBlockSize,
FtwDevice->FtwWorkSpaceLba,
FtwDevice->FtwWorkSpaceBase,
FtwDevice->FtwWorkSpaceSize,
FtwDevice->FtwWorkSpace
);
if (EFI_ERROR (Status)) {
return EFI_ABORTED;
}
Status = FtwGetLastWriteHeader (
FtwDevice->FtwWorkSpaceHeader,
FtwDevice->FtwWorkSpaceSize,
&FtwDevice->FtwLastWriteHeader
);
if (EFI_ERROR (Status)) {
return EFI_ABORTED;
}
}
//
// Refresh the FtwLastWriteRecord
//
Status = FtwGetLastWriteRecord (
FtwDevice->FtwLastWriteHeader,
&FtwDevice->FtwLastWriteRecord
);
if (EFI_ERROR (Status)) {
return EFI_ABORTED;
}
return EFI_SUCCESS;
}
/**
Reclaim the work space on the working block.
@param FtwDevice Point to private data of FTW driver
@param PreserveRecord Whether to preserve the working record is needed
@retval EFI_SUCCESS The function completed successfully
@retval EFI_OUT_OF_RESOURCES Allocate memory error
@retval EFI_ABORTED The function could not complete successfully
**/
EFI_STATUS
FtwReclaimWorkSpace (
IN EFI_FTW_DEVICE *FtwDevice,
IN BOOLEAN PreserveRecord
)
{
EFI_STATUS Status;
UINTN Length;
EFI_FAULT_TOLERANT_WRITE_HEADER *Header;
UINT8 *TempBuffer;
UINTN TempBufferSize;
UINTN SpareBufferSize;
UINT8 *SpareBuffer;
EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *WorkingBlockHeader;
UINTN Index;
UINT8 *Ptr;
EFI_LBA WorkSpaceLbaOffset;
DEBUG ((DEBUG_INFO, "Ftw: start to reclaim work space\n"));
WorkSpaceLbaOffset = FtwDevice->FtwWorkSpaceLba - FtwDevice->FtwWorkBlockLba;
//
// Read all original data from working block to a memory buffer
//
TempBufferSize = FtwDevice->NumberOfWorkBlock * FtwDevice->WorkBlockSize;
TempBuffer = AllocateZeroPool (TempBufferSize);
if (TempBuffer == NULL) {
return EFI_OUT_OF_RESOURCES;
}
Ptr = TempBuffer;
for (Index = 0; Index < FtwDevice->NumberOfWorkBlock; Index += 1) {
Length = FtwDevice->WorkBlockSize;
Status = FtwDevice->FtwFvBlock->Read (
FtwDevice->FtwFvBlock,
FtwDevice->FtwWorkBlockLba + Index,
0,
&Length,
Ptr
);
if (EFI_ERROR (Status)) {
FreePool (TempBuffer);
return EFI_ABORTED;
}
Ptr += Length;
}
//
// Clean up the workspace, remove all the completed records.
//
Ptr = TempBuffer +
(UINTN)WorkSpaceLbaOffset * FtwDevice->WorkBlockSize +
FtwDevice->FtwWorkSpaceBase;
//
// Clear the content of buffer that will save the new work space data
//
SetMem (Ptr, FtwDevice->FtwWorkSpaceSize, FTW_ERASED_BYTE);
//
// Copy EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER to buffer
//
CopyMem (
Ptr,
FtwDevice->FtwWorkSpaceHeader,
sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER)
);
if (PreserveRecord) {
//
// Get the last record following the header,
//
Status = FtwGetLastWriteHeader (
FtwDevice->FtwWorkSpaceHeader,
FtwDevice->FtwWorkSpaceSize,
&FtwDevice->FtwLastWriteHeader
);
Header = FtwDevice->FtwLastWriteHeader;
if (!EFI_ERROR (Status) && (Header != NULL) && (Header->Complete != FTW_VALID_STATE) && (Header->HeaderAllocated == FTW_VALID_STATE)) {
CopyMem (
Ptr + sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER),
FtwDevice->FtwLastWriteHeader,
FTW_WRITE_TOTAL_SIZE (Header->NumberOfWrites, Header->PrivateDataSize)
);
}
}
CopyMem (
FtwDevice->FtwWorkSpace,
Ptr,
FtwDevice->FtwWorkSpaceSize
);
FtwGetLastWriteHeader (
FtwDevice->FtwWorkSpaceHeader,
FtwDevice->FtwWorkSpaceSize,
&FtwDevice->FtwLastWriteHeader
);
FtwGetLastWriteRecord (
FtwDevice->FtwLastWriteHeader,
&FtwDevice->FtwLastWriteRecord
);
//
// Set the WorkingBlockValid and WorkingBlockInvalid as INVALID
//
WorkingBlockHeader = (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *)(TempBuffer +
(UINTN)WorkSpaceLbaOffset * FtwDevice->WorkBlockSize +
FtwDevice->FtwWorkSpaceBase);
WorkingBlockHeader->WorkingBlockValid = FTW_INVALID_STATE;
WorkingBlockHeader->WorkingBlockInvalid = FTW_INVALID_STATE;
//
// Try to keep the content of spare block
// Save spare block into a spare backup memory buffer (Sparebuffer)
//
SpareBufferSize = FtwDevice->SpareAreaLength;
SpareBuffer = AllocatePool (SpareBufferSize);
if (SpareBuffer == NULL) {
FreePool (TempBuffer);
return EFI_OUT_OF_RESOURCES;
}
Ptr = SpareBuffer;
for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) {
Length = FtwDevice->SpareBlockSize;
Status = FtwDevice->FtwBackupFvb->Read (
FtwDevice->FtwBackupFvb,
FtwDevice->FtwSpareLba + Index,
0,
&Length,
Ptr
);
if (EFI_ERROR (Status)) {
FreePool (TempBuffer);
FreePool (SpareBuffer);
return EFI_ABORTED;
}
Ptr += Length;
}
//
// Write the memory buffer to spare block
//
Status = FtwEraseSpareBlock (FtwDevice);
if (EFI_ERROR (Status)) {
FreePool (TempBuffer);
FreePool (SpareBuffer);
return EFI_ABORTED;
}
Ptr = TempBuffer;
for (Index = 0; TempBufferSize > 0; Index += 1) {
if (TempBufferSize > FtwDevice->SpareBlockSize) {
Length = FtwDevice->SpareBlockSize;
} else {
Length = TempBufferSize;
}
Status = FtwDevice->FtwBackupFvb->Write (
FtwDevice->FtwBackupFvb,
FtwDevice->FtwSpareLba + Index,
0,
&Length,
Ptr
);
if (EFI_ERROR (Status)) {
FreePool (TempBuffer);
FreePool (SpareBuffer);
return EFI_ABORTED;
}
Ptr += Length;
TempBufferSize -= Length;
}
//
// Free TempBuffer
//
FreePool (TempBuffer);
//
// Set the WorkingBlockValid in spare block
//
Status = FtwUpdateFvState (
FtwDevice->FtwBackupFvb,
FtwDevice->SpareBlockSize,
FtwDevice->FtwSpareLba + FtwDevice->FtwWorkSpaceLbaInSpare,
FtwDevice->FtwWorkSpaceBaseInSpare + sizeof (EFI_GUID) + sizeof (UINT32),
WORKING_BLOCK_VALID
);
if (EFI_ERROR (Status)) {
FreePool (SpareBuffer);
return EFI_ABORTED;
}
//
// Before erase the working block, set WorkingBlockInvalid in working block.
//
// Offset = OFFSET_OF(EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER,
// WorkingBlockInvalid);
//
Status = FtwUpdateFvState (
FtwDevice->FtwFvBlock,
FtwDevice->WorkBlockSize,
FtwDevice->FtwWorkSpaceLba,
FtwDevice->FtwWorkSpaceBase + sizeof (EFI_GUID) + sizeof (UINT32),
WORKING_BLOCK_INVALID
);
if (EFI_ERROR (Status)) {
FreePool (SpareBuffer);
return EFI_ABORTED;
}
FtwDevice->FtwWorkSpaceHeader->WorkingBlockInvalid = FTW_VALID_STATE;
//
// Write the spare block to working block
//
Status = FlushSpareBlockToWorkingBlock (FtwDevice);
if (EFI_ERROR (Status)) {
FreePool (SpareBuffer);
return Status;
}
//
// Restore spare backup buffer into spare block , if no failure happened during FtwWrite.
//
Status = FtwEraseSpareBlock (FtwDevice);
if (EFI_ERROR (Status)) {
FreePool (SpareBuffer);
return EFI_ABORTED;
}
Ptr = SpareBuffer;
for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) {
Length = FtwDevice->SpareBlockSize;
Status = FtwDevice->FtwBackupFvb->Write (
FtwDevice->FtwBackupFvb,
FtwDevice->FtwSpareLba + Index,
0,
&Length,
Ptr
);
if (EFI_ERROR (Status)) {
FreePool (SpareBuffer);
return EFI_ABORTED;
}
Ptr += Length;
}
FreePool (SpareBuffer);
DEBUG ((DEBUG_INFO, "Ftw: reclaim work space successfully\n"));
return EFI_SUCCESS;
}