/** @file ;****************************************************************************** ;* Copyright (c) 2012 - 2019, Insyde Software Corp. All Rights Reserved. ;* ;* You may not reproduce, distribute, publish, display, perform, modify, adapt, ;* transmit, broadcast, present, recite, release, license or otherwise exploit ;* any part of this publication in any form, by any means, without the prior ;* written permission of Insyde Software Corporation. ;* ;****************************************************************************** */ #include "FtwLite.h" /** Check to see if it is a valid 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. **/ BOOLEAN IsValidWorkSpace ( IN EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *WorkingHeader ) { EFI_STATUS Status; EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER WorkingBlockHeader; ASSERT (WorkingHeader != NULL); if (WorkingHeader->WorkingBlockValid != FTW_VALID_STATE) { return FALSE; } // // Check signature with gEfiSystemNvDataFvGuid // if (!CompareGuid (&gEfiSystemNvDataFvGuid, &WorkingHeader->Signature)) { return FALSE; } // // Check the CRC of header // CopyMem ( &WorkingBlockHeader, WorkingHeader, sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER) ); // // Filter out the Crc and State fields // SetMem (&WorkingBlockHeader.Crc, sizeof (UINT32), FTW_ERASED_BYTE); WorkingBlockHeader.WorkingBlockValid = FTW_ERASE_POLARITY; WorkingBlockHeader.WorkingBlockInvalid = FTW_ERASE_POLARITY; // // Calculate the Crc of woking block header // Status = RuntimeDriverCalculateCrc32 ( (UINT8 *) &WorkingBlockHeader, sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER), &WorkingBlockHeader.Crc ); if (WorkingBlockHeader.Crc != WorkingHeader->Crc) { DEBUG ((mFtwLiteError, "FtwLite: Work block header CRC check error\n")); return FALSE; } return TRUE; } /** 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 ) { EFI_STATUS Status; ASSERT (WorkingHeader != NULL); // // Here using gEfiSystemNvDataFvGuid as the signature. // CopyMem ( &WorkingHeader->Signature, &gEfiSystemNvDataFvGuid, sizeof (EFI_GUID) ); WorkingHeader->WriteQueueSize = (UINTN) FdmGetNAtSize (&gH2OFlashMapRegionFtwStateGuid, 1) - sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER); // // Crc is calculated with all the fields except Crc and STATE // WorkingHeader->WorkingBlockValid = FTW_ERASE_POLARITY; WorkingHeader->WorkingBlockInvalid = FTW_ERASE_POLARITY; SetMem (&WorkingHeader->Crc, sizeof (UINT32), FTW_ERASED_BYTE); // // Calculate the CRC value // Status = RuntimeDriverCalculateCrc32 ( (UINT8 *) WorkingHeader, sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER), &WorkingHeader->Crc ); ASSERT_EFI_ERROR (Status); // // Restore the WorkingBlockValid flag to VALID state // WorkingHeader->WorkingBlockValid = FTW_VALID_STATE; WorkingHeader->WorkingBlockInvalid = FTW_INVALID_STATE; return EFI_SUCCESS; } /** Update a bit of state on a block device. The location of the bit is calculated by the (Lba, Offset, bit). Here bit is determined by the the name of a certain bit. @param FtwLiteDevice The private data of FTW_LITE driver. @param FvBlock FVB Protocol interface to access SrcBlock and DestBlock. @param Lba Lba of a block. @param Offset Offset on the Lba. @param Address The Address of FvState. @param NewBit New value that will override the old value if it can be change. @retval EFI_SUCCESS A state bit has been updated successfully @retval EFI_ABORTED Read block fail. @return Others Access block device error. **/ EFI_STATUS FtwUpdateFvState ( IN EFI_FTW_LITE_DEVICE *FtwLiteDevice, IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvBlock, IN EFI_LBA Lba, IN UINTN Offset, IN UINTN Address, IN UINT8 NewBit ) { EFI_STATUS Status; UINT8 State; UINTN Length; UINT8 ReadState; UINTN TryWritetimes; BOOLEAN WriteSuccess; // // Read state from device, assume State is only one byte. // Length = sizeof (UINT8); if (mSmst == NULL) { Lba += Offset / FtwLiteDevice->SizeOfSpareBlock; Offset = Offset % FtwLiteDevice->SizeOfSpareBlock; Status = FvBlock->Read (FvBlock, Lba, Offset, &Length, &State); if (EFI_ERROR (Status)) { return EFI_ABORTED; } } else { CopyMem (&State, (UINT8 *) Address, Length); } State ^= FTW_POLARITY_REVERT; State |= NewBit; State ^= FTW_POLARITY_REVERT; WriteSuccess = FALSE; TryWritetimes = 0; Status = EFI_ABORTED; // // Start try write state to device and using do-while to make sure // at least write state once. // do { // // Write state back to device // Length = sizeof (UINT8); if (mSmst == NULL) { Status = FvBlock->Write (FvBlock, Lba, Offset, &Length, &State); } else { Status = FtwLiteDevice->SmmFwbServices->Write ( FtwLiteDevice->SmmFwbServices, (UINTN)Address, &Length, &State ); } Length = sizeof (UINT8); if (mSmst == NULL) { Status = FvBlock->Read (FvBlock, Lba, Offset, &Length, &ReadState); } else { CopyMem (&ReadState, (UINT8 *) Address, Length); } // // Check write data is whther correct // WriteSuccess = (ReadState == State) ? TRUE : FALSE; TryWritetimes++; } while (!WriteSuccess && TryWritetimes < FTW_MAX_TRY_ACCESS_FVB_TIMES); // // if write failed, set status as EFI_ABORT // if (!WriteSuccess) { Status = EFI_ABORTED; } return Status; } /** Get the last Write record pointer. The last record is the record whose 'complete' state hasn't been set. After all, this header may be a EMPTY header entry for next Allocate. @param FtwLiteDevice Private data of this driver. @param FtwLastRecord 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 FtwGetLastRecord ( IN EFI_FTW_LITE_DEVICE *FtwLiteDevice, OUT EFI_FTW_LITE_RECORD **FtwLastRecord ) { EFI_FTW_LITE_RECORD *Record; Record = (EFI_FTW_LITE_RECORD *) (FtwLiteDevice->FtwWorkSpaceHeader + 1); while (Record->WriteCompleted == FTW_VALID_STATE) { // // If Offset exceed the FTW work space boudary, return error. // if ((UINTN) ((UINT8 *) Record - FtwLiteDevice->FtwWorkSpace) > FtwLiteDevice->FtwWorkSpaceSize) { return EFI_ABORTED; } Record++; } // // Last write record is found // *FtwLastRecord = Record; return EFI_SUCCESS; } /** Read from working block to refresh the work space in memory. @param FtwLiteDevice 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_LITE_DEVICE *FtwLiteDevice ) { EFI_STATUS Status; UINTN Length; UINTN Offset; EFI_FTW_LITE_RECORD *Record; UINTN Index; // // Initialize WorkSpace as FTW_ERASED_BYTE // SetMem (FtwLiteDevice->FtwWorkSpace, FtwLiteDevice->FtwWorkSpaceSize, FTW_ERASED_BYTE); // // Read from working block // if (mSmst == NULL) { if (FtwLiteDevice->NumberOfFtwWorkBlock != 0) { for (Index = 0; Index < FtwLiteDevice->NumberOfFtwWorkBlock; Index++) { Length = FtwLiteDevice->FtwWorkSpaceSize / FtwLiteDevice->NumberOfFtwWorkBlock; Status = FtwLiteDevice->FtwFvBlock->Read ( FtwLiteDevice->FtwFvBlock, FtwLiteDevice->FtwWorkSpaceLba + Index, FtwLiteDevice->FtwWorkSpaceBase, &Length, FtwLiteDevice->FtwWorkSpace + (Length * Index) ); if (EFI_ERROR (Status)) { return EFI_ABORTED; } } } else { Length = FtwLiteDevice->FtwWorkSpaceSize; Status = FtwLiteDevice->FtwFvBlock->Read ( FtwLiteDevice->FtwFvBlock, FtwLiteDevice->FtwWorkSpaceLba, FtwLiteDevice->FtwWorkSpaceBase, &Length, FtwLiteDevice->FtwWorkSpace ); if (EFI_ERROR (Status)) { return EFI_ABORTED; } } } else { Length = FtwLiteDevice->FtwWorkSpaceSize; CopyMem (FtwLiteDevice->FtwWorkSpace, (UINT8 *) (UINTN) FtwLiteDevice->WorkSpaceAddress, Length); } // // Refresh the FtwLastRecord // Status = FtwGetLastRecord (FtwLiteDevice, &FtwLiteDevice->FtwLastRecord); Record = FtwLiteDevice->FtwLastRecord; Offset = (UINTN) (UINT8 *) Record - (UINTN) FtwLiteDevice->FtwWorkSpace; // // IF work space has error or Record is out of the workspace limit, THEN // call reclaim. // if (EFI_ERROR (Status) || (Offset + WRITE_TOTAL_SIZE >= FtwLiteDevice->FtwWorkSpaceSize)) { // // reclaim work space in working block. // Status = FtwReclaimWorkSpace (FtwLiteDevice); if (EFI_ERROR (Status)) { DEBUG ((mFtwLiteError, "FtwLite: Reclaim workspace - %r\n", Status)); return EFI_ABORTED; } } return EFI_SUCCESS; } /** Reclaim the work space. Get rid of all the completed write records and write records in the Fault Tolerant work space. @param FtwLiteDevice Point to private data of FTW driver. @param FtwSpaceBuffer Buffer to contain the reclaimed clean data. @param BufferSize Size of the FtwSpaceBuffer. @retval EFI_SUCCESS The function completed successfully. @retval EFI_BUFFER_TOO_SMALL The FtwSpaceBuffer is too small. @retval EFI_ABORTED The function could not complete successfully. **/ EFI_STATUS CleanupWorkSpace ( IN EFI_FTW_LITE_DEVICE *FtwLiteDevice, IN OUT UINT8 *FtwSpaceBuffer, IN UINTN BufferSize ) { UINTN Length; EFI_FTW_LITE_RECORD *Record; // // To check if the buffer is large enough // Length = FtwLiteDevice->FtwWorkSpaceSize; if (BufferSize < Length) { return EFI_BUFFER_TOO_SMALL; } // // Clear the content of buffer that will save the new work space data // SetMem (FtwSpaceBuffer, Length, FTW_ERASED_BYTE); // // Copy EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER to buffer // CopyMem ( FtwSpaceBuffer, FtwLiteDevice->FtwWorkSpaceHeader, sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER) ); // // Get the last record // Record = FtwLiteDevice->FtwLastRecord; if ((Record != NULL) && (Record->WriteAllocated == FTW_VALID_STATE) && (Record->WriteCompleted != FTW_VALID_STATE)) { CopyMem ( (UINT8 *) FtwSpaceBuffer + sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER), Record, WRITE_TOTAL_SIZE ); } return EFI_SUCCESS; } /** Reclaim the work space on the working block. @param FtwLiteDevice Point to private data of FTW driver. @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_LITE_DEVICE *FtwLiteDevice ) { EFI_STATUS Status; UINT8 *TempBuffer; UINTN TempBufferSize; UINT8 *Ptr; UINTN SpareBufferSize; UINT8 *SpareBuffer; EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *WorkingBlockHeader; DEBUG ((mFtwLiteError, "FtwLite: start to reclaim work space\n")); // // Read all original data from working block to a memory buffer // TempBufferSize = FtwLiteDevice->SpareAreaLength; TempBuffer = FtwAllocateZeroBuffer (FtwLiteDevice, TempBufferSize); if (TempBuffer == NULL) { return EFI_OUT_OF_RESOURCES; } ZeroMem (TempBuffer, TempBufferSize); Ptr = TempBuffer; Status = FtwLiteReadWorkBlock (FtwLiteDevice, TempBuffer); if (EFI_ERROR (Status)) { FtwFreePool (FtwLiteDevice, (VOID **)&TempBuffer); return EFI_ABORTED; } // // Clean up the workspace, remove all the completed records. // Ptr = TempBuffer + ((UINTN) (FtwLiteDevice->FtwWorkSpaceLba - FtwLiteDevice->FtwWorkBlockLba)) * FtwLiteDevice->SizeOfSpareBlock + FtwLiteDevice->FtwWorkSpaceBase; Status = CleanupWorkSpace ( FtwLiteDevice, Ptr, FtwLiteDevice->FtwWorkSpaceSize ); CopyMem ( FtwLiteDevice->FtwWorkSpace, Ptr, FtwLiteDevice->FtwWorkSpaceSize ); Status = FtwGetLastRecord (FtwLiteDevice, &FtwLiteDevice->FtwLastRecord); // // Set the WorkingBlockValid and WorkingBlockInvalid as INVALID // WorkingBlockHeader = (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *) Ptr; 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 = FtwLiteDevice->SpareAreaLength; SpareBuffer = FtwAllocateZeroBuffer (FtwLiteDevice, SpareBufferSize); if (SpareBuffer == NULL) { FtwFreePool (FtwLiteDevice, (VOID **)&TempBuffer); return EFI_OUT_OF_RESOURCES; } Ptr = SpareBuffer; Status = FtwLiteReadSpareBlock (FtwLiteDevice, SpareBuffer); // // Write the memory buffer to spare block // Status = FtwWriteSpareBlock (FtwLiteDevice, TempBuffer, SpareBufferSize, FTW_MAX_TRY_ACCESS_FVB_TIMES); if (EFI_ERROR (Status)) { FtwFreePool (FtwLiteDevice, (VOID **)&TempBuffer); FtwFreePool (FtwLiteDevice, (VOID **)&SpareBuffer); return EFI_ABORTED; } FtwFreePool (FtwLiteDevice, (VOID **)&TempBuffer); // // Write the spare block to working block // Status = FlushSpareBlockToWorkingBlock (FtwLiteDevice); if (EFI_ERROR (Status)) { FtwFreePool (FtwLiteDevice, (VOID **)&SpareBuffer); return Status; } // // Restore spare backup buffer into spare block , if no failure happened during FtwWrite. // Status = FtwWriteSpareBlock (FtwLiteDevice, SpareBuffer, SpareBufferSize, FTW_MAX_TRY_ACCESS_FVB_TIMES); if (EFI_ERROR (Status)) { FtwFreePool (FtwLiteDevice, (VOID **)&SpareBuffer); return EFI_ABORTED; } FtwFreePool (FtwLiteDevice, (VOID **)&SpareBuffer); DEBUG ((mFtwLiteError, "FtwLite: reclaim work space success\n")); return EFI_SUCCESS; }