/** 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; }
/** To check if FtwRecord is the last record of FtwHeader. Because the FtwHeader has NumberOfWrites & PrivateDataSize, the FtwRecord can be determined if it is the last record of FtwHeader. @param FtwHeader Pointer to the write record header @param FtwRecord Pointer to the write record @retval TRUE FtwRecord is the last Record of the FtwHeader @retval FALSE FtwRecord is not the last Record of the FtwHeader **/ BOOLEAN IsLastRecordOfWrites ( IN EFI_FAULT_TOLERANT_WRITE_HEADER *FtwHeader, IN EFI_FAULT_TOLERANT_WRITE_RECORD *FtwRecord ) { UINT8 *Head; UINT8 *Ptr; Head = (UINT8 *) FtwHeader; Ptr = (UINT8 *) FtwRecord; Head += FTW_WRITE_TOTAL_SIZE (FtwHeader->NumberOfWrites - 1, FtwHeader->PrivateDataSize); return (BOOLEAN) (Head == Ptr); }
/** 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; }
/** Initialization for Fault Tolerant Write protocol. @param[in, out] FtwDevice Pointer to the FTW device structure @retval EFI_SUCCESS Initialize the FTW protocol successfully. @retval EFI_NOT_FOUND No proper FVB protocol was found. **/ EFI_STATUS InitFtwProtocol ( IN OUT EFI_FTW_DEVICE *FtwDevice ) { EFI_STATUS Status; EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *Fvb; UINTN Length; EFI_FAULT_TOLERANT_WRITE_HEADER *FtwHeader; UINTN Offset; EFI_HANDLE FvbHandle; EFI_LBA WorkSpaceLbaOffset; // // Find the right SMM Fvb protocol instance for FTW. // Status = FindFvbForFtw (FtwDevice); if (EFI_ERROR (Status)) { return EFI_NOT_FOUND; } // // Calculate the start LBA of working block. Working block is an area which // contains working space in its last block and has the same size as spare // block, unless there are not enough blocks before the block that contains // working space. // FtwDevice->FtwWorkBlockLba = FtwDevice->FtwWorkSpaceLba - FtwDevice->NumberOfSpareBlock + 1; ASSERT ((INT64) (FtwDevice->FtwWorkBlockLba) >= 0); // // Initialize other parameters, and set WorkSpace as FTW_ERASED_BYTE. // FtwDevice->FtwWorkSpace = (UINT8 *) (FtwDevice + 1); FtwDevice->FtwWorkSpaceHeader = (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *) FtwDevice->FtwWorkSpace; FtwDevice->FtwLastWriteHeader = NULL; FtwDevice->FtwLastWriteRecord = NULL; InitializeLocalWorkSpaceHeader (); // // Refresh the working space data from working block // Status = WorkSpaceRefresh (FtwDevice); ASSERT_EFI_ERROR (Status); // // If the working block workspace is not valid, try the spare block // if (!IsValidWorkSpace (FtwDevice->FtwWorkSpaceHeader)) { // // Read from spare block // WorkSpaceLbaOffset = FtwDevice->FtwWorkSpaceLba - FtwDevice->FtwWorkBlockLba; Length = FtwDevice->FtwWorkSpaceSize; Status = FtwDevice->FtwBackupFvb->Read ( FtwDevice->FtwBackupFvb, FtwDevice->FtwSpareLba + WorkSpaceLbaOffset, FtwDevice->FtwWorkSpaceBase, &Length, FtwDevice->FtwWorkSpace ); ASSERT_EFI_ERROR (Status); // // If spare block is valid, then replace working block content. // if (IsValidWorkSpace (FtwDevice->FtwWorkSpaceHeader)) { Status = FlushSpareBlockToWorkingBlock (FtwDevice); DEBUG ((EFI_D_ERROR, "Ftw: Restart working block update in InitFtwProtocol() - %r\n", Status)); FtwAbort (&FtwDevice->FtwInstance); // // Refresh work space. // Status = WorkSpaceRefresh (FtwDevice); ASSERT_EFI_ERROR (Status); } else { DEBUG ((EFI_D_ERROR, "Ftw: Both are invalid, init workspace\n")); // // If both are invalid, then initialize work space. // SetMem ( FtwDevice->FtwWorkSpace, FtwDevice->FtwWorkSpaceSize, FTW_ERASED_BYTE ); InitWorkSpaceHeader (FtwDevice->FtwWorkSpaceHeader); // // Initialize the work space // Status = FtwReclaimWorkSpace (FtwDevice, FALSE); ASSERT_EFI_ERROR (Status); } } // // If the FtwDevice->FtwLastWriteRecord is 1st record of write header && // (! SpareComplete) THEN call Abort(). // if ((FtwDevice->FtwLastWriteHeader->HeaderAllocated == FTW_VALID_STATE) && (FtwDevice->FtwLastWriteRecord->SpareComplete != FTW_VALID_STATE) && IsFirstRecordOfWrites (FtwDevice->FtwLastWriteHeader, FtwDevice->FtwLastWriteRecord) ) { DEBUG ((EFI_D_ERROR, "Ftw: Init.. find first record not SpareCompleted, abort()\n")); FtwAbort (&FtwDevice->FtwInstance); } // // If Header is incompleted and the last record has completed, then // call Abort() to set the Header->Complete FLAG. // if ((FtwDevice->FtwLastWriteHeader->Complete != FTW_VALID_STATE) && (FtwDevice->FtwLastWriteRecord->DestinationComplete == FTW_VALID_STATE) && IsLastRecordOfWrites (FtwDevice->FtwLastWriteHeader, FtwDevice->FtwLastWriteRecord) ) { DEBUG ((EFI_D_ERROR, "Ftw: Init.. find last record completed but header not, abort()\n")); FtwAbort (&FtwDevice->FtwInstance); } // // To check the workspace buffer following last Write header/records is EMPTY or not. // If it's not EMPTY, FTW also need to call reclaim(). // FtwHeader = FtwDevice->FtwLastWriteHeader; Offset = (UINT8 *) FtwHeader - FtwDevice->FtwWorkSpace; if (FtwDevice->FtwWorkSpace[Offset] != FTW_ERASED_BYTE) { Offset += FTW_WRITE_TOTAL_SIZE (FtwHeader->NumberOfWrites, FtwHeader->PrivateDataSize); } if (!IsErasedFlashBuffer (FtwDevice->FtwWorkSpace + Offset, FtwDevice->FtwWorkSpaceSize - Offset)) { Status = FtwReclaimWorkSpace (FtwDevice, TRUE); ASSERT_EFI_ERROR (Status); } // // Restart if it's boot block // if ((FtwDevice->FtwLastWriteHeader->Complete != FTW_VALID_STATE) && (FtwDevice->FtwLastWriteRecord->SpareComplete == FTW_VALID_STATE) ) { if (FtwDevice->FtwLastWriteRecord->BootBlockUpdate == FTW_VALID_STATE) { Status = FlushSpareBlockToBootBlock (FtwDevice); DEBUG ((EFI_D_ERROR, "Ftw: Restart boot block update - %r\n", Status)); ASSERT_EFI_ERROR (Status); FtwAbort (&FtwDevice->FtwInstance); } else { // // if (SpareCompleted) THEN Restart to fault tolerant write. // FvbHandle = NULL; FvbHandle = GetFvbByAddress ((EFI_PHYSICAL_ADDRESS) (UINTN) ((INT64) FtwDevice->SpareAreaAddress + FtwDevice->FtwLastWriteRecord->RelativeOffset), &Fvb); if (FvbHandle != NULL) { Status = FtwRestart (&FtwDevice->FtwInstance, FvbHandle); DEBUG ((EFI_D_ERROR, "FtwLite: Restart last write - %r\n", Status)); ASSERT_EFI_ERROR (Status); } FtwAbort (&FtwDevice->FtwInstance); } } // // Hook the protocol API // FtwDevice->FtwInstance.GetMaxBlockSize = FtwGetMaxBlockSize; FtwDevice->FtwInstance.Allocate = FtwAllocate; FtwDevice->FtwInstance.Write = FtwWrite; FtwDevice->FtwInstance.Restart = FtwRestart; FtwDevice->FtwInstance.Abort = FtwAbort; FtwDevice->FtwInstance.GetLastWrite = FtwGetLastWrite; return EFI_SUCCESS; }