| /** @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; | |
| } |