diff --git a/MdeModulePkg/Include/Protocol/FaultTolerantWrite.h b/MdeModulePkg/Include/Protocol/FaultTolerantWrite.h new file mode 100644 index 0000000000..d673627249 --- /dev/null +++ b/MdeModulePkg/Include/Protocol/FaultTolerantWrite.h @@ -0,0 +1,207 @@ +/** @file +Fault Tolerant Write protocol provides boot-time service to do fault tolerant +write capability for block devices. The protocol provides for non-volatile +intermediate storage of the data and private information a caller would need to +recover from a critical fault, such as power failure. + +Copyright (c) 2009, Intel Corporation +All rights reserved. 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. + +**/ + +#ifndef _FW_FAULT_TOLERANT_WRITE_PROTOCOL_H_ +#define _FW_FAULT_TOLERANT_WRITE_PROTOCOL_H_ + +#define EFI_FAULT_TOLERANT_WRITE_PROTOCOL_GUID \ + { \ + 0x3ebd9e82, 0x2c78, 0x4de6, {0x97, 0x86, 0x8d, 0x4b, 0xfc, 0xb7, 0xc8, 0x81 } \ + } + +// +// Forward reference for pure ANSI compatability +// +typedef struct _EFI_FAULT_TOLERANT_WRITE_PROTOCOL EFI_FAULT_TOLERANT_WRITE_PROTOCOL; + +/** + Query the largest block that may be updated in a fault tolerant manner. + + @param This Indicates a pointer to the calling context. + @param BlockSize A pointer to a caller allocated UINTN that is + updated to indicate the size of the largest block + that can be updated. + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_FAULT_TOLERANT_WRITE_GET_MAX_BLOCK_SIZE) ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL * This, + OUT UINTN *BlockSize + ); + +/** + Allocates space for the protocol to maintain information about writes. + Since writes must be completed in a fault tolerant manner and multiple + updates will require more resources to be successful, this function + enables the protocol to ensure that enough space exists to track + information about the upcoming writes. + + @param This Indicates a pointer to the calling context. + @param CallerId The GUID identifying the write. + @param PrivateDataSize The size of the caller's private data that must be + recorded for each write. + @param NumberOfWrites The number of fault tolerant block writes that will + need to occur. + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully. + @retval EFI_ACCESS_DENIED All allocated writes have not been completed. All + writes must be completed or aborted before another + fault tolerant write can occur. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_FAULT_TOLERANT_WRITE_ALLOCATE) ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL * This, + IN EFI_GUID * CallerId, + IN UINTN PrivateDataSize, + IN UINTN NumberOfWrites + ); + +/** + Starts a target block update. This records information about the write + in fault tolerant storage and will complete the write in a recoverable + manner, ensuring at all times that either the original contents or + the modified contents are available. + + @param This Calling context + @param Lba The logical block address of the target block. + @param Offset The offset within the target block to place the + data. + @param Length The number of bytes to write to the target block. + @param PrivateData A pointer to private data that the caller requires + to complete any pending writes in the event of a + fault. + @param FvBlockHandle The handle of FVB protocol that provides services + for reading, writing, and erasing the target block. + @param Buffer The data to write. + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully. + @retval EFI_BAD_BUFFER_SIZE The write would span a block boundary, which is not + a valid action. + @retval EFI_ACCESS_DENIED No writes have been allocated. + @retval EFI_NOT_READY The last write has not been completed. Restart () + must be called to complete it. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_FAULT_TOLERANT_WRITE_WRITE) ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL * This, + IN EFI_LBA Lba, + IN UINTN Offset, + IN UINTN Length, + IN VOID *PrivateData, + IN EFI_HANDLE FvbHandle, + IN VOID *Buffer + ); + +/** + Restarts a previously interrupted write. The caller must provide the + block protocol needed to complete the interrupted write. + + @param This Calling context. + @param FvBlockProtocol The handle of FVB protocol that provides services + for reading, writing, and erasing the target block. + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully. + @retval EFI_ACCESS_DENIED No pending writes exist. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_FAULT_TOLERANT_WRITE_RESTART) ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL * This, + IN EFI_HANDLE FvbHandle + ); + +/** + Aborts all previous allocated writes. + + @param This Calling context + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully. + @retval EFI_NOT_FOUND No allocated writes exist. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_FAULT_TOLERANT_WRITE_ABORT) ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL * This + ); + +/** + Starts a target block update. This records information about the write + in fault tolerant storage and will complete the write in a recoverable + manner, ensuring at all times that either the original contents or + the modified contents are available. + + @param This Indicates a pointer to the calling context. + @param CallerId The GUID identifying the last write. + @param Lba The logical block address of the last write. + @param Offset The offset within the block of the last write. + @param Length The length of the last write. + @param PrivateDataSize On input, the size of the PrivateData buffer. On + output, the size of the private data stored for + this write. + @param PrivateData A pointer to a buffer. The function will copy + PrivateDataSize bytes from the private data stored + for this write. + @param Complete A Boolean value with TRUE indicating that the write + was completed. + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully. + @retval EFI_NOT_FOUND No allocated writes exist. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_FAULT_TOLERANT_WRITE_GET_LAST_WRITE) ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL * This, + OUT EFI_GUID * CallerId, + OUT EFI_LBA *Lba, + OUT UINTN *Offset, + OUT UINTN *Length, + IN OUT UINTN *PrivateDataSize, + OUT VOID *PrivateData, + OUT BOOLEAN *Complete + ); + +// +// Protocol declaration +// +struct _EFI_FAULT_TOLERANT_WRITE_PROTOCOL { + EFI_FAULT_TOLERANT_WRITE_GET_MAX_BLOCK_SIZE GetMaxBlockSize; + EFI_FAULT_TOLERANT_WRITE_ALLOCATE Allocate; + EFI_FAULT_TOLERANT_WRITE_WRITE Write; + EFI_FAULT_TOLERANT_WRITE_RESTART Restart; + EFI_FAULT_TOLERANT_WRITE_ABORT Abort; + EFI_FAULT_TOLERANT_WRITE_GET_LAST_WRITE GetLastWrite; +}; + +extern EFI_GUID gEfiFaultTolerantWriteProtocolGuid; + +#endif diff --git a/MdeModulePkg/Include/Protocol/SwapAddressRange.h b/MdeModulePkg/Include/Protocol/SwapAddressRange.h new file mode 100644 index 0000000000..0aff5835a4 --- /dev/null +++ b/MdeModulePkg/Include/Protocol/SwapAddressRange.h @@ -0,0 +1,182 @@ +/** @file +The EFI_SWAP_ADDRESS_RANGE_PROTOCOL is used to abstract the swap operation of boot block +and backup block of FV. This swap is especially needed when updating the boot block of FV. If any +power failure happens during updating boot block, the swapped backup block (now is the boot block) +can boot the machine with old boot block backuped in it. The swap operation is platform dependent, so +other protocols such as FTW (Fault Tolerant Write) should use this protocol instead of handling hardward directly. + +Copyright (c) 2009, Intel Corporation +All rights reserved. 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. + +**/ + +#ifndef _EFI_SWAP_ADDRESS_RANGE_PROTOCOL_H_ +#define _EFI_SWAP_ADDRESS_RANGE_PROTOCOL_H_ + +#define EFI_SWAP_ADDRESS_RANGE_PROTOCOL_GUID \ + { \ + 0x1259f60d, 0xb754, 0x468e, {0xa7, 0x89, 0x4d, 0xb8, 0x5d, 0x55, 0xe8, 0x7e } \ + } + +// +// Forward reference for pure ANSI compatability +// +typedef struct _EFI_SWAP_ADDRESS_RANGE_PROTOCOL EFI_SWAP_ADDRESS_RANGE_PROTOCOL; + +#define EFI_UNSUPPORT_LOCK 0 +#define EFI_SOFTWARE_LOCK 1 +#define EFI_HARDWARE_LOCK 2 + +typedef UINT8 EFI_SWAP_LOCK_CAPABILITY; + +// +// Protocl APIs +// + +/** + This service gets the address range location of boot block and backup block. + The EFI_GET_RANGE_LOCATION service allows caller to get the range location of + boot block and backup block. + + @param This Indicates the calling context. + @param BootBlockBase Base address of current boot block. + @param BootBlockSize Size (in bytes) of current boot block. + @param BackupBlockBase Base address of current backup block. + @param BackupBlockSize Size (in bytes) of current backup block. + + @retval EFI_SUCCESS The call was successful. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_GET_RANGE_LOCATION) ( + IN EFI_SWAP_ADDRESS_RANGE_PROTOCOL * This, + OUT EFI_PHYSICAL_ADDRESS * BootBlockBase, + OUT UINTN *BootBlockSize, + OUT EFI_PHYSICAL_ADDRESS * BackupBlockBase, + OUT UINTN *BackupBlockSize + ); + +/** + This service checks if the boot block and backup block has been swapped. + + The EFI_GET_SWAP_STATE service allows caller to get current swap state of boot block and backup block. + + @param This Indicates the calling context. + @param SwapState True if the boot block and backup block has been swapped. + False if the boot block and backup block has not been swapped. + + @retval EFI_SUCCESS The call was successful. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_GET_SWAP_STATE) ( + IN EFI_SWAP_ADDRESS_RANGE_PROTOCOL * This, + OUT BOOLEAN *SwapState + ); + +/** + This service swaps the boot block and backup block, or swaps them back. + + The EFI_SET_SWAP_STATE service allows caller to set the swap state of boot block and backup block. + It also acquires and releases software swap lock during operation. Note the setting of new swap state + is not affected by the old swap state. + + @param This Indicates the calling context. + @param NewSwapState True to swap real boot block and backup block , False to swap them back.. + + @retval EFI_SUCCESS The call was successful. + @retval EFI_ABORTED Set swap state error + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_SET_SWAP_STATE) ( + IN EFI_SWAP_ADDRESS_RANGE_PROTOCOL * This, + IN BOOLEAN NewSwapState + ); + + + +/** + This service checks if a RTC (Real Time Clock) power failure happened. + + The EFI_GET_RTC_POWER_STATUS service allows caller to get Real Time Clock power failure status. + If parameter RtcPowerFailed is true after function returns, the trickle current (from the main battery or trickle supply) + has been removed or failed, this means the swap status was lost in some platform (such as IA32). + So it is recommended to check RTC power status before calling GetSwapState(). + + @param This Indicates the calling context. + @param RtcPowerFailed True if a RTC (Real Time Clock) power failure has happened. + + @retval EFI_SUCCESS The call was successful. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_GET_RTC_POWER_STATUS) ( + IN EFI_SWAP_ADDRESS_RANGE_PROTOCOL * This, + OUT BOOLEAN *RtcPowerFailed + ); + +/** + This service returns supported lock methods for swap operation in current platform. Could be software lock, hardware lock, or unsupport lock. + + The EFI_GET_SWAP_LOCK_CAPABILITY service allows caller to get supported lock method for swap operation in current platform. + Note that software and hardware lock mothod can be used simultaneously. + + @param This Indicates the calling context. + @param LockCapability Current lock method for swap operation. + + @retval EFI_SUCCESS The call was successful. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_GET_SWAP_LOCK_CAPABILITY) ( + IN EFI_SWAP_ADDRESS_RANGE_PROTOCOL * This, + OUT EFI_SWAP_LOCK_CAPABILITY * LockCapability + ); + + + +/** + This service is used to acquire or release appointed kind of lock for Swap Address Range operation. + + The EFI_GET_SWAP_LOCK_CAPABILITY service allows caller to get supported lock method for swap operation in current platform. + Note that software and hardware lock mothod can be used simultaneously. + + @param This Indicates the calling context. + @param LockCapability Indicates which lock to acquire or release. + @param NewLockState True to acquire lock, False to release lock. + + @retval EFI_SUCCESS The call was successful. + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_SET_SWAP_LOCK) ( + IN EFI_SWAP_ADDRESS_RANGE_PROTOCOL * This, + IN EFI_SWAP_LOCK_CAPABILITY LockCapability, + IN BOOLEAN NewLockState + ); + +struct _EFI_SWAP_ADDRESS_RANGE_PROTOCOL { + EFI_GET_RANGE_LOCATION GetRangeLocation; // has output parameters for base and length + EFI_GET_SWAP_STATE GetSwapState; // are ranges swapped or not + EFI_SET_SWAP_STATE SetSwapState; // swap or unswap ranges + EFI_GET_RTC_POWER_STATUS GetRtcPowerStatus; // checks RTC battery, or whatever... + EFI_GET_SWAP_LOCK_CAPABILITY GetSwapLockCapability; // Get TOP_SWAP lock capability, + EFI_SET_SWAP_LOCK SetSwapLock; // Set TOP_SWAP lock state +}; + +extern EFI_GUID gEfiSwapAddressRangeProtocolGuid; + +#endif diff --git a/MdeModulePkg/MdeModulePkg.dec b/MdeModulePkg/MdeModulePkg.dec index 32bab80629..c8ecd0e356 100644 --- a/MdeModulePkg/MdeModulePkg.dec +++ b/MdeModulePkg/MdeModulePkg.dec @@ -208,6 +208,14 @@ ## Tcg addtional services to measure PeImage and ActionString. ## Include/Protocol/TcgPlatform.h gEfiTcgPlatformProtocolGuid = { 0x8c4c9a41, 0xbf56, 0x4627, { 0x9e, 0xa, 0xc8, 0x38, 0x6d, 0x66, 0x11, 0x5c }} + + ## Fault Tolerant Write protocol provides boot-time service to do fault tolerant write capability for block devices. + # Include/Protocol/FaultTolerantWrite.h + gEfiFaultTolerantWriteProtocolGuid = { 0x3EBD9E82, 0x2C78, 0x4DE6, { 0x97, 0x86, 0x8D, 0x4B, 0xFC, 0xB7, 0xC8, 0x81 }} + + ## This protocol is used to abstract the swap operation of boot block and backup block of boot FV. + # Include/Protocol/SwapAddressRange.h + gEfiSwapAddressRangeProtocolGuid = { 0x1259F60D, 0xB754, 0x468E, { 0xA7, 0x89, 0x4D, 0xB8, 0x5D, 0x55, 0xE8, 0x7E }} [PcdsFeatureFlag] ## Indicate whether platform can support update capsule across a system reset @@ -283,6 +291,11 @@ ## gEfiMdeModulePkgTokenSpaceGuid.PcdHelloWorldPrintEnable|TRUE|BOOLEAN|0x0001200a + ## + # If TRUE, FULL FTW protocol services (total six APIs) will be produced. + # If FASLE, only FTW Write service is available. + gEfiMdeModulePkgTokenSpaceGuid.PcdFullFtwServiceEnable|TRUE|BOOLEAN|0x0001200a + [PcdsFixedAtBuild] ## Dynamic type PCD can be registered callback function for Pcd setting action. # PcdMaxPeiPcdCallBackNumberPerPcdEntry indicate maximum number of callback function diff --git a/MdeModulePkg/MdeModulePkg.dsc b/MdeModulePkg/MdeModulePkg.dsc index eecc51095b..affd59ac24 100644 --- a/MdeModulePkg/MdeModulePkg.dsc +++ b/MdeModulePkg/MdeModulePkg.dsc @@ -331,7 +331,7 @@ MdeModulePkg/Application/VariableInfo/VariableInfo.inf MdeModulePkg/Universal/Variable/Pei/VariablePei.inf MdeModulePkg/Universal/WatchdogTimerDxe/WatchdogTimer.inf - + MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWriteDxe.inf [Components.IA32, Components.X64] MdeModulePkg/Universal/DebugSupportDxe/DebugSupportDxe.inf diff --git a/MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWrite.c b/MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWrite.c new file mode 100644 index 0000000000..d31eaaa66a --- /dev/null +++ b/MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWrite.c @@ -0,0 +1,1237 @@ +/** @file + + This is a simple fault tolerant write driver. + + This boot service protocol only provides fault tolerant write capability for + block devices. The protocol has internal non-volatile intermediate storage + of the data and private information. It should be able to recover + automatically from a critical fault, such as power failure. + + The implementation uses an FTW (Fault Tolerant Write) Work Space. + This work space is a memory copy of the work space on the Working Block, + the size of the work space is the FTW_WORK_SPACE_SIZE bytes. + + The work space stores each write record as EFI_FTW_RECORD structure. + The spare block stores the write buffer before write to the target block. + + The write record has three states to specify the different phase of write operation. + 1) WRITE_ALLOCATED is that the record is allocated in write space. + The information of write operation is stored in write record structure. + 2) SPARE_COMPLETED is that the data from write buffer is writed into the spare block as the backup. + 3) WRITE_COMPLETED is that the data is copied from the spare block to the target block. + + This driver operates the data as the whole size of spare block. + It first read the SpareAreaLength data from the target block into the spare memory buffer. + Then copy the write buffer data into the spare memory buffer. + Then write the spare memory buffer into the spare block. + Final copy the data from the spare block to the target block. + + To make this drive work well, the following conditions must be satisfied: + 1. The write NumBytes data must be fit within Spare area. + Offset + NumBytes <= SpareAreaLength + 2. The whole flash range has the same block size. + 3. Working block is an area which contains working space in its last block and has the same size as spare block. + 4. Working Block area must be in the single one Firmware Volume Block range which FVB protocol is produced on. + 5. Spare area must be in the single one Firmware Volume Block range which FVB protocol is produced on. + 6. Any write data area (SpareAreaLength Area) which the data will be written into must be + in the single one Firmware Volume Block range which FVB protocol is produced on. + 7. If write data area (such as Variable range) is enlarged, the spare area range must be enlarged. + The spare area must be enough large to store the write data before write them into the target range. + If one of them is not satisfied, FtwWrite may fail. + Usually, Spare area only takes one block. That's SpareAreaLength = BlockSize, NumberOfSpareBlock = 1. + +Copyright (c) 2006 - 2009, Intel Corporation +All rights reserved. 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" + + +// +// Fault Tolerant Write Protocol API +// +/** + Query the largest block that may be updated in a fault tolerant manner. + + + @param This The pointer to this protocol instance. + @param BlockSize A pointer to a caller allocated UINTN that is updated to + indicate the size of the largest block that can be updated. + + @return EFI_SUCCESS The function completed successfully + +**/ +EFI_STATUS +EFIAPI +FtwGetMaxBlockSize ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *This, + OUT UINTN *BlockSize + ) +{ + EFI_FTW_DEVICE *FtwDevice; + + if (!FeaturePcdGet(PcdFullFtwServiceEnable)) { + return EFI_UNSUPPORTED; + } + + FtwDevice = FTW_CONTEXT_FROM_THIS (This); + + *BlockSize = FtwDevice->SpareAreaLength; + + return EFI_SUCCESS; +} + +/** + Allocates space for the protocol to maintain information about writes. + Since writes must be completed in a fault tolerant manner and multiple + updates will require more resources to be successful, this function + enables the protocol to ensure that enough space exists to track + information about the upcoming writes. + + All writes must be completed or aborted before another fault tolerant write can occur. + + @param This The pointer to this protocol instance. + @param CallerId The GUID identifying the write. + @param PrivateDataSize The size of the caller's private data + that must be recorded for each write. + @param NumberOfWrites The number of fault tolerant block writes + that will need to occur. + + @return EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully. + @retval EFI_ACCESS_DENIED All allocated writes have not been completed. + +**/ +EFI_STATUS +EFIAPI +FtwAllocate ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *This, + IN EFI_GUID *CallerId, + IN UINTN PrivateDataSize, + IN UINTN NumberOfWrites + ) +{ + EFI_STATUS Status; + UINTN Length; + UINTN Offset; + EFI_FTW_DEVICE *FtwDevice; + EFI_FAULT_TOLERANT_WRITE_HEADER *FtwHeader; + + FtwDevice = FTW_CONTEXT_FROM_THIS (This); + + Status = WorkSpaceRefresh (FtwDevice); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + // + // Check if there is enough space for the coming allocation + // + if (WRITE_TOTAL_SIZE (NumberOfWrites, PrivateDataSize) > FtwDevice->FtwWorkSpaceHeader->WriteQueueSize) { + DEBUG ((EFI_D_ERROR, "Ftw: Allocate() request exceed Workspace, Caller: %g\n", CallerId)); + return EFI_BUFFER_TOO_SMALL; + } + // + // Find the last write header and record. + // If the FtwHeader is complete, skip the completed last write header/records + // + FtwHeader = FtwDevice->FtwLastWriteHeader; + + // + // Previous write has not completed, access denied. + // + if ((FtwHeader->HeaderAllocated == FTW_VALID_STATE) || (FtwHeader->WritesAllocated == FTW_VALID_STATE)) { + return EFI_ACCESS_DENIED; + } + // + // If workspace is not enough, then reclaim workspace + // + Offset = (UINT8 *) FtwHeader - (UINT8 *) FtwDevice->FtwWorkSpace; + if (Offset + WRITE_TOTAL_SIZE (NumberOfWrites, PrivateDataSize) > FtwDevice->FtwWorkSpaceSize) { + Status = FtwReclaimWorkSpace (FtwDevice, TRUE); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + + FtwHeader = FtwDevice->FtwLastWriteHeader; + } + // + // Prepare FTW write header, + // overwrite the buffer and write to workspace. + // + FtwHeader->WritesAllocated = FTW_INVALID_STATE; + FtwHeader->Complete = FTW_INVALID_STATE; + CopyMem (&FtwHeader->CallerId, CallerId, sizeof (EFI_GUID)); + FtwHeader->NumberOfWrites = NumberOfWrites; + FtwHeader->PrivateDataSize = PrivateDataSize; + FtwHeader->HeaderAllocated = FTW_VALID_STATE; + + Length = sizeof (EFI_FAULT_TOLERANT_WRITE_HEADER); + Status = FtwDevice->FtwFvBlock->Write ( + FtwDevice->FtwFvBlock, + FtwDevice->FtwWorkSpaceLba, + FtwDevice->FtwWorkSpaceBase + Offset, + &Length, + (UINT8 *) FtwHeader + ); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + // + // Update Header->WriteAllocated as VALID + // + Status = FtwUpdateFvState ( + FtwDevice->FtwFvBlock, + FtwDevice->FtwWorkSpaceLba, + FtwDevice->FtwWorkSpaceBase + Offset, + WRITES_ALLOCATED + ); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + + DEBUG ( + (EFI_D_ERROR, + "Ftw: Allocate() success, Caller:%g, # %d\n", + CallerId, + NumberOfWrites) + ); + + return EFI_SUCCESS; +} + + +/** + Write a record with fault tolerant mannaer. + Since the content has already backuped in spare block, the write is + guaranteed to be completed with fault tolerant manner. + + @param This The pointer to this protocol instance. + @param Fvb The FVB protocol that provides services for + reading, writing, and erasing the target block. + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully + +**/ +EFI_STATUS +FtwWriteRecord ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *This, + IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *Fvb + ) +{ + EFI_STATUS Status; + EFI_FTW_DEVICE *FtwDevice; + EFI_FAULT_TOLERANT_WRITE_HEADER *Header; + EFI_FAULT_TOLERANT_WRITE_RECORD *Record; + UINTN Offset; + + FtwDevice = FTW_CONTEXT_FROM_THIS (This); + + // + // Spare Complete but Destination not complete, + // Recover the targt block with the spare block. + // + Header = FtwDevice->FtwLastWriteHeader; + Record = FtwDevice->FtwLastWriteRecord; + + // + // IF target block is working block, THEN Flush Spare Block To Working Block; + // ELSE flush spare block to target block, which may be boot block after all. + // + if (IsWorkingBlock (FtwDevice, Fvb, Record->Lba)) { + // + // If target block is working block, + // it also need to set SPARE_COMPLETED to spare block. + // + Offset = (UINT8 *) Record - FtwDevice->FtwWorkSpace; + Status = FtwUpdateFvState ( + FtwDevice->FtwBackupFvb, + FtwDevice->FtwWorkSpaceLba, + FtwDevice->FtwWorkSpaceBase + Offset, + SPARE_COMPLETED + ); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + + Status = FlushSpareBlockToWorkingBlock (FtwDevice); + } else if (IsBootBlock (FtwDevice, Fvb, Record->Lba)) { + // + // Update boot block + // + Status = FlushSpareBlockToBootBlock (FtwDevice); + } else { + // + // Update blocks other than working block or boot block + // + Status = FlushSpareBlockToTargetBlock (FtwDevice, Fvb, Record->Lba); + } + + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + // + // Record the DestionationComplete in record + // + Offset = (UINT8 *) Record - FtwDevice->FtwWorkSpace; + Status = FtwUpdateFvState ( + FtwDevice->FtwFvBlock, + FtwDevice->FtwWorkSpaceLba, + FtwDevice->FtwWorkSpaceBase + Offset, + DEST_COMPLETED + ); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + + Record->DestinationComplete = FTW_VALID_STATE; + + // + // If this is the last Write in these write sequence, + // set the complete flag of write header. + // + if (IsLastRecordOfWrites (Header, Record)) { + Offset = (UINT8 *) Header - FtwDevice->FtwWorkSpace; + Status = FtwUpdateFvState ( + FtwDevice->FtwFvBlock, + FtwDevice->FtwWorkSpaceLba, + FtwDevice->FtwWorkSpaceBase + Offset, + WRITES_COMPLETED + ); + Header->Complete = FTW_VALID_STATE; + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + } + + return EFI_SUCCESS; +} + +/** + Starts a target block update. This function will record data about write + in fault tolerant storage and will complete the write in a recoverable + manner, ensuring at all times that either the original contents or + the modified contents are available. + + @param This The pointer to this protocol instance. + @param Lba The logical block address of the target block. + @param Offset The offset within the target block to place the data. + @param Length The number of bytes to write to the target block. + @param PrivateData A pointer to private data that the caller requires to + complete any pending writes in the event of a fault. + @param FvBlockHandle The handle of FVB protocol that provides services for + reading, writing, and erasing the target block. + @param Buffer The data to write. + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully. + @retval EFI_BAD_BUFFER_SIZE The input data can't fit within the spare block. + Offset + *NumBytes > SpareAreaLength. + @retval EFI_ACCESS_DENIED No writes have been allocated. + @retval EFI_OUT_OF_RESOURCES Cannot allocate enough memory resource. + @retval EFI_NOT_FOUND Cannot find FVB protocol by handle. + +**/ +EFI_STATUS +EFIAPI +FtwWrite ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *This, + IN EFI_LBA Lba, + IN UINTN Offset, + IN UINTN Length, + IN VOID *PrivateData, + IN EFI_HANDLE FvBlockHandle, + IN VOID *Buffer + ) +{ + EFI_STATUS Status; + EFI_FTW_DEVICE *FtwDevice; + EFI_FAULT_TOLERANT_WRITE_HEADER *Header; + EFI_FAULT_TOLERANT_WRITE_RECORD *Record; + EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *Fvb; + UINTN MyLength; + UINTN MyOffset; + UINTN MyBufferSize; + UINT8 *MyBuffer; + UINTN SpareBufferSize; + UINT8 *SpareBuffer; + UINTN Index; + UINT8 *Ptr; + EFI_PHYSICAL_ADDRESS FvbPhysicalAddress; + + FtwDevice = FTW_CONTEXT_FROM_THIS (This); + + Status = WorkSpaceRefresh (FtwDevice); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + + Header = FtwDevice->FtwLastWriteHeader; + Record = FtwDevice->FtwLastWriteRecord; + + if (IsErasedFlashBuffer ((UINT8 *) Header, sizeof (EFI_FAULT_TOLERANT_WRITE_HEADER))) { + if (PrivateData == NULL) { + // + // Ftw Write Header is not allocated. + // No additional private data, the private data size is zero. Number of record can be set to 1. + // + Status = FtwAllocate (This, &gEfiCallerIdGuid, 0, 1); + if (EFI_ERROR (Status)) { + return Status; + } + } else { + // + // Ftw Write Header is not allocated + // Additional private data is not NULL, the private data size can't be determined. + // + DEBUG ((EFI_D_ERROR, "Ftw: no allocates space for write record!\n")); + DEBUG ((EFI_D_ERROR, "Ftw: Allocate service should be called before Write service!\n")); + return EFI_NOT_READY; + } + } + + // + // If Record is out of the range of Header, return access denied. + // + if (((UINTN)((UINT8 *) Record - (UINT8 *) Header)) > WRITE_TOTAL_SIZE (Header->NumberOfWrites - 1, Header->PrivateDataSize)) { + return EFI_ACCESS_DENIED; + } + + // + // Check the COMPLETE flag of last write header + // + if (Header->Complete == FTW_VALID_STATE) { + return EFI_ACCESS_DENIED; + } + + if (Record->DestinationComplete == FTW_VALID_STATE) { + return EFI_ACCESS_DENIED; + } + + if ((Record->SpareComplete == FTW_VALID_STATE) && (Record->DestinationComplete != FTW_VALID_STATE)) { + return EFI_NOT_READY; + } + // + // Check if the input data can fit within the target block + // + if ((Offset + Length) > FtwDevice->SpareAreaLength) { + return EFI_BAD_BUFFER_SIZE; + } + // + // Get the FVB protocol by handle + // + Status = FtwGetFvbByHandle (FvBlockHandle, &Fvb); + if (EFI_ERROR (Status)) { + return EFI_NOT_FOUND; + } + + Status = Fvb->GetPhysicalAddress (Fvb, &FvbPhysicalAddress); + if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_ERROR, "FtwLite: Get FVB physical address - %r\n", Status)); + return EFI_ABORTED; + } + + // + // Set BootBlockUpdate FLAG if it's updating boot block. + // + if (IsBootBlock (FtwDevice, Fvb, Lba)) { + Record->BootBlockUpdate = FTW_VALID_STATE; + } + // + // Write the record to the work space. + // + Record->Lba = Lba; + Record->Offset = Offset; + Record->Length = Length; + Record->FvBaseAddress = FvbPhysicalAddress; + CopyMem ((Record + 1), PrivateData, Header->PrivateDataSize); + + MyOffset = (UINT8 *) Record - FtwDevice->FtwWorkSpace; + MyLength = RECORD_SIZE (Header->PrivateDataSize); + + Status = FtwDevice->FtwFvBlock->Write ( + FtwDevice->FtwFvBlock, + FtwDevice->FtwWorkSpaceLba, + FtwDevice->FtwWorkSpaceBase + MyOffset, + &MyLength, + (UINT8 *) Record + ); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + // + // Record has written to working block, then do the data. + // + // + // Allocate a memory buffer + // + MyBufferSize = FtwDevice->SpareAreaLength; + MyBuffer = AllocatePool (MyBufferSize); + if (MyBuffer == NULL) { + return EFI_OUT_OF_RESOURCES; + } + // + // Read all original data from target block to memory buffer + // + Ptr = MyBuffer; + for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) { + MyLength = FtwDevice->BlockSize; + Status = Fvb->Read (Fvb, Lba + Index, 0, &MyLength, Ptr); + if (EFI_ERROR (Status)) { + FreePool (MyBuffer); + return EFI_ABORTED; + } + + Ptr += MyLength; + } + // + // Overwrite the updating range data with + // the input buffer content + // + CopyMem (MyBuffer + Offset, Buffer, Length); + + // + // 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 (MyBuffer); + return EFI_OUT_OF_RESOURCES; + } + + Ptr = SpareBuffer; + for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) { + MyLength = FtwDevice->BlockSize; + Status = FtwDevice->FtwBackupFvb->Read ( + FtwDevice->FtwBackupFvb, + FtwDevice->FtwSpareLba + Index, + 0, + &MyLength, + Ptr + ); + if (EFI_ERROR (Status)) { + FreePool (MyBuffer); + FreePool (SpareBuffer); + return EFI_ABORTED; + } + + Ptr += MyLength; + } + // + // Write the memory buffer to spare block + // + Status = FtwEraseSpareBlock (FtwDevice); + Ptr = MyBuffer; + for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) { + MyLength = FtwDevice->BlockSize; + Status = FtwDevice->FtwBackupFvb->Write ( + FtwDevice->FtwBackupFvb, + FtwDevice->FtwSpareLba + Index, + 0, + &MyLength, + Ptr + ); + if (EFI_ERROR (Status)) { + FreePool (MyBuffer); + FreePool (SpareBuffer); + return EFI_ABORTED; + } + + Ptr += MyLength; + } + // + // Free MyBuffer + // + FreePool (MyBuffer); + + // + // Set the SpareComplete in the FTW record, + // + MyOffset = (UINT8 *) Record - FtwDevice->FtwWorkSpace; + Status = FtwUpdateFvState ( + FtwDevice->FtwFvBlock, + FtwDevice->FtwWorkSpaceLba, + FtwDevice->FtwWorkSpaceBase + MyOffset, + SPARE_COMPLETED + ); + if (EFI_ERROR (Status)) { + FreePool (SpareBuffer); + return EFI_ABORTED; + } + + Record->SpareComplete = FTW_VALID_STATE; + + // + // Since the content has already backuped in spare block, the write is + // guaranteed to be completed with fault tolerant manner. + // + Status = FtwWriteRecord (This, Fvb); + if (EFI_ERROR (Status)) { + FreePool (SpareBuffer); + return EFI_ABORTED; + } + // + // Restore spare backup buffer into spare block , if no failure happened during FtwWrite. + // + Status = FtwEraseSpareBlock (FtwDevice); + Ptr = SpareBuffer; + for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) { + MyLength = FtwDevice->BlockSize; + Status = FtwDevice->FtwBackupFvb->Write ( + FtwDevice->FtwBackupFvb, + FtwDevice->FtwSpareLba + Index, + 0, + &MyLength, + Ptr + ); + if (EFI_ERROR (Status)) { + FreePool (SpareBuffer); + return EFI_ABORTED; + } + + Ptr += MyLength; + } + // + // All success. + // + FreePool (SpareBuffer); + + DEBUG ( + (EFI_D_ERROR, + "Ftw: Write() success, (Lba:Offset)=(%lx:0x%x), Length: 0x%x\n", + Lba, + Offset, + Length) + ); + + return EFI_SUCCESS; +} + +/** + Restarts a previously interrupted write. The caller must provide the + block protocol needed to complete the interrupted write. + + @param This The pointer to this protocol instance. + @param FvBlockHandle The handle of FVB protocol that provides services for + reading, writing, and erasing the target block. + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ACCESS_DENIED No pending writes exist + @retval EFI_NOT_FOUND FVB protocol not found by the handle + @retval EFI_ABORTED The function could not complete successfully + +**/ +EFI_STATUS +EFIAPI +FtwRestart ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *This, + IN EFI_HANDLE FvBlockHandle + ) +{ + EFI_STATUS Status; + EFI_FTW_DEVICE *FtwDevice; + EFI_FAULT_TOLERANT_WRITE_HEADER *Header; + EFI_FAULT_TOLERANT_WRITE_RECORD *Record; + EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *Fvb; + + FtwDevice = FTW_CONTEXT_FROM_THIS (This); + + Status = WorkSpaceRefresh (FtwDevice); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + + Header = FtwDevice->FtwLastWriteHeader; + Record = FtwDevice->FtwLastWriteRecord; + + // + // Spare Complete but Destination not complete, + // Recover the targt block with the spare block. + // + Status = FtwGetFvbByHandle (FvBlockHandle, &Fvb); + if (EFI_ERROR (Status)) { + return EFI_NOT_FOUND; + } + + // + // Check the COMPLETE flag of last write header + // + if (Header->Complete == FTW_VALID_STATE) { + return EFI_ACCESS_DENIED; + } + + // + // Check the flags of last write record + // + if (Record->DestinationComplete == FTW_VALID_STATE) { + return EFI_ACCESS_DENIED; + } + + if ((Record->SpareComplete != FTW_VALID_STATE)) { + return EFI_ABORTED; + } + + // + // Since the content has already backuped in spare block, the write is + // guaranteed to be completed with fault tolerant manner. + // + Status = FtwWriteRecord (This, Fvb); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + + // + // Erase Spare block + // This is restart, no need to keep spareblock content. + // + FtwEraseSpareBlock (FtwDevice); + + DEBUG ((EFI_D_ERROR, "Ftw: Restart() success \n")); + return EFI_SUCCESS; +} + +/** + Aborts all previous allocated writes. + + @param This The pointer to this protocol instance. + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully. + @retval EFI_NOT_FOUND No allocated writes exist. + +**/ +EFI_STATUS +EFIAPI +FtwAbort ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *This + ) +{ + EFI_STATUS Status; + UINTN Offset; + EFI_FTW_DEVICE *FtwDevice; + + FtwDevice = FTW_CONTEXT_FROM_THIS (This); + + Status = WorkSpaceRefresh (FtwDevice); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + + if (FtwDevice->FtwLastWriteHeader->Complete == FTW_VALID_STATE) { + return EFI_NOT_FOUND; + } + // + // Update the complete state of the header as VALID and abort. + // + Offset = (UINT8 *) FtwDevice->FtwLastWriteHeader - FtwDevice->FtwWorkSpace; + Status = FtwUpdateFvState ( + FtwDevice->FtwFvBlock, + FtwDevice->FtwWorkSpaceLba, + FtwDevice->FtwWorkSpaceBase + Offset, + WRITES_COMPLETED + ); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + + FtwDevice->FtwLastWriteHeader->Complete = FTW_VALID_STATE; + + DEBUG ((EFI_D_ERROR, "Ftw: Abort() success \n")); + return EFI_SUCCESS; +} + +/** + Starts a target block update. This records information about the write + in fault tolerant storage and will complete the write in a recoverable + manner, ensuring at all times that either the original contents or + the modified contents are available. + + @param This The pointer to this protocol instance. + @param CallerId The GUID identifying the last write. + @param Lba The logical block address of the last write. + @param Offset The offset within the block of the last write. + @param Length The length of the last write. + @param PrivateDataSize bytes from the private data + stored for this write. + @param PrivateData A pointer to a buffer. The function will copy + @param Complete A Boolean value with TRUE indicating + that the write was completed. + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully + @retval EFI_NOT_FOUND No allocated writes exist + @retval EFI_BUFFER_TOO_SMALL Input buffer is not larget enough + +**/ +EFI_STATUS +EFIAPI +FtwGetLastWrite ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *This, + OUT EFI_GUID *CallerId, + OUT EFI_LBA *Lba, + OUT UINTN *Offset, + OUT UINTN *Length, + IN OUT UINTN *PrivateDataSize, + OUT VOID *PrivateData, + OUT BOOLEAN *Complete + ) +{ + EFI_STATUS Status; + EFI_FTW_DEVICE *FtwDevice; + EFI_FAULT_TOLERANT_WRITE_HEADER *Header; + EFI_FAULT_TOLERANT_WRITE_RECORD *Record; + + if (!FeaturePcdGet(PcdFullFtwServiceEnable)) { + return EFI_UNSUPPORTED; + } + + FtwDevice = FTW_CONTEXT_FROM_THIS (This); + + Status = WorkSpaceRefresh (FtwDevice); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + + Header = FtwDevice->FtwLastWriteHeader; + Record = FtwDevice->FtwLastWriteRecord; + + // + // If Header is incompleted and the last record has completed, then + // call Abort() to set the Header->Complete FLAG. + // + if ((Header->Complete != FTW_VALID_STATE) && + (Record->DestinationComplete == FTW_VALID_STATE) && + IsLastRecordOfWrites (Header, Record) + ) { + + Status = FtwAbort (This); + *Complete = TRUE; + return EFI_NOT_FOUND; + } + // + // If there is no write header/record, return not found. + // + if (Header->HeaderAllocated != FTW_VALID_STATE) { + *Complete = TRUE; + return EFI_NOT_FOUND; + } + // + // If this record SpareComplete has not set, then it can not restart. + // + if (Record->SpareComplete != FTW_VALID_STATE) { + if (IsFirstRecordOfWrites (Header, Record)) { + // + // The First record cannot be restart and target is still healthy, + // so abort() is a safe solution. + // + FtwAbort (This); + + *Complete = TRUE; + return EFI_NOT_FOUND; + } else { + // + // Step back to the previous record + // + GetPreviousRecordOfWrites (Header, &Record); + } + } + // + // Fill all the requested values + // + CopyMem (CallerId, &Header->CallerId, sizeof (EFI_GUID)); + *Lba = Record->Lba; + *Offset = Record->Offset; + *Length = Record->Length; + *Complete = (BOOLEAN) (Record->DestinationComplete == FTW_VALID_STATE); + + if (*PrivateDataSize < Header->PrivateDataSize) { + *PrivateDataSize = Header->PrivateDataSize; + PrivateData = NULL; + Status = EFI_BUFFER_TOO_SMALL; + } else { + *PrivateDataSize = Header->PrivateDataSize; + CopyMem (PrivateData, Record + 1, *PrivateDataSize); + Status = EFI_SUCCESS; + } + + DEBUG ((EFI_D_ERROR, "Ftw: GetLasetWrite() success\n")); + + return Status; +} + +/** + This function is the entry point of the Fault Tolerant Write driver. + + @param ImageHandle A handle for the image that is initializing this driver + @param SystemTable A pointer to the EFI system table + + @return EFI_SUCCESS FTW has finished the initialization + @retval EFI_NOT_FOUND Locate FVB protocol error + @retval EFI_OUT_OF_RESOURCES Allocate memory error + @retval EFI_VOLUME_CORRUPTED Firmware volume is error + @retval EFI_ABORTED FTW initialization error + +**/ +EFI_STATUS +EFIAPI +InitializeFaultTolerantWrite ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *Fvb; + UINTN Index; + EFI_HANDLE *HandleBuffer; + UINTN HandleCount; + EFI_FIRMWARE_VOLUME_HEADER *FwVolHeader; + EFI_PHYSICAL_ADDRESS BaseAddress; + EFI_FTW_DEVICE *FtwDevice; + EFI_FAULT_TOLERANT_WRITE_HEADER *FtwHeader; + UINTN Length; + EFI_STATUS Status; + UINTN Offset; + EFI_FV_BLOCK_MAP_ENTRY *FvbMapEntry; + UINT32 LbaIndex; + EFI_HANDLE FvbHandle; + + // + // Allocate Private data of this driver, + // INCLUDING THE FtwWorkSpace[FTW_WORK_SPACE_SIZE]. + // + FvbHandle = NULL; + FtwDevice = NULL; + FtwDevice = AllocatePool (sizeof (EFI_FTW_DEVICE) + PcdGet32 (PcdFlashNvStorageFtwWorkingSize)); + if (FtwDevice == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + ZeroMem (FtwDevice, sizeof (EFI_FTW_DEVICE)); + FtwDevice->Signature = FTW_DEVICE_SIGNATURE; + + // + // Initialize other parameters, and set WorkSpace as FTW_ERASED_BYTE. + // + + FtwDevice->WorkSpaceAddress = (EFI_PHYSICAL_ADDRESS) PcdGet32 (PcdFlashNvStorageFtwWorkingBase); + FtwDevice->WorkSpaceLength = (UINTN) PcdGet32 (PcdFlashNvStorageFtwWorkingSize); + + FtwDevice->SpareAreaAddress = (EFI_PHYSICAL_ADDRESS) PcdGet32 (PcdFlashNvStorageFtwSpareBase); + FtwDevice->SpareAreaLength = (UINTN) PcdGet32 (PcdFlashNvStorageFtwSpareSize); + + if ((FtwDevice->WorkSpaceLength == 0) || (FtwDevice->SpareAreaLength == 0)) { + DEBUG ((EFI_D_ERROR, "Ftw: Workspace or Spare block does not exist!\n")); + FreePool (FtwDevice); + return EFI_OUT_OF_RESOURCES; + } + // + // Locate FVB protocol by handle + // + Status = gBS->LocateHandleBuffer ( + ByProtocol, + &gEfiFirmwareVolumeBlockProtocolGuid, + NULL, + &HandleCount, + &HandleBuffer + ); + if (EFI_ERROR (Status)) { + FreePool (FtwDevice); + return EFI_NOT_FOUND; + } + + if (HandleCount <= 0) { + FreePool (FtwDevice); + return EFI_NOT_FOUND; + } + + Fvb = NULL; + FtwDevice->FtwFvBlock = NULL; + FtwDevice->FtwBackupFvb = NULL; + FtwDevice->FtwWorkSpaceLba = (EFI_LBA) (-1); + FtwDevice->FtwSpareLba = (EFI_LBA) (-1); + for (Index = 0; Index < HandleCount; Index += 1) { + Status = gBS->HandleProtocol ( + HandleBuffer[Index], + &gEfiFirmwareVolumeBlockProtocolGuid, + (VOID **) &Fvb + ); + if (EFI_ERROR (Status)) { + FreePool (FtwDevice); + return Status; + } + + Status = Fvb->GetPhysicalAddress (Fvb, &BaseAddress); + if (EFI_ERROR (Status)) { + continue; + } + + FwVolHeader = (EFI_FIRMWARE_VOLUME_HEADER *) ((UINTN) BaseAddress); + + if ((FtwDevice->WorkSpaceAddress >= BaseAddress) && + ((FtwDevice->WorkSpaceAddress + FtwDevice->WorkSpaceLength) <= (BaseAddress + FwVolHeader->FvLength)) + ) { + FtwDevice->FtwFvBlock = Fvb; + // + // To get the LBA of work space + // + if ((FwVolHeader->FvLength) > (FwVolHeader->HeaderLength)) { + // + // Now, one FV has one type of BlockLength + // + FvbMapEntry = &FwVolHeader->BlockMap[0]; + for (LbaIndex = 1; LbaIndex <= FvbMapEntry->NumBlocks; LbaIndex += 1) { + if ((FtwDevice->WorkSpaceAddress >= (BaseAddress + FvbMapEntry->Length * (LbaIndex - 1))) + && (FtwDevice->WorkSpaceAddress < (BaseAddress + FvbMapEntry->Length * LbaIndex))) { + FtwDevice->FtwWorkSpaceLba = LbaIndex - 1; + // + // Get the Work space size and Base(Offset) + // + FtwDevice->FtwWorkSpaceSize = FtwDevice->WorkSpaceLength; + FtwDevice->FtwWorkSpaceBase = (UINTN) (FtwDevice->WorkSpaceAddress - (BaseAddress + FvbMapEntry->Length * (LbaIndex - 1))); + break; + } + } + } + } + + if ((FtwDevice->SpareAreaAddress >= BaseAddress) && + ((FtwDevice->SpareAreaAddress + FtwDevice->SpareAreaLength) <= (BaseAddress + FwVolHeader->FvLength)) + ) { + FtwDevice->FtwBackupFvb = Fvb; + // + // To get the LBA of spare + // + if ((FwVolHeader->FvLength) > (FwVolHeader->HeaderLength)) { + // + // Now, one FV has one type of BlockLength + // + FvbMapEntry = &FwVolHeader->BlockMap[0]; + for (LbaIndex = 1; LbaIndex <= FvbMapEntry->NumBlocks; LbaIndex += 1) { + if ((FtwDevice->SpareAreaAddress >= (BaseAddress + FvbMapEntry->Length * (LbaIndex - 1))) + && (FtwDevice->SpareAreaAddress < (BaseAddress + FvbMapEntry->Length * LbaIndex))) { + // + // Get the NumberOfSpareBlock and BlockSize + // + FtwDevice->FtwSpareLba = LbaIndex - 1; + FtwDevice->BlockSize = FvbMapEntry->Length; + FtwDevice->NumberOfSpareBlock = FtwDevice->SpareAreaLength / FtwDevice->BlockSize; + // + // Check the range of spare area to make sure that it's in FV range + // + if ((FtwDevice->FtwSpareLba + FtwDevice->NumberOfSpareBlock) > FvbMapEntry->NumBlocks) { + DEBUG ((EFI_D_ERROR, "Ftw: Spare area is out of FV range\n")); + FreePool (FtwDevice); + return EFI_ABORTED; + } + break; + } + } + } + } + } + + // + // 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; + if ((INT64) (FtwDevice->FtwWorkBlockLba) < 0) { + DEBUG ((EFI_D_ERROR, "Ftw: The spare block range is too large than the working block range!\n")); + FreePool (FtwDevice); + return EFI_ABORTED; + } + + if ((FtwDevice->FtwFvBlock == NULL) || + (FtwDevice->FtwBackupFvb == NULL) || + (FtwDevice->FtwWorkSpaceLba == (EFI_LBA) (-1)) || + (FtwDevice->FtwSpareLba == (EFI_LBA) (-1)) + ) { + DEBUG ((EFI_D_ERROR, "Ftw: Working or spare FVB not ready\n")); + FreePool (FtwDevice); + return EFI_ABORTED; + } + // + // 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; + + // + // Refresh the working space data from working block + // + Status = WorkSpaceRefresh (FtwDevice); + if (EFI_ERROR (Status)) { + goto Recovery; + } + // + // If the working block workspace is not valid, try the spare block + // + if (!IsValidWorkSpace (FtwDevice->FtwWorkSpaceHeader)) { + // + // Read from spare block + // + Length = FtwDevice->FtwWorkSpaceSize; + Status = FtwDevice->FtwBackupFvb->Read ( + FtwDevice->FtwBackupFvb, + FtwDevice->FtwSpareLba, + FtwDevice->FtwWorkSpaceBase, + &Length, + FtwDevice->FtwWorkSpace + ); + if (EFI_ERROR (Status)) { + goto Recovery; + } + // + // 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 Init() - %r\n", Status)); + FtwAbort (&FtwDevice->FtwInstance); + // + // Refresh work space. + // + Status = WorkSpaceRefresh (FtwDevice); + if (EFI_ERROR (Status)) { + goto Recovery; + } + } 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); + if (EFI_ERROR (Status)) { + goto Recovery; + } + } + } + + // + // 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 += WRITE_TOTAL_SIZE (FtwHeader->NumberOfWrites, FtwHeader->PrivateDataSize); + } + + if (!IsErasedFlashBuffer ( + FtwDevice->FtwWorkSpace + Offset, + FtwDevice->FtwWorkSpaceSize - Offset + )) { + Status = FtwReclaimWorkSpace (FtwDevice, TRUE); + if (EFI_ERROR (Status)) { + goto Recovery; + } + } + // + // 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)); + if (EFI_ERROR (Status)) { + goto Recovery; + } + + FtwAbort (&FtwDevice->FtwInstance); + } else { + // + // if (SpareCompleted) THEN Restart to fault tolerant write. + // + FvbHandle = GetFvbByAddress (FtwDevice->FtwLastWriteRecord->FvBaseAddress, &Fvb); + if (FvbHandle != NULL) { + Status = FtwRestart (&FtwDevice->FtwInstance, FvbHandle); + DEBUG ((EFI_D_ERROR, "FtwLite: Restart last write - %r\n", Status)); + if (EFI_ERROR (Status)) { + goto Recovery; + } + } + 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; + + // + // Install protocol interface + // + Status = gBS->InstallProtocolInterface ( + &FtwDevice->Handle, + &gEfiFaultTolerantWriteProtocolGuid, + EFI_NATIVE_INTERFACE, + &FtwDevice->FtwInstance + ); + if (EFI_ERROR (Status)) { + goto Recovery; + } + + return EFI_SUCCESS; + +Recovery: + + if (FtwDevice != NULL) { + FreePool (FtwDevice); + } + + DEBUG ((EFI_D_ERROR, "Ftw: Severe Error occurs, need to recovery\n")); + + return EFI_VOLUME_CORRUPTED; +} diff --git a/MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWrite.h b/MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWrite.h new file mode 100644 index 0000000000..3cc6572e55 --- /dev/null +++ b/MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWrite.h @@ -0,0 +1,672 @@ +/** @file + + The internal header file includes the common header files, defines + internal structure and functions used by FtwLite module. + +Copyright (c) 2006 - 2008, Intel Corporation +All rights reserved. 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. + +**/ + +#ifndef _EFI_FAULT_TOLERANT_WRITE_H_ +#define _EFI_FAULT_TOLERANT_WRITE_H_ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +// +// Flash erase polarity is 1 +// +#define FTW_ERASE_POLARITY 1 + +#define FTW_VALID_STATE 0 +#define FTW_INVALID_STATE 1 + +#define FTW_ERASED_BYTE ((UINT8) (255)) +#define FTW_POLARITY_REVERT ((UINT8) (255)) + +// +// EFI Fault tolerant block update write queue entry +// +typedef struct { + UINT8 HeaderAllocated : 1; + UINT8 WritesAllocated : 1; + UINT8 Complete : 1; +#define HEADER_ALLOCATED 0x1 +#define WRITES_ALLOCATED 0x2 +#define WRITES_COMPLETED 0x4 + UINT8 Reserved : 5; + EFI_GUID CallerId; + UINTN NumberOfWrites; + UINTN PrivateDataSize; +} EFI_FAULT_TOLERANT_WRITE_HEADER; + +// +// EFI Fault tolerant block update write queue record +// +typedef struct { + UINT8 BootBlockUpdate : 1; + UINT8 SpareComplete : 1; + UINT8 DestinationComplete : 1; +#define BOOT_BLOCK_UPDATE 0x1 +#define SPARE_COMPLETED 0x2 +#define DEST_COMPLETED 0x4 + UINT8 Reserved : 5; + EFI_LBA Lba; + UINTN Offset; + UINTN Length; + EFI_PHYSICAL_ADDRESS FvBaseAddress; + // + // UINT8 PrivateData[PrivateDataSize] + // +} EFI_FAULT_TOLERANT_WRITE_RECORD; + + +#define RECORD_SIZE(PrivateDataSize) (sizeof (EFI_FAULT_TOLERANT_WRITE_RECORD) + PrivateDataSize) + +#define RECORD_TOTAL_SIZE(NumberOfWrites, PrivateDataSize) \ + ((NumberOfWrites) * (sizeof (EFI_FAULT_TOLERANT_WRITE_RECORD) + PrivateDataSize)) + +#define WRITE_TOTAL_SIZE(NumberOfWrites, PrivateDataSize) \ + ( \ + sizeof (EFI_FAULT_TOLERANT_WRITE_HEADER) + (NumberOfWrites) * \ + (sizeof (EFI_FAULT_TOLERANT_WRITE_RECORD) + PrivateDataSize) \ + ) + +#define FTW_DEVICE_SIGNATURE SIGNATURE_32 ('F', 'T', 'W', 'D') + +// +// EFI Fault tolerant protocol private data structure +// +typedef struct { + UINTN Signature; + EFI_HANDLE Handle; + EFI_FAULT_TOLERANT_WRITE_PROTOCOL FtwInstance; + EFI_PHYSICAL_ADDRESS WorkSpaceAddress; // Base address of working space range in flash. + EFI_PHYSICAL_ADDRESS SpareAreaAddress; // Base address of spare range in flash. + UINTN WorkSpaceLength; // Size of working space range in flash. + UINTN SpareAreaLength; // Size of spare range in flash. + UINTN NumberOfSpareBlock; // Number of the blocks in spare block. + UINTN BlockSize; // Block size in bytes of the blocks in flash + EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *FtwWorkSpaceHeader;// Pointer to Working Space Header in memory buffer + EFI_FAULT_TOLERANT_WRITE_HEADER *FtwLastWriteHeader;// Pointer to last record header in memory buffer + EFI_FAULT_TOLERANT_WRITE_RECORD *FtwLastWriteRecord;// Pointer to last record in memory buffer + EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FtwFvBlock; // FVB of working block + EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FtwBackupFvb; // FVB of spare block + EFI_LBA FtwSpareLba; // Start LBA of spare block + EFI_LBA FtwWorkBlockLba; // Start LBA of working block that contains working space in its last block. + EFI_LBA FtwWorkSpaceLba; // Start LBA of working space + UINTN FtwWorkSpaceBase; // Offset into the FtwWorkSpaceLba block. + UINTN FtwWorkSpaceSize; // Size of working space range that stores write record. + UINT8 *FtwWorkSpace; // Point to Work Space in memory buffer + // + // Following a buffer of FtwWorkSpace[FTW_WORK_SPACE_SIZE], + // Allocated with EFI_FTW_DEVICE. + // +} EFI_FTW_DEVICE; + +#define FTW_CONTEXT_FROM_THIS(a) CR (a, EFI_FTW_DEVICE, FtwInstance, FTW_DEVICE_SIGNATURE) + +// +// Driver entry point +// +/** + This function is the entry point of the Fault Tolerant Write driver. + + @param ImageHandle A handle for the image that is initializing this driver + @param SystemTable A pointer to the EFI system table + + @return EFI_SUCCESS FTW has finished the initialization + @retval EFI_NOT_FOUND Locate FVB protocol error + @retval EFI_OUT_OF_RESOURCES Allocate memory error + @retval EFI_VOLUME_CORRUPTED Firmware volume is error + @retval EFI_ABORTED FTW initialization error + +**/ +EFI_STATUS +EFIAPI +InitializeFaultTolerantWrite ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ); + +// +// Fault Tolerant Write Protocol API +// + +/** + Query the largest block that may be updated in a fault tolerant manner. + + + @param This Indicates a pointer to the calling context. + @param BlockSize A pointer to a caller allocated UINTN that is updated to + indicate the size of the largest block that can be updated. + + @return EFI_SUCCESS The function completed successfully + +**/ +EFI_STATUS +EFIAPI +FtwGetMaxBlockSize ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *This, + OUT UINTN *BlockSize + ); + +/** + Allocates space for the protocol to maintain information about writes. + Since writes must be completed in a fault tolerant manner and multiple + updates will require more resources to be successful, this function + enables the protocol to ensure that enough space exists to track + information about the upcoming writes. + + All writes must be completed or aborted before another fault tolerant write can occur. + + @param This Indicates a pointer to the calling context. + @param CallerId The GUID identifying the write. + @param PrivateDataSize The size of the caller's private data + that must be recorded for each write. + @param NumberOfWrites The number of fault tolerant block writes + that will need to occur. + + @return EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully. + @retval EFI_ACCESS_DENIED All allocated writes have not been completed. + +**/ +EFI_STATUS +EFIAPI +FtwAllocate ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *This, + IN EFI_GUID *CallerId, + IN UINTN PrivateDataSize, + IN UINTN NumberOfWrites + ); + +/** + Starts a target block update. This function will record data about write + in fault tolerant storage and will complete the write in a recoverable + manner, ensuring at all times that either the original contents or + the modified contents are available. + + + @param This Calling context + @param Lba The logical block address of the target block. + @param Offset The offset within the target block to place the data. + @param Length The number of bytes to write to the target block. + @param PrivateData A pointer to private data that the caller requires to + complete any pending writes in the event of a fault. + @param FvBlockHandle The handle of FVB protocol that provides services for + reading, writing, and erasing the target block. + @param Buffer The data to write. + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully. + @retval EFI_BAD_BUFFER_SIZE The input data can't fit within the spare block. + Offset + *NumBytes > SpareAreaLength. + @retval EFI_ACCESS_DENIED No writes have been allocated. + @retval EFI_OUT_OF_RESOURCES Cannot allocate enough memory resource. + @retval EFI_NOT_FOUND Cannot find FVB protocol by handle. + +**/ +EFI_STATUS +EFIAPI +FtwWrite ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *This, + IN EFI_LBA Lba, + IN UINTN Offset, + IN UINTN Length, + IN VOID *PrivateData, + IN EFI_HANDLE FvBlockHandle, + IN VOID *Buffer + ); + +/** + Restarts a previously interrupted write. The caller must provide the + block protocol needed to complete the interrupted write. + + @param This Calling context. + @param FvBlockHandle The handle of FVB protocol that provides services for + reading, writing, and erasing the target block. + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ACCESS_DENIED No pending writes exist + @retval EFI_NOT_FOUND FVB protocol not found by the handle + @retval EFI_ABORTED The function could not complete successfully + +**/ +EFI_STATUS +EFIAPI +FtwRestart ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *This, + IN EFI_HANDLE FvBlockHandle + ); + +/** + Aborts all previous allocated writes. + + @param This Calling context + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully. + @retval EFI_NOT_FOUND No allocated writes exist. + +**/ +EFI_STATUS +EFIAPI +FtwAbort ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *This + ); + +/** + Starts a target block update. This records information about the write + in fault tolerant storage and will complete the write in a recoverable + manner, ensuring at all times that either the original contents or + the modified contents are available. + + @param This Indicates a pointer to the calling context. + @param CallerId The GUID identifying the last write. + @param Lba The logical block address of the last write. + @param Offset The offset within the block of the last write. + @param Length The length of the last write. + @param PrivateDataSize bytes from the private data + stored for this write. + @param PrivateData A pointer to a buffer. The function will copy + @param Complete A Boolean value with TRUE indicating + that the write was completed. + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully + @retval EFI_NOT_FOUND No allocated writes exist + @retval EFI_BUFFER_TOO_SMALL Input buffer is not larget enough + +**/ +EFI_STATUS +EFIAPI +FtwGetLastWrite ( + IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *This, + OUT EFI_GUID *CallerId, + OUT EFI_LBA *Lba, + OUT UINTN *Offset, + OUT UINTN *Length, + IN OUT UINTN *PrivateDataSize, + OUT VOID *PrivateData, + OUT BOOLEAN *Complete + ); + +/** + Erase spare block. + + @param FtwDevice The private data of FTW driver + + @retval EFI_SUCCESS The erase request was successfully completed. + @retval EFI_ACCESS_DENIED The firmware volume is in the WriteDisabled state. + @retval EFI_DEVICE_ERROR The block device is not functioning + correctly and could not be written. + The firmware device may have been + partially erased. + @retval EFI_INVALID_PARAMETER One or more of the LBAs listed + in the variable argument list do + not exist in the firmware volume. + + +**/ +EFI_STATUS +FtwEraseSpareBlock ( + IN EFI_FTW_DEVICE *FtwDevice + ); + +/** + Retrive the proper FVB protocol interface by HANDLE. + + + @param FvBlockHandle The handle of FVB protocol that provides services for + reading, writing, and erasing the target block. + @param FvBlock The interface of FVB protocol + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully + +**/ +EFI_STATUS +FtwGetFvbByHandle ( + IN EFI_HANDLE FvBlockHandle, + OUT EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL **FvBlock + ); + +/** + + Is it in working block? + + @param FtwDevice The private data of FTW driver + @param FvBlock Fvb protocol instance + @param Lba The block specified + + @return A BOOLEAN value indicating in working block or not. + +**/ +BOOLEAN +IsWorkingBlock ( + EFI_FTW_DEVICE *FtwDevice, + EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvBlock, + EFI_LBA Lba + ); + +/** + + Is it in boot block? + + @param FtwDevice The private data of FTW driver + @param FvBlock Fvb protocol instance + @param Lba The block specified + + @return A BOOLEAN value indicating in boot block or not. + +**/ +BOOLEAN +IsBootBlock ( + EFI_FTW_DEVICE *FtwDevice, + EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvBlock, + EFI_LBA Lba + ); + +/** + Copy the content of spare block to a target block. Size is FTW_BLOCK_SIZE. + Spare block is accessed by FTW backup FVB protocol interface. LBA is 1. + Target block is accessed by FvbBlock protocol interface. LBA is Lba. + + + @param FtwDevice The private data of FTW driver + @param FvBlock FVB Protocol interface to access target block + @param Lba Lba of the target block + + @retval EFI_SUCCESS Spare block content is copied to target block + @retval EFI_INVALID_PARAMETER Input parameter error + @retval EFI_OUT_OF_RESOURCES Allocate memory error + @retval EFI_ABORTED The function could not complete successfully + +**/ +EFI_STATUS +FlushSpareBlockToTargetBlock ( + EFI_FTW_DEVICE *FtwDevice, + EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvBlock, + EFI_LBA Lba + ); + +/** + Copy the content of spare block to working block. Size is FTW_BLOCK_SIZE. + Spare block is accessed by FTW backup FVB protocol interface. LBA is + FtwDevice->FtwSpareLba. + Working block is accessed by FTW working FVB protocol interface. LBA is + FtwDevice->FtwWorkBlockLba. + + Since the working block header is important when FTW initializes, the + state of the operation should be handled carefully. The Crc value is + calculated without STATE element. + + @param FtwDevice The private data of FTW driver + + @retval EFI_SUCCESS Spare block content is copied to target block + @retval EFI_OUT_OF_RESOURCES Allocate memory error + @retval EFI_ABORTED The function could not complete successfully + +**/ +EFI_STATUS +FlushSpareBlockToWorkingBlock ( + EFI_FTW_DEVICE *FtwDevice + ); + +/** + Copy the content of spare block to a boot block. Size is FTW_BLOCK_SIZE. + Spare block is accessed by FTW working FVB protocol interface. LBA is 1. + Target block is accessed by FvbBlock protocol interface. LBA is Lba. + + FTW will do extra work on boot block update. + FTW should depend on a protocol of EFI_ADDRESS_RANGE_SWAP_PROTOCOL, + which is produced by a chipset driver. + FTW updating boot block steps may be: + 1. GetRangeLocation(), if the Range is inside the boot block, FTW know + that boot block will be update. It shall add a FLAG in the working block. + 2. When spare block is ready, + 3. SetSwapState(EFI_SWAPPED) + 4. erasing boot block, + 5. programming boot block until the boot block is ok. + 6. SetSwapState(UNSWAPPED) + FTW shall not allow to update boot block when battery state is error. + + @param FtwDevice The private data of FTW driver + + @retval EFI_SUCCESS Spare block content is copied to boot block + @retval EFI_INVALID_PARAMETER Input parameter error + @retval EFI_OUT_OF_RESOURCES Allocate memory error + @retval EFI_ABORTED The function could not complete successfully + +**/ +EFI_STATUS +FlushSpareBlockToBootBlock ( + EFI_FTW_DEVICE *FtwDevice + ); + +/** + 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 FvBlock FVB Protocol interface to access SrcBlock and DestBlock + @param Lba Lba of a block + @param Offset Offset on the Lba + @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 Others Access block device error. + Notes: + Assume all bits of State are inside the same BYTE. + @retval EFI_ABORTED Read block fail + +**/ +EFI_STATUS +FtwUpdateFvState ( + IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvBlock, + IN EFI_LBA Lba, + IN UINTN Offset, + IN UINT8 NewBit + ); + +/** + 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 + ); + +/** + Get the last Write Record pointer. The last write Record is the Record + whose DestinationCompleted state hasn't been set. After all, this Record + may be a EMPTY record entry for next write. + + + @param FtwWriteHeader Pointer to the write record header + @param FtwWriteRecord 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 +FtwGetLastWriteRecord ( + IN EFI_FAULT_TOLERANT_WRITE_HEADER *FtwWriteHeader, + OUT EFI_FAULT_TOLERANT_WRITE_RECORD **FtwWriteRecord + ); + +/** + To check if FtwRecord is the first record of FtwHeader. + + @param FtwHeader Pointer to the write record header + @param FtwRecord Pointer to the write record + + @retval TRUE FtwRecord is the first Record of the FtwHeader + @retval FALSE FtwRecord is not the first Record of the FtwHeader + +**/ +BOOLEAN +IsFirstRecordOfWrites ( + IN EFI_FAULT_TOLERANT_WRITE_HEADER *FtwHeader, + IN EFI_FAULT_TOLERANT_WRITE_RECORD *FtwRecord + ); + +/** + 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 + ); + +/** + To check if FtwRecord is the first record of FtwHeader. + + @param FtwHeader Pointer to the write record header + @param FtwRecord Pointer to retrieve the previous write record + + @retval EFI_ACCESS_DENIED Input record is the first record, no previous record is return. + @retval EFI_SUCCESS The previous write record is found. + +**/ +EFI_STATUS +GetPreviousRecordOfWrites ( + IN EFI_FAULT_TOLERANT_WRITE_HEADER *FtwHeader, + IN OUT EFI_FAULT_TOLERANT_WRITE_RECORD **FtwRecord + ); + +/** + + Check whether a flash buffer is erased. + + @param Buffer Buffer to check + @param BufferSize Size of the buffer + + @return A BOOLEAN value indicating erased or not. + +**/ +BOOLEAN +IsErasedFlashBuffer ( + IN UINT8 *Buffer, + IN UINTN BufferSize + ); +/** + 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 + ); +/** + 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 + ); +/** + 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 + ); +/** + 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 + ); + +/** + + Get firmware block by address. + + + @param Address Address specified the block + @param FvBlock The block caller wanted + + @retval EFI_SUCCESS The protocol instance if found. + @retval EFI_NOT_FOUND Block not found + +**/ +EFI_HANDLE +GetFvbByAddress ( + IN EFI_PHYSICAL_ADDRESS Address, + OUT EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL **FvBlock + ); + +#endif diff --git a/MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWriteDxe.inf b/MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWriteDxe.inf new file mode 100644 index 0000000000..cf526e701d --- /dev/null +++ b/MdeModulePkg/Universal/FaultTolerantWriteDxe/FaultTolerantWriteDxe.inf @@ -0,0 +1,67 @@ +#/** @file +# This driver installs Fault Tolerant Write (FTW) protocol, +# which provides fault tolerant write capability for block devices. +# Its implementation depends on the full functionality FVB protocol that support read, write/erase flash access. +# +# Copyright (c) 2006 - 2009, Intel Corporation +# +# All rights reserved. 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. +# +#**/ + +[Defines] + INF_VERSION = 0x00010005 + BASE_NAME = FaultTolerantWriteDxe + FILE_GUID = FE5CEA76-4F72-49e8-986F-2CD899DFFE5D + MODULE_TYPE = DXE_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = InitializeFaultTolerantWrite + +# +# The following information is for reference only and not required by the build tools. +# +# VALID_ARCHITECTURES = IA32 X64 IPF EBC +# + +[Sources.common] + FtwMisc.c + UpdateWorkingBlock.c + FaultTolerantWrite.c + FaultTolerantWrite.h + +[Packages] + MdePkg/MdePkg.dec + MdeModulePkg/MdeModulePkg.dec + +[LibraryClasses] + UefiBootServicesTableLib + MemoryAllocationLib + BaseMemoryLib + UefiDriverEntryPoint + DebugLib + +[Guids] + gEfiSystemNvDataFvGuid ## CONSUMES ## FV Signature of Working Space Header + +[Protocols] + gEfiSwapAddressRangeProtocolGuid | PcdFullFtwServiceEnable ## CONSUMES + gEfiFirmwareVolumeBlockProtocolGuid ## CONSUMES + gEfiFaultTolerantWriteProtocolGuid ## PRODUCES + +[FeaturePcd] + gEfiMdeModulePkgTokenSpaceGuid.PcdFullFtwServiceEnable + +[Pcd] + gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageFtwWorkingBase + gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageFtwWorkingSize + gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageFtwSpareBase + gEfiMdeModulePkgTokenSpaceGuid.PcdFlashNvStorageFtwSpareSize + +[Depex] + gEfiFirmwareVolumeBlockProtocolGuid AND gEfiAlternateFvBlockGuid ## gEfiAlternateFvBlockGuid specifies FVB protocol with read, write/erase flash access. + diff --git a/MdeModulePkg/Universal/FaultTolerantWriteDxe/FtwMisc.c b/MdeModulePkg/Universal/FaultTolerantWriteDxe/FtwMisc.c new file mode 100644 index 0000000000..de768e12ea --- /dev/null +++ b/MdeModulePkg/Universal/FaultTolerantWriteDxe/FtwMisc.c @@ -0,0 +1,971 @@ +/** @file + + Internal generic functions to operate flash block. + +Copyright (c) 2006 - 2008, Intel Corporation +All rights reserved. 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" + +/** + + Check whether a flash buffer is erased. + + @param Buffer Buffer to check + @param BufferSize Size of the buffer + + @return A BOOLEAN value indicating erased or not. + +**/ +BOOLEAN +IsErasedFlashBuffer ( + IN UINT8 *Buffer, + IN UINTN BufferSize + ) +{ + BOOLEAN IsEmpty; + UINT8 *Ptr; + UINTN Index; + + Ptr = Buffer; + IsEmpty = TRUE; + for (Index = 0; Index < BufferSize; Index += 1) { + if (*Ptr++ != FTW_ERASED_BYTE) { + IsEmpty = FALSE; + break; + } + } + + return IsEmpty; +} + +/** + To erase the block with the spare block size. + + + @param FtwDevice The private data of FTW driver + @param FvBlock FVB Protocol interface + @param Lba Lba of the firmware block + + @retval EFI_SUCCESS Block LBA is Erased successfully + @retval Others Error occurs + +**/ +EFI_STATUS +FtwEraseBlock ( + IN EFI_FTW_DEVICE *FtwDevice, + EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvBlock, + EFI_LBA Lba + ) +{ + return FvBlock->EraseBlocks ( + FvBlock, + Lba, + FtwDevice->NumberOfSpareBlock, + EFI_LBA_LIST_TERMINATOR + ); +} + +/** + Erase spare block. + + @param FtwDevice The private data of FTW driver + + @retval EFI_SUCCESS The erase request was successfully completed. + @retval EFI_ACCESS_DENIED The firmware volume is in the WriteDisabled state. + @retval EFI_DEVICE_ERROR The block device is not functioning + correctly and could not be written. + The firmware device may have been + partially erased. + @retval EFI_INVALID_PARAMETER One or more of the LBAs listed + in the variable argument list do + not exist in the firmware volume. + + +**/ +EFI_STATUS +FtwEraseSpareBlock ( + IN EFI_FTW_DEVICE *FtwDevice + ) +{ + return FtwDevice->FtwBackupFvb->EraseBlocks ( + FtwDevice->FtwBackupFvb, + FtwDevice->FtwSpareLba, + FtwDevice->NumberOfSpareBlock, + EFI_LBA_LIST_TERMINATOR + ); +} + +/** + Retrive the proper FVB protocol interface by HANDLE. + + + @param FvBlockHandle The handle of FVB protocol that provides services for + reading, writing, and erasing the target block. + @param FvBlock The interface of FVB protocol + + @retval EFI_SUCCESS The function completed successfully + @retval EFI_ABORTED The function could not complete successfully + +**/ +EFI_STATUS +FtwGetFvbByHandle ( + IN EFI_HANDLE FvBlockHandle, + OUT EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL **FvBlock + ) +{ + // + // To get the FVB protocol interface on the handle + // + return gBS->HandleProtocol ( + FvBlockHandle, + &gEfiFirmwareVolumeBlockProtocolGuid, + (VOID **) FvBlock + ); +} + +/** + + Is it in working block? + + @param FtwDevice The private data of FTW driver + @param FvBlock Fvb protocol instance + @param Lba The block specified + + @return A BOOLEAN value indicating in working block or not. + +**/ +BOOLEAN +IsWorkingBlock ( + EFI_FTW_DEVICE *FtwDevice, + EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvBlock, + EFI_LBA Lba + ) +{ + // + // If matching the following condition, the target block is in working block. + // 1. Target block is on the FV of working block (Using the same FVB protocol instance). + // 2. Lba falls into the range of working block. + // + return (BOOLEAN) + ( + (FvBlock == FtwDevice->FtwFvBlock) && + (Lba >= FtwDevice->FtwWorkBlockLba) && + (Lba <= FtwDevice->FtwWorkSpaceLba) + ); +} + +/** + + Get firmware block by address. + + + @param Address Address specified the block + @param FvBlock The block caller wanted + + @retval EFI_SUCCESS The protocol instance if found. + @retval EFI_NOT_FOUND Block not found + +**/ +EFI_HANDLE +GetFvbByAddress ( + IN EFI_PHYSICAL_ADDRESS Address, + OUT EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL **FvBlock + ) +{ + EFI_STATUS Status; + EFI_HANDLE *HandleBuffer; + UINTN HandleCount; + UINTN Index; + EFI_PHYSICAL_ADDRESS FvbBaseAddress; + EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *Fvb; + EFI_FIRMWARE_VOLUME_HEADER *FwVolHeader; + EFI_HANDLE FvbHandle; + + *FvBlock = NULL; + FvbHandle = NULL; + // + // Locate all handles of Fvb protocol + // + Status = gBS->LocateHandleBuffer ( + ByProtocol, + &gEfiFirmwareVolumeBlockProtocolGuid, + NULL, + &HandleCount, + &HandleBuffer + ); + if (EFI_ERROR (Status)) { + return NULL; + } + // + // Get the FVB to access variable store + // + for (Index = 0; Index < HandleCount; Index += 1) { + Status = gBS->HandleProtocol ( + HandleBuffer[Index], + &gEfiFirmwareVolumeBlockProtocolGuid, + (VOID **) &Fvb + ); + if (EFI_ERROR (Status)) { + break; + } + // + // Compare the address and select the right one + // + Status = Fvb->GetPhysicalAddress (Fvb, &FvbBaseAddress); + if (EFI_ERROR (Status)) { + continue; + } + + FwVolHeader = (EFI_FIRMWARE_VOLUME_HEADER *) ((UINTN) FvbBaseAddress); + if ((Address >= FvbBaseAddress) && (Address <= (FvbBaseAddress + (FwVolHeader->FvLength - 1)))) { + *FvBlock = Fvb; + FvbHandle = HandleBuffer[Index]; + break; + } + } + + FreePool (HandleBuffer); + return FvbHandle; +} + +/** + + Is it in boot block? + + @param FtwDevice The private data of FTW driver + @param FvBlock Fvb protocol instance + @param Lba The block specified + + @return A BOOLEAN value indicating in boot block or not. + +**/ +BOOLEAN +IsBootBlock ( + EFI_FTW_DEVICE *FtwDevice, + EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvBlock, + EFI_LBA Lba + ) +{ + EFI_STATUS Status; + EFI_SWAP_ADDRESS_RANGE_PROTOCOL *SarProtocol; + EFI_PHYSICAL_ADDRESS BootBlockBase; + UINTN BootBlockSize; + EFI_PHYSICAL_ADDRESS BackupBlockBase; + UINTN BackupBlockSize; + EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *BootFvb; + BOOLEAN IsSwapped; + EFI_HANDLE FvbHandle; + + if (!FeaturePcdGet(PcdFullFtwServiceEnable)) { + return FALSE; + } + + Status = gBS->LocateProtocol (&gEfiSwapAddressRangeProtocolGuid, NULL, (VOID **) &SarProtocol); + if (EFI_ERROR (Status)) { + return FALSE; + } + // + // Get the boot block range + // + Status = SarProtocol->GetRangeLocation ( + SarProtocol, + &BootBlockBase, + &BootBlockSize, + &BackupBlockBase, + &BackupBlockSize + ); + if (EFI_ERROR (Status)) { + return FALSE; + } + + Status = SarProtocol->GetSwapState (SarProtocol, &IsSwapped); + if (EFI_ERROR (Status)) { + return FALSE; + } + // + // Get FVB by address + // + if (!IsSwapped) { + FvbHandle = GetFvbByAddress (BootBlockBase, &BootFvb); + } else { + FvbHandle = GetFvbByAddress (BackupBlockBase, &BootFvb); + } + + if (FvbHandle == NULL) { + return FALSE; + } + // + // Compare the Fvb + // + return (BOOLEAN) (FvBlock == BootFvb); +} + +/** + Copy the content of spare block to a boot block. Size is FTW_BLOCK_SIZE. + Spare block is accessed by FTW working FVB protocol interface. LBA is 1. + Target block is accessed by FvbBlock protocol interface. LBA is Lba. + + FTW will do extra work on boot block update. + FTW should depend on a protocol of EFI_ADDRESS_RANGE_SWAP_PROTOCOL, + which is produced by a chipset driver. + FTW updating boot block steps may be: + 1. GetRangeLocation(), if the Range is inside the boot block, FTW know + that boot block will be update. It shall add a FLAG in the working block. + 2. When spare block is ready, + 3. SetSwapState(EFI_SWAPPED) + 4. erasing boot block, + 5. programming boot block until the boot block is ok. + 6. SetSwapState(UNSWAPPED) + FTW shall not allow to update boot block when battery state is error. + + @param FtwDevice The private data of FTW driver + + @retval EFI_SUCCESS Spare block content is copied to boot block + @retval EFI_INVALID_PARAMETER Input parameter error + @retval EFI_OUT_OF_RESOURCES Allocate memory error + @retval EFI_ABORTED The function could not complete successfully + +**/ +EFI_STATUS +FlushSpareBlockToBootBlock ( + EFI_FTW_DEVICE *FtwDevice + ) +{ + EFI_STATUS Status; + UINTN Length; + UINT8 *Buffer; + UINTN Count; + UINT8 *Ptr; + UINTN Index; + BOOLEAN TopSwap; + EFI_SWAP_ADDRESS_RANGE_PROTOCOL *SarProtocol; + EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *BootFvb; + EFI_LBA BootLba; + + if (!FeaturePcdGet(PcdFullFtwServiceEnable)) { + return EFI_UNSUPPORTED; + } + + // + // Locate swap address range protocol + // + Status = gBS->LocateProtocol (&gEfiSwapAddressRangeProtocolGuid, NULL, (VOID **) &SarProtocol); + if (EFI_ERROR (Status)) { + return Status; + } + // + // Allocate a memory buffer + // + Length = FtwDevice->SpareAreaLength; + Buffer = AllocatePool (Length); + if (Buffer == NULL) { + return EFI_OUT_OF_RESOURCES; + } + // + // Get TopSwap bit state + // + Status = SarProtocol->GetSwapState (SarProtocol, &TopSwap); + if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_ERROR, "Ftw: Get Top Swapped status - %r\n", Status)); + FreePool (Buffer); + return EFI_ABORTED; + } + + if (TopSwap) { + // + // Get FVB of current boot block + // + if (GetFvbByAddress (FtwDevice->SpareAreaAddress + FtwDevice->SpareAreaLength, &BootFvb) == NULL) { + FreePool (Buffer); + return EFI_ABORTED; + } + // + // Read data from current boot block + // + BootLba = 0; + Ptr = Buffer; + for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) { + Count = FtwDevice->BlockSize; + Status = BootFvb->Read ( + BootFvb, + BootLba + Index, + 0, + &Count, + Ptr + ); + if (EFI_ERROR (Status)) { + FreePool (Buffer); + return Status; + } + + Ptr += Count; + } + } else { + // + // Read data from spare block + // + Ptr = Buffer; + for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) { + Count = FtwDevice->BlockSize; + Status = FtwDevice->FtwBackupFvb->Read ( + FtwDevice->FtwBackupFvb, + FtwDevice->FtwSpareLba + Index, + 0, + &Count, + Ptr + ); + if (EFI_ERROR (Status)) { + FreePool (Buffer); + return Status; + } + + Ptr += Count; + } + // + // Set TopSwap bit + // + Status = SarProtocol->SetSwapState (SarProtocol, TRUE); + if (EFI_ERROR (Status)) { + FreePool (Buffer); + return Status; + } + } + // + // Erase current spare block + // Because TopSwap is set, this actually erase the top block (boot block)! + // + Status = FtwEraseSpareBlock (FtwDevice); + if (EFI_ERROR (Status)) { + FreePool (Buffer); + return EFI_ABORTED; + } + // + // Write memory buffer currenet spare block. Still top block. + // + Ptr = Buffer; + for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) { + Count = FtwDevice->BlockSize; + Status = FtwDevice->FtwBackupFvb->Write ( + FtwDevice->FtwBackupFvb, + FtwDevice->FtwSpareLba + Index, + 0, + &Count, + Ptr + ); + if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_ERROR, "Ftw: FVB Write boot block - %r\n", Status)); + FreePool (Buffer); + return Status; + } + + Ptr += Count; + } + + FreePool (Buffer); + + // + // Clear TopSwap bit + // + Status = SarProtocol->SetSwapState (SarProtocol, FALSE); + + return Status; +} + +/** + Copy the content of spare block to a target block. Size is FTW_BLOCK_SIZE. + Spare block is accessed by FTW backup FVB protocol interface. LBA is 1. + Target block is accessed by FvbBlock protocol interface. LBA is Lba. + + + @param FtwDevice The private data of FTW driver + @param FvBlock FVB Protocol interface to access target block + @param Lba Lba of the target block + + @retval EFI_SUCCESS Spare block content is copied to target block + @retval EFI_INVALID_PARAMETER Input parameter error + @retval EFI_OUT_OF_RESOURCES Allocate memory error + @retval EFI_ABORTED The function could not complete successfully + +**/ +EFI_STATUS +FlushSpareBlockToTargetBlock ( + EFI_FTW_DEVICE *FtwDevice, + EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvBlock, + EFI_LBA Lba + ) +{ + EFI_STATUS Status; + UINTN Length; + UINT8 *Buffer; + UINTN Count; + UINT8 *Ptr; + UINTN Index; + + if ((FtwDevice == NULL) || (FvBlock == NULL)) { + return EFI_INVALID_PARAMETER; + } + // + // Allocate a memory buffer + // + Length = FtwDevice->SpareAreaLength; + Buffer = AllocatePool (Length); + if (Buffer == NULL) { + return EFI_OUT_OF_RESOURCES; + } + // + // Read all content of spare block to memory buffer + // + Ptr = Buffer; + for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) { + Count = FtwDevice->BlockSize; + Status = FtwDevice->FtwBackupFvb->Read ( + FtwDevice->FtwBackupFvb, + FtwDevice->FtwSpareLba + Index, + 0, + &Count, + Ptr + ); + if (EFI_ERROR (Status)) { + FreePool (Buffer); + return Status; + } + + Ptr += Count; + } + // + // Erase the target block + // + Status = FtwEraseBlock (FtwDevice, FvBlock, Lba); + if (EFI_ERROR (Status)) { + FreePool (Buffer); + return EFI_ABORTED; + } + // + // Write memory buffer to block, using the FvbBlock protocol interface + // + Ptr = Buffer; + for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) { + Count = FtwDevice->BlockSize; + Status = FvBlock->Write (FvBlock, Lba + Index, 0, &Count, Ptr); + if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_ERROR, "Ftw: FVB Write block - %r\n", Status)); + FreePool (Buffer); + return Status; + } + + Ptr += Count; + } + + FreePool (Buffer); + + return Status; +} + +/** + Copy the content of spare block to working block. Size is FTW_BLOCK_SIZE. + Spare block is accessed by FTW backup FVB protocol interface. LBA is + FtwDevice->FtwSpareLba. + Working block is accessed by FTW working FVB protocol interface. LBA is + FtwDevice->FtwWorkBlockLba. + + Since the working block header is important when FTW initializes, the + state of the operation should be handled carefully. The Crc value is + calculated without STATE element. + + @param FtwDevice The private data of FTW driver + + @retval EFI_SUCCESS Spare block content is copied to target block + @retval EFI_OUT_OF_RESOURCES Allocate memory error + @retval EFI_ABORTED The function could not complete successfully + +**/ +EFI_STATUS +FlushSpareBlockToWorkingBlock ( + EFI_FTW_DEVICE *FtwDevice + ) +{ + EFI_STATUS Status; + UINTN Length; + UINT8 *Buffer; + EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *WorkingBlockHeader; + UINTN Count; + UINT8 *Ptr; + UINTN Index; + EFI_LBA WorkSpaceLbaOffset; + + // + // Allocate a memory buffer + // + Length = FtwDevice->SpareAreaLength; + Buffer = AllocatePool (Length); + if (Buffer == NULL) { + return EFI_OUT_OF_RESOURCES; + } + // + // To guarantee that the WorkingBlockValid is set on spare block + // + // Offset = OFFSET_OF(EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER, + // WorkingBlockValid); + // To skip Signature and Crc: sizeof(EFI_GUID)+sizeof(UINT32). + // + FtwUpdateFvState ( + FtwDevice->FtwBackupFvb, + FtwDevice->FtwWorkSpaceLba, + FtwDevice->FtwWorkSpaceBase + sizeof (EFI_GUID) + sizeof (UINT32), + WORKING_BLOCK_VALID + ); + // + // Read from spare block to memory buffer + // + Ptr = Buffer; + for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) { + Count = FtwDevice->BlockSize; + Status = FtwDevice->FtwBackupFvb->Read ( + FtwDevice->FtwBackupFvb, + FtwDevice->FtwSpareLba + Index, + 0, + &Count, + Ptr + ); + if (EFI_ERROR (Status)) { + FreePool (Buffer); + return Status; + } + + Ptr += Count; + } + // + // Clear the CRC and STATE, copy data from spare to working block. + // + WorkSpaceLbaOffset = FtwDevice->FtwWorkSpaceLba - FtwDevice->FtwWorkBlockLba; + WorkingBlockHeader = (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *) (Buffer + (UINTN) WorkSpaceLbaOffset * FtwDevice->BlockSize + FtwDevice->FtwWorkSpaceBase); + InitWorkSpaceHeader (WorkingBlockHeader); + WorkingBlockHeader->WorkingBlockValid = FTW_ERASE_POLARITY; + WorkingBlockHeader->WorkingBlockInvalid = FTW_ERASE_POLARITY; + + // + // target block is working block, then + // Set WorkingBlockInvalid in EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER + // before erase the working block. + // + // Offset = OFFSET_OF(EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER, + // WorkingBlockInvalid); + // So hardcode offset as sizeof(EFI_GUID)+sizeof(UINT32) to + // skip Signature and Crc. + // + Status = FtwUpdateFvState ( + FtwDevice->FtwFvBlock, + FtwDevice->FtwWorkSpaceLba, + FtwDevice->FtwWorkSpaceBase + sizeof (EFI_GUID) + sizeof (UINT32), + WORKING_BLOCK_INVALID + ); + if (EFI_ERROR (Status)) { + FreePool (Buffer); + return EFI_ABORTED; + } + + FtwDevice->FtwWorkSpaceHeader->WorkingBlockInvalid = FTW_VALID_STATE; + + // + // Erase the working block + // + Status = FtwEraseBlock (FtwDevice, FtwDevice->FtwFvBlock, FtwDevice->FtwWorkBlockLba); + if (EFI_ERROR (Status)) { + FreePool (Buffer); + return EFI_ABORTED; + } + // + // Write memory buffer to working block, using the FvbBlock protocol interface + // + Ptr = Buffer; + for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) { + Count = FtwDevice->BlockSize; + Status = FtwDevice->FtwFvBlock->Write ( + FtwDevice->FtwFvBlock, + FtwDevice->FtwWorkBlockLba + Index, + 0, + &Count, + Ptr + ); + if (EFI_ERROR (Status)) { + DEBUG ((EFI_D_ERROR, "Ftw: FVB Write block - %r\n", Status)); + FreePool (Buffer); + return Status; + } + + Ptr += Count; + } + // + // Since the memory buffer will not be used, free memory Buffer. + // + FreePool (Buffer); + + // + // Update the VALID of the working block + // + // Offset = OFFSET_OF(EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER, WorkingBlockValid); + // So hardcode offset as sizeof(EFI_GUID)+sizeof(UINT32) to skip Signature and Crc. + // + Status = FtwUpdateFvState ( + FtwDevice->FtwFvBlock, + FtwDevice->FtwWorkSpaceLba, + FtwDevice->FtwWorkSpaceBase + sizeof (EFI_GUID) + sizeof (UINT32), + WORKING_BLOCK_VALID + ); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + + FtwDevice->FtwWorkSpaceHeader->WorkingBlockValid = FTW_VALID_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 FvBlock FVB Protocol interface to access SrcBlock and DestBlock + @param Lba Lba of a block + @param Offset Offset on the Lba + @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 Others Access block device error. + Notes: + Assume all bits of State are inside the same BYTE. + @retval EFI_ABORTED Read block fail + +**/ +EFI_STATUS +FtwUpdateFvState ( + IN EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *FvBlock, + IN EFI_LBA Lba, + IN UINTN Offset, + IN UINT8 NewBit + ) +{ + EFI_STATUS Status; + UINT8 State; + UINTN Length; + + // + // Read state from device, assume State is only one byte. + // + Length = sizeof (UINT8); + Status = FvBlock->Read (FvBlock, Lba, Offset, &Length, &State); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + + State ^= FTW_POLARITY_REVERT; + State = (UINT8) (State | NewBit); + State ^= FTW_POLARITY_REVERT; + + // + // Write state back to device + // + Length = sizeof (UINT8); + Status = FvBlock->Write (FvBlock, Lba, Offset, &Length, &State); + + return Status; +} + +/** + 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_WRITE_HEADER); + + while (FtwHeader->Complete == FTW_VALID_STATE) { + Offset += 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; +} + +/** + Get the last Write Record pointer. The last write Record is the Record + whose DestinationCompleted state hasn't been set. After all, this Record + may be a EMPTY record entry for next write. + + + @param FtwWriteHeader Pointer to the write record header + @param FtwWriteRecord 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 +FtwGetLastWriteRecord ( + IN EFI_FAULT_TOLERANT_WRITE_HEADER *FtwWriteHeader, + OUT EFI_FAULT_TOLERANT_WRITE_RECORD **FtwWriteRecord + ) +{ + UINTN Index; + EFI_FAULT_TOLERANT_WRITE_RECORD *FtwRecord; + + *FtwWriteRecord = NULL; + FtwRecord = (EFI_FAULT_TOLERANT_WRITE_RECORD *) (FtwWriteHeader + 1); + + // + // Try to find the last write record "that has not completed" + // + for (Index = 0; Index < FtwWriteHeader->NumberOfWrites; Index += 1) { + if (FtwRecord->DestinationComplete != FTW_VALID_STATE) { + // + // The last write record is found + // + *FtwWriteRecord = FtwRecord; + return EFI_SUCCESS; + } + + FtwRecord++; + + if (FtwWriteHeader->PrivateDataSize != 0) { + FtwRecord = (EFI_FAULT_TOLERANT_WRITE_RECORD *) ((UINTN) FtwRecord + FtwWriteHeader->PrivateDataSize); + } + } + // + // if Index == NumberOfWrites, then + // the last record has been written successfully, + // but the Header->Complete Flag has not been set. + // also return the last record. + // + if (Index == FtwWriteHeader->NumberOfWrites) { + *FtwWriteRecord = (EFI_FAULT_TOLERANT_WRITE_RECORD *) ((UINTN) FtwRecord - RECORD_SIZE (FtwWriteHeader->PrivateDataSize)); + return EFI_SUCCESS; + } + + return EFI_ABORTED; +} + +/** + To check if FtwRecord is the first record of FtwHeader. + + @param FtwHeader Pointer to the write record header + @param FtwRecord Pointer to the write record + + @retval TRUE FtwRecord is the first Record of the FtwHeader + @retval FALSE FtwRecord is not the first Record of the FtwHeader + +**/ +BOOLEAN +IsFirstRecordOfWrites ( + 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 += sizeof (EFI_FAULT_TOLERANT_WRITE_HEADER); + return (BOOLEAN) (Head == Ptr); +} + +/** + 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 += WRITE_TOTAL_SIZE (FtwHeader->NumberOfWrites - 1, FtwHeader->PrivateDataSize); + return (BOOLEAN) (Head == Ptr); +} + +/** + To check if FtwRecord is the first record of FtwHeader. + + @param FtwHeader Pointer to the write record header + @param FtwRecord Pointer to retrieve the previous write record + + @retval EFI_ACCESS_DENIED Input record is the first record, no previous record is return. + @retval EFI_SUCCESS The previous write record is found. + +**/ +EFI_STATUS +GetPreviousRecordOfWrites ( + IN EFI_FAULT_TOLERANT_WRITE_HEADER *FtwHeader, + IN OUT EFI_FAULT_TOLERANT_WRITE_RECORD **FtwRecord + ) +{ + UINT8 *Ptr; + + if (IsFirstRecordOfWrites (FtwHeader, *FtwRecord)) { + *FtwRecord = NULL; + return EFI_ACCESS_DENIED; + } + + Ptr = (UINT8 *) (*FtwRecord); + Ptr -= RECORD_SIZE (FtwHeader->PrivateDataSize); + *FtwRecord = (EFI_FAULT_TOLERANT_WRITE_RECORD *) Ptr; + return EFI_SUCCESS; +} diff --git a/MdeModulePkg/Universal/FaultTolerantWriteDxe/UpdateWorkingBlock.c b/MdeModulePkg/Universal/FaultTolerantWriteDxe/UpdateWorkingBlock.c new file mode 100644 index 0000000000..83bb3b88eb --- /dev/null +++ b/MdeModulePkg/Universal/FaultTolerantWriteDxe/UpdateWorkingBlock.c @@ -0,0 +1,491 @@ +/** @file + + Internal functions to operate Working Block Space. + +Copyright (c) 2006 - 2008, Intel Corporation +All rights reserved. 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" + +/** + 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; + + if (WorkingHeader == NULL) { + return FALSE; + } + + if (WorkingHeader->WorkingBlockValid != FTW_VALID_STATE) { + DEBUG ((EFI_D_ERROR, "Ftw: Work block header valid bit check error\n")); + return FALSE; + } + // + // Check signature with gEfiSystemNvDataFvGuid + // + if (!CompareGuid (&gEfiSystemNvDataFvGuid, &WorkingHeader->Signature)) { + DEBUG ((EFI_D_ERROR, "Ftw: Work block header signature check error\n")); + 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 = gBS->CalculateCrc32 ( + (UINT8 *) &WorkingBlockHeader, + sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER), + &WorkingBlockHeader.Crc + ); + if (EFI_ERROR (Status)) { + return FALSE; + } + + if (WorkingBlockHeader.Crc != WorkingHeader->Crc) { + DEBUG ((EFI_D_ERROR, "Ftw: 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; + + if (WorkingHeader == NULL) { + return EFI_INVALID_PARAMETER; + } + // + // Here using gEfiSystemNvDataFvGuid as the signature. + // + CopyMem ( + &WorkingHeader->Signature, + &gEfiSystemNvDataFvGuid, + sizeof (EFI_GUID) + ); + WorkingHeader->WriteQueueSize = (UINT64) (PcdGet32 (PcdFlashNvStorageFtwWorkingSize) - 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 = gBS->CalculateCrc32 ( + (UINT8 *) WorkingHeader, + sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER), + &WorkingHeader->Crc + ); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + // + // Restore the WorkingBlockValid flag to VALID state + // + WorkingHeader->WorkingBlockValid = FTW_VALID_STATE; + WorkingHeader->WorkingBlockInvalid = FTW_INVALID_STATE; + + 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 Length; + UINTN Offset; + EFI_FAULT_TOLERANT_WRITE_HEADER *FtwHeader; + + // + // Initialize WorkSpace as FTW_ERASED_BYTE + // + SetMem ( + FtwDevice->FtwWorkSpace, + FtwDevice->FtwWorkSpaceSize, + FTW_ERASED_BYTE + ); + + // + // Read from working block + // + Length = FtwDevice->FtwWorkSpaceSize; + Status = FtwDevice->FtwFvBlock->Read ( + FtwDevice->FtwFvBlock, + FtwDevice->FtwWorkSpaceLba, + FtwDevice->FtwWorkSpaceBase, + &Length, + FtwDevice->FtwWorkSpace + ); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + // + // Refresh the FtwLastWriteHeader + // + Status = FtwGetLastWriteHeader ( + FtwDevice->FtwWorkSpaceHeader, + FtwDevice->FtwWorkSpaceSize, + &FtwDevice->FtwLastWriteHeader + ); + + FtwHeader = FtwDevice->FtwLastWriteHeader; + Offset = (UINTN) (UINT8 *) FtwHeader - (UINTN) FtwDevice->FtwWorkSpace; + + // + // if the Header is out of the workspace limit, call reclaim. + // + if (EFI_ERROR (Status) && (Offset >= FtwDevice->FtwWorkSpaceSize)) { + // + // 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 + // + Length = FtwDevice->FtwWorkSpaceSize; + Status = FtwDevice->FtwFvBlock->Read ( + FtwDevice->FtwFvBlock, + FtwDevice->FtwWorkSpaceLba, + FtwDevice->FtwWorkSpaceBase, + &Length, + FtwDevice->FtwWorkSpace + ); + if (EFI_ERROR (Status)) { + return EFI_ABORTED; + } + + Status = FtwGetLastWriteHeader ( + FtwDevice->FtwWorkSpaceHeader, + FtwDevice->FtwWorkSpaceSize, + &FtwDevice->FtwLastWriteHeader + ); + } + // + // 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; + + DEBUG ((EFI_D_ERROR, "Ftw: start to reclaim work space\n")); + + // + // Read all original data from working block to a memory buffer + // + TempBufferSize = FtwDevice->SpareAreaLength; + TempBuffer = AllocateZeroPool (TempBufferSize); + if (TempBuffer == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Ptr = TempBuffer; + for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) { + Length = FtwDevice->BlockSize; + 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) (FtwDevice->FtwWorkSpaceLba - FtwDevice->FtwWorkBlockLba)) * FtwDevice->BlockSize + + 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)) { + CopyMem ( + Ptr + sizeof (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER), + FtwDevice->FtwLastWriteHeader, + WRITE_TOTAL_SIZE (Header->NumberOfWrites, Header->PrivateDataSize) + ); + } + } + + CopyMem ( + FtwDevice->FtwWorkSpace, + Ptr, + FtwDevice->FtwWorkSpaceSize + ); + + FtwGetLastWriteHeader ( + FtwDevice->FtwWorkSpaceHeader, + FtwDevice->FtwWorkSpaceSize, + &FtwDevice->FtwLastWriteHeader + ); + + // + // Set the WorkingBlockValid and WorkingBlockInvalid as INVALID + // + WorkingBlockHeader = (EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER *) (TempBuffer + 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->BlockSize; + 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); + Ptr = TempBuffer; + for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) { + Length = FtwDevice->BlockSize; + 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; + } + // + // Free TempBuffer + // + FreePool (TempBuffer); + + // + // Set the WorkingBlockValid in spare block + // + Status = FtwUpdateFvState ( + FtwDevice->FtwBackupFvb, + FtwDevice->FtwWorkSpaceLba, + FtwDevice->FtwWorkSpaceBase + 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->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); + Ptr = SpareBuffer; + for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) { + Length = FtwDevice->BlockSize; + 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_ERROR, "Ftw: reclaim work space successfully\n")); + + return EFI_SUCCESS; +}