/** @file Implement the Fault Tolerant Write (FTW) protocol based on SMM FTW module. Copyright (c) 2011 - 2018, Intel Corporation. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "FaultTolerantWriteSmmDxe.h" EFI_HANDLE mHandle = NULL; EFI_SMM_COMMUNICATION_PROTOCOL *mSmmCommunication = NULL; UINTN mPrivateDataSize = 0; EFI_FAULT_TOLERANT_WRITE_PROTOCOL mFaultTolerantWriteDriver = { FtwGetMaxBlockSize, FtwAllocate, FtwWrite, FtwRestart, FtwAbort, FtwGetLastWrite }; /** Initialize the communicate buffer using DataSize and Function number. @param[out] CommunicateBuffer The communicate buffer. Caller should free it after use. @param[out] DataPtr Points to the data in the communicate buffer. Caller should not free it. @param[in] DataSize The payload size. @param[in] Function The function number used to initialize the communicate header. **/ VOID InitCommunicateBuffer ( OUT VOID **CommunicateBuffer, OUT VOID **DataPtr, IN UINTN DataSize, IN UINTN Function ) { EFI_SMM_COMMUNICATE_HEADER *SmmCommunicateHeader; SMM_FTW_COMMUNICATE_FUNCTION_HEADER *SmmFtwFunctionHeader; // // The whole buffer size: SMM_COMMUNICATE_HEADER_SIZE + SMM_FTW_COMMUNICATE_HEADER_SIZE + DataSize. // SmmCommunicateHeader = AllocateZeroPool(DataSize + SMM_COMMUNICATE_HEADER_SIZE + SMM_FTW_COMMUNICATE_HEADER_SIZE); ASSERT (SmmCommunicateHeader != NULL); // // Prepare data buffer. // CopyGuid (&SmmCommunicateHeader->HeaderGuid, &gEfiSmmFaultTolerantWriteProtocolGuid); SmmCommunicateHeader->MessageLength = DataSize + SMM_FTW_COMMUNICATE_HEADER_SIZE; SmmFtwFunctionHeader = (SMM_FTW_COMMUNICATE_FUNCTION_HEADER *) SmmCommunicateHeader->Data; SmmFtwFunctionHeader->Function = Function; *CommunicateBuffer = SmmCommunicateHeader; if (DataPtr != NULL) { *DataPtr = SmmFtwFunctionHeader->Data; } } /** Send the data in communicate buffer to SMI handler and get response. @param[in, out] SmmCommunicateHeader The communicate buffer. @param[in] DataSize The payload size. **/ EFI_STATUS SendCommunicateBuffer ( IN OUT EFI_SMM_COMMUNICATE_HEADER *SmmCommunicateHeader, IN UINTN DataSize ) { EFI_STATUS Status; UINTN CommSize; SMM_FTW_COMMUNICATE_FUNCTION_HEADER *SmmFtwFunctionHeader; CommSize = DataSize + SMM_COMMUNICATE_HEADER_SIZE + SMM_FTW_COMMUNICATE_HEADER_SIZE; Status = mSmmCommunication->Communicate (mSmmCommunication, SmmCommunicateHeader, &CommSize); ASSERT_EFI_ERROR(Status); SmmFtwFunctionHeader = (SMM_FTW_COMMUNICATE_FUNCTION_HEADER *) SmmCommunicateHeader->Data; return SmmFtwFunctionHeader->ReturnStatus; } /** Get the FvbBaseAddress and FvbAttributes from the FVB handle FvbHandle. @param[in] FvbHandle The handle of FVB protocol that provides services. @param[out] FvbBaseAddress The base address of the FVB attached with FvbHandle. @param[out] FvbAttributes The attributes of the FVB attached with FvbHandle. @retval EFI_SUCCESS The function completed successfully. @retval Others The function could not complete successfully. **/ EFI_STATUS ConvertFvbHandle ( IN EFI_HANDLE FvbHandle, OUT EFI_PHYSICAL_ADDRESS *FvbBaseAddress, OUT EFI_FVB_ATTRIBUTES_2 *FvbAttributes ) { EFI_STATUS Status; EFI_FIRMWARE_VOLUME_BLOCK_PROTOCOL *Fvb; Status = gBS->HandleProtocol (FvbHandle, &gEfiFirmwareVolumeBlockProtocolGuid, (VOID **) &Fvb); if (EFI_ERROR(Status)) { return Status; } Status = Fvb->GetPhysicalAddress (Fvb, FvbBaseAddress); if (EFI_ERROR(Status)) { return Status; } Status = Fvb->GetAttributes (Fvb, FvbAttributes); return Status; } /** Get the size of the largest block that can be updated in a fault-tolerant manner. @param[in] This Indicates a pointer to the calling context. @param[out] 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. **/ EFI_STATUS EFIAPI FtwGetMaxBlockSize ( IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *This, OUT UINTN *BlockSize ) { EFI_STATUS Status; UINTN PayloadSize; EFI_SMM_COMMUNICATE_HEADER *SmmCommunicateHeader; SMM_FTW_GET_MAX_BLOCK_SIZE_HEADER *SmmFtwBlockSizeHeader; // // Initialize the communicate buffer. // PayloadSize = sizeof (SMM_FTW_GET_MAX_BLOCK_SIZE_HEADER); InitCommunicateBuffer ((VOID **)&SmmCommunicateHeader, (VOID **)&SmmFtwBlockSizeHeader, PayloadSize, FTW_FUNCTION_GET_MAX_BLOCK_SIZE); // // Send data to SMM. // Status = SendCommunicateBuffer (SmmCommunicateHeader, PayloadSize); // // Get data from SMM // *BlockSize = SmmFtwBlockSizeHeader->BlockSize; FreePool(SmmCommunicateHeader); return Status; } /** Allocates space for the protocol to maintain information about writes. Since writes must be completed in a fault-tolerant manner and multiple writes require more resources to be successful, this function enables the protocol to ensure that enough space exists to track information about upcoming writes. @param[in] This A pointer to the calling context. @param[in] CallerId The GUID identifying the write. @param[in] PrivateDataSize The size of the caller's private data that must be recorded for each write. @param[in] 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 Not all allocated writes have been completed. All writes must be completed or aborted before another fault tolerant write can occur. **/ 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 PayloadSize; EFI_SMM_COMMUNICATE_HEADER *SmmCommunicateHeader; SMM_FTW_ALLOCATE_HEADER *SmmFtwAllocateHeader; // // Initialize the communicate buffer. // PayloadSize = sizeof (SMM_FTW_ALLOCATE_HEADER); InitCommunicateBuffer ((VOID **)&SmmCommunicateHeader, (VOID **)&SmmFtwAllocateHeader, PayloadSize, FTW_FUNCTION_ALLOCATE); CopyGuid (&SmmFtwAllocateHeader->CallerId, CallerId); SmmFtwAllocateHeader->PrivateDataSize = PrivateDataSize; SmmFtwAllocateHeader->NumberOfWrites = NumberOfWrites; // // Send data to SMM. // Status = SendCommunicateBuffer (SmmCommunicateHeader, PayloadSize); if (!EFI_ERROR( Status)) { mPrivateDataSize = PrivateDataSize; } FreePool(SmmCommunicateHeader); return Status; } /** 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[in] This The calling context. @param[in] Lba The logical block address of the target block. @param[in] Offset The offset within the target block to place the data. @param[in] Length The number of bytes to write to the target block. @param[in] PrivateData A pointer to private data that the caller requires to complete any pending writes in the event of a fault. @param[in] FvBlockHandle The handle of FVB protocol that provides services for reading, writing, and erasing the target block. @param[in] 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. **/ 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; UINTN PayloadSize; EFI_SMM_COMMUNICATE_HEADER *SmmCommunicateHeader; SMM_FTW_WRITE_HEADER *SmmFtwWriteHeader; // // Initialize the communicate buffer. // PayloadSize = OFFSET_OF (SMM_FTW_WRITE_HEADER, Data) + Length; if (PrivateData != NULL) { // // The private data buffer size should be the same one in FtwAllocate API. // PayloadSize += mPrivateDataSize; } InitCommunicateBuffer ((VOID **)&SmmCommunicateHeader, (VOID **)&SmmFtwWriteHeader, PayloadSize, FTW_FUNCTION_WRITE); // // FvBlockHandle can not be used in SMM environment. Here we get the FVB protocol first, then get FVB base address // and its attribute. Send these information to SMM handler, the SMM handler will find the proper FVB to write data. // Status = ConvertFvbHandle (FvBlockHandle, &SmmFtwWriteHeader->FvbBaseAddress, &SmmFtwWriteHeader->FvbAttributes); if (EFI_ERROR(Status)) { FreePool(SmmCommunicateHeader); return EFI_ABORTED; } SmmFtwWriteHeader->Lba = Lba; SmmFtwWriteHeader->Offset = Offset; SmmFtwWriteHeader->Length = Length; CopyMem(SmmFtwWriteHeader->Data, Buffer, Length); if (PrivateData == NULL) { SmmFtwWriteHeader->PrivateDataSize = 0; } else { SmmFtwWriteHeader->PrivateDataSize = mPrivateDataSize; CopyMem(&SmmFtwWriteHeader->Data[Length], PrivateData, mPrivateDataSize); } // // Send data to SMM. // Status = SendCommunicateBuffer (SmmCommunicateHeader, PayloadSize); FreePool(SmmCommunicateHeader); return Status; } /** Restarts a previously interrupted write. The caller must provide the block protocol needed to complete the interrupted write. @param[in] This The calling context. @param[in] FvBlockHandle The handle of FVB protocol that provides services. @retval EFI_SUCCESS The function completed successfully. @retval EFI_ABORTED The function could not complete successfully. @retval EFI_ACCESS_DENIED No pending writes exist. **/ EFI_STATUS EFIAPI FtwRestart ( IN EFI_FAULT_TOLERANT_WRITE_PROTOCOL *This, IN EFI_HANDLE FvBlockHandle ) { EFI_STATUS Status; UINTN PayloadSize; EFI_SMM_COMMUNICATE_HEADER *SmmCommunicateHeader; SMM_FTW_RESTART_HEADER *SmmFtwRestartHeader; // // Initialize the communicate buffer. // PayloadSize = sizeof (SMM_FTW_RESTART_HEADER); InitCommunicateBuffer ((VOID **)&SmmCommunicateHeader, (VOID **)&SmmFtwRestartHeader, PayloadSize, FTW_FUNCTION_RESTART); // // FvBlockHandle can not be used in SMM environment. Here we get the FVB protocol first, then get FVB base address // and its attribute. Send these information to SMM handler, the SMM handler will find the proper FVB to write data. // Status = ConvertFvbHandle (FvBlockHandle, &SmmFtwRestartHeader->FvbBaseAddress, &SmmFtwRestartHeader->FvbAttributes); if (EFI_ERROR(Status)) { FreePool(SmmCommunicateHeader); return EFI_ABORTED; } // // Send data to SMM. // Status = SendCommunicateBuffer (SmmCommunicateHeader, PayloadSize); FreePool(SmmCommunicateHeader); return Status; } /** Aborts all previously allocated writes. @param[in] This The 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 ) { EFI_STATUS Status; EFI_SMM_COMMUNICATE_HEADER *SmmCommunicateHeader; // // Initialize the communicate buffer. // InitCommunicateBuffer ((VOID **)&SmmCommunicateHeader, NULL, 0, FTW_FUNCTION_ABORT); // // Send data to SMM. // Status = SendCommunicateBuffer (SmmCommunicateHeader, 0); FreePool(SmmCommunicateHeader); return Status; } /** Starts a target block update. This function records information about the write in fault-tolerant storage and completes the write in a recoverable manner, ensuring at all times that either the original contents or the modified contents are available. @param[in] This Indicates a pointer to the calling context. @param[out] CallerId The GUID identifying the last write. @param[out] Lba The logical block address of the last write. @param[out] Offset The offset within the block of the last write. @param[out] Length The length of the last write. @param[in, out] PrivateDataSize On input, the size of the PrivateData buffer. On output, the size of the private data stored for this write. @param[out] PrivateData A pointer to a buffer. The function will copy PrivateDataSize bytes from the private data stored for this write. @param[out] 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. **/ 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; UINTN PayloadSize; EFI_SMM_COMMUNICATE_HEADER *SmmCommunicateHeader; SMM_FTW_GET_LAST_WRITE_HEADER *SmmFtwGetLastWriteHeader; // // Initialize the communicate buffer. // PayloadSize = OFFSET_OF (SMM_FTW_GET_LAST_WRITE_HEADER, Data) + *PrivateDataSize; InitCommunicateBuffer ((VOID **)&SmmCommunicateHeader, (VOID **)&SmmFtwGetLastWriteHeader, PayloadSize, FTW_FUNCTION_GET_LAST_WRITE); SmmFtwGetLastWriteHeader->PrivateDataSize = *PrivateDataSize; // // Send data to SMM. // Status = SendCommunicateBuffer (SmmCommunicateHeader, PayloadSize); // // Get data from SMM // *PrivateDataSize = SmmFtwGetLastWriteHeader->PrivateDataSize; if (Status == EFI_SUCCESS || Status == EFI_BUFFER_TOO_SMALL) { *Lba = SmmFtwGetLastWriteHeader->Lba; *Offset = SmmFtwGetLastWriteHeader->Offset; *Length = SmmFtwGetLastWriteHeader->Length; *Complete = SmmFtwGetLastWriteHeader->Complete; CopyGuid (CallerId, &SmmFtwGetLastWriteHeader->CallerId); if (Status == EFI_SUCCESS) { CopyMem(PrivateData, SmmFtwGetLastWriteHeader->Data, *PrivateDataSize); } } else if (Status == EFI_NOT_FOUND) { *Complete = SmmFtwGetLastWriteHeader->Complete; } FreePool(SmmCommunicateHeader); return Status; } /** SMM Fault Tolerant Write Protocol notification event handler. Install Fault Tolerant Write Protocol. @param[in] Event Event whose notification function is being invoked. @param[in] Context Pointer to the notification function's context. **/ VOID EFIAPI SmmFtwReady ( IN EFI_EVENT Event, IN VOID *Context ) { EFI_STATUS Status; EFI_FAULT_TOLERANT_WRITE_PROTOCOL *FtwProtocol; // // Just return to avoid install SMM FaultTolerantWriteProtocol again // if Fault Tolerant Write protocol had been installed. // Status = gBS->LocateProtocol (&gEfiFaultTolerantWriteProtocolGuid, NULL, (VOID **)&FtwProtocol); if (!EFI_ERROR(Status)) { return; } Status = gBS->LocateProtocol (&gEfiSmmCommunicationProtocolGuid, NULL, (VOID **) &mSmmCommunication); ASSERT_EFI_ERROR(Status); // // Install protocol interface // Status = gBS->InstallProtocolInterface ( &mHandle, &gEfiFaultTolerantWriteProtocolGuid, EFI_NATIVE_INTERFACE, &mFaultTolerantWriteDriver ); ASSERT_EFI_ERROR(Status); Status = gBS->CloseEvent (Event); ASSERT_EFI_ERROR(Status); } /** The driver entry point for Fault Tolerant Write driver. The function does the necessary initialization work. @param[in] ImageHandle The firmware allocated handle for the UEFI image. @param[in] SystemTable A pointer to the EFI system table. @retval EFI_SUCCESS This funtion always return EFI_SUCCESS. **/ EFI_STATUS EFIAPI FaultTolerantWriteSmmInitialize ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { VOID *SmmFtwRegistration; // // Smm FTW driver is ready // EfiCreateProtocolNotifyEvent ( &gEfiSmmFaultTolerantWriteProtocolGuid, TPL_CALLBACK, SmmFtwReady, NULL, &SmmFtwRegistration ); return EFI_SUCCESS; }