mirror of https://github.com/acidanthera/audk.git
620 lines
18 KiB
C
620 lines
18 KiB
C
/** @file
|
|
|
|
Internal functions to operate Working Block Space.
|
|
|
|
Copyright (c) 2006 - 2017, Intel Corporation. All rights reserved.<BR>
|
|
This program and the accompanying materials
|
|
are licensed and made available under the terms and conditions of the BSD License
|
|
which accompanies this distribution. The full text of the license may be found at
|
|
http://opensource.org/licenses/bsd-license.php
|
|
|
|
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
|
|
|
**/
|
|
|
|
|
|
#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.
|
|
**/
|
|
VOID
|
|
InitializeLocalWorkSpaceHeader (
|
|
VOID
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
|
|
//
|
|
// 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 = PcdGet32 (PcdFlashNvStorageFtwWorkingSize) - 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
|
|
//
|
|
Status = gBS->CalculateCrc32 (
|
|
&mWorkingBlockHeader,
|
|
sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER),
|
|
&mWorkingBlockHeader.Crc
|
|
);
|
|
ASSERT_EFI_ERROR (Status);
|
|
|
|
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 ((EFI_D_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 ((EFI_D_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 ((EFI_D_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 ((EFI_D_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 ((EFI_D_INFO, "Ftw: reclaim work space successfully\n"));
|
|
|
|
return EFI_SUCCESS;
|
|
}
|