FaultTolerantWriteDxe

驱动注释   

        Edk2中的FaultTolerantWriteDxe.c源码中有一段注释如下:该驱动由于拥有非易失性中间存储数据和私有信息,使得其可以自动的从致命错误,例如掉电中恢复,且该驱动只能作用于块设备,例如flash。

          该驱动使用 FTW(容错写入)工作空间。此工作空间是工作块上工作空间的内存副本,工作空间的大小为 FTW_WORK_SPACE_SIZE 字节。

        工作区间将每个写入记录存储为EFI_FTW_RECORD结构。

        在将数据写入到目标块之前,会先在备份块里面存储写buffer的数据内容,如同其名,就是做一个备份。

        写入记录具有三种状态,用于指定写入操作的不同阶段。
  1) WRITE_ALLOCATED是记录在写空间中分配好了的标志。写操作的信息存储在写记录结构体中。
  2) SPARE_COMPLETED作为备份完成标志,意味着写buffer中的数据已经备份在了备用块中。
  3) WRITE_COMPLETED标志着将数据从备用块复制到目标块的完成。

        该驱动以备用块的整个空间大小来操作数据。

1、它首先将 SpareAreaLength 长度的数据从目标块读取到备用内存缓冲区中

2、然后将写buffer的数据拷贝进备用buffer

3、接下来将备份buffer中的数据写入到备用块

4、将备用块的数据拷贝至目标块

        从上述驱动工作原理可以看出,容错写入并不是一开始就将数据写入到目标块,而是先写入备用块,在写入目标块。

        要使此驱动器正常工作,必须满足以下条件:
  1、写入的数据必须适合备用区域。(偏移量 + 字节数 <= 备用区域长度)
  2、整个flash具有相同的块大小。
  3、工作块是在其最后一个块中包含工作空间的区域,其大小与备用块相同。
  4、工作块区域必须在产生FVB协议的单个固件卷块范围内。
  5、备用区域必须在产生 FVB 协议的单个固件卷块范围内。
  6、任何写入数据的区域(备用区域长度区域)都必须是在产生FVB协议的单个固件卷块范围内。
  7、如果写入数据区域(如可变范围)扩大,则必须扩大备用区域范围。备用区域必须足够大,以便在将写入数据写入目标范围之前存储写入数据。

        以上如果任意一个不满足,FtwWrite 可能会失败。

        通常,备用区域只占用一个块。即SpareAreaLength = BlockSize, NumberOfSpareBlock = 1。

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.

FaultTolerantWriteInitialize

        根据FaultTolerantWriteDxe.inf文件可以看到该模块入口函数名为FaultTolerantWriteInitialize

         找到FaultTolerantWriteDxe.c文件中的FaultTolerantWriteInitialize入口

/**
  This function is the entry point of the Fault Tolerant Write driver.

  @param[in] ImageHandle        A handle for the image that is initializing this driver
  @param[in] SystemTable        A pointer to the EFI system table

  @retval EFI_SUCCESS           The initialization finished successfully.
  @retval EFI_OUT_OF_RESOURCES  Allocate memory error
  @retval EFI_INVALID_PARAMETER Workspace or Spare block does not exist

**/
EFI_STATUS
EFIAPI
FaultTolerantWriteInitialize (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS      Status;
  EFI_FTW_DEVICE  *FtwDevice;

  FtwDevice = NULL;

  //
  // Allocate private data structure for FTW protocol and do some initialization
  //
  Status = InitFtwDevice (&FtwDevice);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Register FvbNotificationEvent () notify function.
  //
  EfiCreateProtocolNotifyEvent (
    &gEfiFirmwareVolumeBlockProtocolGuid,
    TPL_CALLBACK,
    FvbNotificationEvent,
    (VOID *)FtwDevice,
    &mFvbRegistration
    );

  return EFI_SUCCESS;
}

        其中EFI_FTW_DEVICE  *FtwDevice;结构体如下图,可以看到结构体内的成员变量基本就是前面驱动注释提到过的工作空间、备用空间的部分参数,例如地址,块大小等,这些参数将在容错写入中被用到,而InitFtwDevice (&FtwDevice);函数则是对该结构体的成员变量进行初始化操作。

//
// 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                                      NumberOfWorkSpaceBlock;  // Number of the blocks in work block for work space.
  UINTN                                      WorkBlockSize;           // Block size in bytes of the work blocks in flash
  UINTN                                      SpareAreaLength;         // Size of spare range in flash.
  UINTN                                      NumberOfSpareBlock;      // Number of the blocks in spare block.
  UINTN                                      SpareBlockSize;          // Block size in bytes of the spare 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.
  UINTN                                      NumberOfWorkBlock;       // Number of the blocks in work 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.
  EFI_LBA                                    FtwWorkSpaceLbaInSpare;  // Start LBA of working space in spare block.
  UINTN                                      FtwWorkSpaceBaseInSpare; // Offset into the FtwWorkSpaceLbaInSpare block.
  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;

         在入口函数中还注册了一个Notify事件,函数中的gEfiFirmwareVolumeBlockProtocolGuid是在FvbServicesRuntimeDxe.inf模块中安装的,而这个模块会先于FaultTolerantWriteDxe.inf运行,所以虽说是回调,但是因为gEfiFirmwareVolumeBlockProtocolGuid已经安装了,所以会在模块中直接执行。该回调函数的主要作用是安装gEfiFaultTolerantWriteProtocolGuid对应的Protocol。

/**
  Firmware Volume Block Protocol notification event handler.

  @param[in] Event    Event whose notification function is being invoked.
  @param[in] Context  Pointer to the notification function's context.

**/
VOID
EFIAPI
FvbNotificationEvent (
  IN  EFI_EVENT  Event,
  IN  VOID       *Context
  )
{
  EFI_STATUS                         Status;
  EFI_FAULT_TOLERANT_WRITE_PROTOCOL  *FtwProtocol;
  EFI_FTW_DEVICE                     *FtwDevice;

  //
  // Just return to avoid installing FaultTolerantWriteProtocol again
  // if Fault Tolerant Write protocol has been installed.
  //
  Status = gBS->LocateProtocol (
                  &gEfiFaultTolerantWriteProtocolGuid,
                  NULL,
                  (VOID **)&FtwProtocol
                  );
  if (!EFI_ERROR (Status)) {
    return;
  }

  //
  // Found proper FVB protocol and initialize FtwDevice for protocol installation
  //
  FtwDevice = (EFI_FTW_DEVICE *)Context;
  Status    = InitFtwProtocol (FtwDevice);
  if (EFI_ERROR (Status)) {
    return;
  }

FaultTolerantWrite.c       

         安装好该驱动后,就可以使用了,来到FaultTolerantWrite.c文件,可以看到该文件下总共有这些函数。

        值得注意的是FtwWrite与FtwRestart,前者可以向目标块区域写入数据,而后者主要作用则在于重启之前被中断的写操作。

FtwWrite

        FtwWrite作为写入函数,首先需要获取到FtwDevice的所有成员,并将FtwLastWriteHeader赋给Header,FtwLastWriteRecord赋给Record,并检查获取到的Header下的Complete标志位与与Record下的DestinationComplete是否有效,这一步应该是若标志位有效,那就说明数据已经正常写入,并不需要容错写入。

        做完检查后,就可以获取FVB的protocol了,FVBprotocol的安装在之前DXE文件中已经实现。

        接下来将Record写入工作空间中,当Record写入到工作空间中后,便可以开始数据的部分。

        数据部分的主要操作流程如图:

        1、申请一个mybuffer,并令ptr=mybuffer,随后将Lba+Index的数据读入ptr,此处的Lba+index为The starting logical block index from which to read.需要读的逻辑块地址,此举是将目标块的所有原始数据读入到mybuffer中先存起来,随后调用copymem将要写入的新的buffer数据追加到mybuffer中。

        2、申请sparebuffer,此举目的是为了保存备份块的内容,因此,让ptr=sparebuffer,并将spare block的数据读入到sparebuffer中。

        3、接下来就是将mybuffer里的数据写入到spare block备份块中,这么做的目的在驱动注释中已经有所解释,只要将数据先写入到spare block,那么容错写入就一定能完成。在这一步中还会将SpareComplete标志位写入Record中,表示备份块写入完成。

        4、最后一步,恢复Spare block的原始数据内容,至此,容错写入就全部完成。

/**
  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;
  UINTN                               BlockSize;
  UINTN                               NumberOfBlocks;
  UINTN                               NumberOfWriteBlocks;
  UINTN                               WriteLength;

  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 ((DEBUG_ERROR, "Ftw: no allocates space for write record!\n"));
      DEBUG ((DEBUG_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)Record - (UINTN)Header) > FTW_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;
  }

  //
  // 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 ((DEBUG_ERROR, "Ftw: Write(), Get FVB physical address - %r\n", Status));
    return EFI_ABORTED;
  }

  //
  // Now, one FVB has one type of BlockSize.
  //
  Status = Fvb->GetBlockSize (Fvb, 0, &BlockSize, &NumberOfBlocks);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Ftw: Write(), Get block size - %r\n", Status));
    return EFI_ABORTED;
  }

  NumberOfWriteBlocks = FTW_BLOCKS (Offset + Length, BlockSize);
  DEBUG ((DEBUG_INFO, "Ftw: Write(), BlockSize - 0x%x, NumberOfWriteBlock - 0x%x\n", BlockSize, NumberOfWriteBlocks));
  WriteLength = NumberOfWriteBlocks * BlockSize;

  //
  // Check if the input data can fit within the spare block.
  //
  if (WriteLength > FtwDevice->SpareAreaLength) {
    return EFI_BAD_BUFFER_SIZE;
  }

  //
  // Set BootBlockUpdate FLAG if it's updating boot block.
  //
  if (IsBootBlock (FtwDevice, Fvb)) {
    Record->BootBlockUpdate = FTW_VALID_STATE;
    //
    // Boot Block and Spare Block should have same block size and block numbers.
    //
    ASSERT ((BlockSize == FtwDevice->SpareBlockSize) && (NumberOfWriteBlocks == FtwDevice->NumberOfSpareBlock));
  }

  //
  // Write the record to the work space.
  //
  Record->Lba            = Lba;
  Record->Offset         = Offset;
  Record->Length         = Length;
  Record->RelativeOffset = (INT64)(FvbPhysicalAddress + (UINTN)Lba * BlockSize) - (INT64)FtwDevice->SpareAreaAddress;
  if (PrivateData != NULL) {
    CopyMem ((Record + 1), PrivateData, (UINTN)Header->PrivateDataSize);
  }

  MyOffset = (UINT8 *)Record - FtwDevice->FtwWorkSpace;
  MyLength = FTW_RECORD_SIZE (Header->PrivateDataSize);

  Status = WriteWorkSpaceData (
             FtwDevice->FtwFvBlock,
             FtwDevice->WorkBlockSize,
             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 = WriteLength;
  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 < NumberOfWriteBlocks; Index += 1) {
    MyLength = 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->SpareBlockSize;
    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
  // Do not assume Spare Block and Target Block have same block size
  //
  Status = FtwEraseSpareBlock (FtwDevice);
  if (EFI_ERROR (Status)) {
    FreePool (MyBuffer);
    FreePool (SpareBuffer);
    return EFI_ABORTED;
  }

  Ptr = MyBuffer;
  for (Index = 0; MyBufferSize > 0; Index += 1) {
    if (MyBufferSize > FtwDevice->SpareBlockSize) {
      MyLength = FtwDevice->SpareBlockSize;
    } else {
      MyLength = MyBufferSize;
    }

    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;
    MyBufferSize -= MyLength;
  }

  //
  // Free MyBuffer
  //
  FreePool (MyBuffer);

  //
  // Set the SpareComplete in the FTW record,
  //
  MyOffset = (UINT8 *)Record - FtwDevice->FtwWorkSpace;
  Status   = FtwUpdateFvState (
               FtwDevice->FtwFvBlock,
               FtwDevice->WorkBlockSize,
               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, BlockSize);
  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);
  if (EFI_ERROR (Status)) {
    FreePool (SpareBuffer);
    return EFI_ABORTED;
  }

  Ptr = SpareBuffer;
  for (Index = 0; Index < FtwDevice->NumberOfSpareBlock; Index += 1) {
    MyLength = FtwDevice->SpareBlockSize;
    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 (
    (DEBUG_INFO,
     "Ftw: Write() success, (Lba:Offset)=(%lx:0x%x), Length: 0x%x\n",
     Lba,
     Offset,
     Length)
    );

  return EFI_SUCCESS;
}

 FtwRestart        

        观察一下FtwRestart函数,可以看到,复写操作其实就是当备份块完成了,但目的块还没完成的时候,就发生了断电等问题,那么就用复写,将备份块的数据再写入目的块中。并且只要数据备份在了备份块,就可以保证写入目的块。最后通过检查Header->Complete标志以及Record->DestinationComplete标志来判断是否写完了,如果写完了,则可以擦除备份块了,因为已经不需要了。

/**
  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;
  UINTN                               BlockSize;
  UINTN                               NumberOfBlocks;

  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;
  }

  //
  // Now, one FVB has one type of BlockSize
  //
  Status = Fvb->GetBlockSize (Fvb, 0, &BlockSize, &NumberOfBlocks);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "Ftw: Restart(), Get block size - %r\n", Status));
    return EFI_ABORTED;
  }

  //
  // 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, BlockSize);
  if (EFI_ERROR (Status)) {
    return EFI_ABORTED;
  }

  //
  // Erase Spare block
  // This is restart, no need to keep spareblock content.
  //
  Status = FtwEraseSpareBlock (FtwDevice);
  if (EFI_ERROR (Status)) {
    return EFI_ABORTED;
  }

  DEBUG ((DEBUG_INFO, "%a(): success\n", __func__));
  return EFI_SUCCESS;
}

FtwWriteRecord

         可以看到FtwWriteRecord函数是利用容错写入方法写入一份记录,若目标块就是工作区域,那么直接将备份块的数据flush进工作区域,而如果不是,则将备份块的数据flush进目标块,主要通过FlushSpareBlockToWorkingBlock与FlushSpareBlockToBootBlock实现,而其FlushSpareBlockToWorkingBlock实现原理和FtwWrite类似:

        1、申请一个buffer,将备份块数据读入buffer

        2、将buffer数据写入工作区域

/**
  Write a record with fault tolerant manner.
  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.
  @param BlockSize       The size of the 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,
  IN UINTN                               BlockSize
  )
{
  EFI_STATUS                       Status;
  EFI_FTW_DEVICE                   *FtwDevice;
  EFI_FAULT_TOLERANT_WRITE_HEADER  *Header;
  EFI_FAULT_TOLERANT_WRITE_RECORD  *Record;
  UINTN                            Offset;
  UINTN                            NumberOfWriteBlocks;

  FtwDevice = FTW_CONTEXT_FROM_THIS (This);

  //
  // Spare Complete but Destination not complete,
  // Recover the target 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->SpareBlockSize,
               FtwDevice->FtwSpareLba + FtwDevice->FtwWorkSpaceLbaInSpare,
               FtwDevice->FtwWorkSpaceBaseInSpare + Offset,
               SPARE_COMPLETED
               );
    if (EFI_ERROR (Status)) {
      return EFI_ABORTED;
    }

    Status = FlushSpareBlockToWorkingBlock (FtwDevice);
  } else if (IsBootBlock (FtwDevice, Fvb)) {
    //
    // Update boot block
    //
    Status = FlushSpareBlockToBootBlock (FtwDevice);
  } else {
    //
    // Update blocks other than working block or boot block
    //
    NumberOfWriteBlocks = FTW_BLOCKS ((UINTN)(Record->Offset + Record->Length), BlockSize);
    Status              = FlushSpareBlockToTargetBlock (FtwDevice, Fvb, Record->Lba, BlockSize, NumberOfWriteBlocks);
  }

  if (EFI_ERROR (Status)) {
    return EFI_ABORTED;
  }

  //
  // Record the DestionationComplete in record
  //
  Offset = (UINT8 *)Record - FtwDevice->FtwWorkSpace;
  Status = FtwUpdateFvState (
             FtwDevice->FtwFvBlock,
             FtwDevice->WorkBlockSize,
             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->WorkBlockSize,
               FtwDevice->FtwWorkSpaceLba,
               FtwDevice->FtwWorkSpaceBase + Offset,
               WRITES_COMPLETED
               );
    Header->Complete = FTW_VALID_STATE;
    if (EFI_ERROR (Status)) {
      return EFI_ABORTED;
    }
  }

  return EFI_SUCCESS;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值