/** @file This file implement the Variable Protocol for the block device. Copyright (c) 2015-2016, Linaro Limited. All rights reserved. Copyright (c) 2015-2016, Hisilicon Limited. 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 <Guid/VariableFormat.h> #include <Guid/SystemNvDataGuid.h> #include <Library/BaseMemoryLib.h> #include <Library/CacheMaintenanceLib.h> #include <Library/DebugLib.h> #include <Library/DevicePathLib.h> #include <Library/IoLib.h> #include <Library/MemoryAllocationLib.h> #include <Library/PcdLib.h> #include <Library/UefiBootServicesTableLib.h> #include <Library/UefiLib.h> #include "BlockVariableDxe.h" STATIC EFI_PHYSICAL_ADDRESS mMapNvStorageVariableBase; EFI_STATUS EFIAPI FvbGetAttributes ( IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL *This, OUT EFI_FVB_ATTRIBUTES_2 *Attributes ) { EFI_FVB_ATTRIBUTES_2 FvbAttributes; FvbAttributes = (EFI_FVB_ATTRIBUTES_2) ( EFI_FVB2_READ_ENABLED_CAP | // Reads may be enabled EFI_FVB2_READ_STATUS | // Reads are currently enabled EFI_FVB2_STICKY_WRITE | // A block erase is required to flip bits into EFI_FVB2_ERASE_POLARITY EFI_FVB2_MEMORY_MAPPED | // It is memory mapped EFI_FVB2_ERASE_POLARITY // After erasure all bits take this value (i.e. '1') ); FvbAttributes |= EFI_FVB2_WRITE_STATUS | // Writes are currently enabled EFI_FVB2_WRITE_ENABLED_CAP; // Writes may be enabled *Attributes = FvbAttributes; DEBUG ((DEBUG_BLKIO, "FvbGetAttributes(0x%X)\n", *Attributes)); return EFI_SUCCESS; } EFI_STATUS EFIAPI FvbSetAttributes( IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL *This, IN OUT EFI_FVB_ATTRIBUTES_2 *Attributes ) { DEBUG ((DEBUG_BLKIO, "FvbSetAttributes(0x%X) is not supported\n",*Attributes)); return EFI_UNSUPPORTED; } EFI_STATUS EFIAPI FvbGetPhysicalAddress ( IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL *This, OUT EFI_PHYSICAL_ADDRESS *Address ) { *Address = mMapNvStorageVariableBase; return EFI_SUCCESS; } EFI_STATUS EFIAPI FvbGetBlockSize ( IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL *This, IN EFI_LBA Lba, OUT UINTN *BlockSize, OUT UINTN *NumberOfBlocks ) { BLOCK_VARIABLE_INSTANCE *Instance; Instance = CR (This, BLOCK_VARIABLE_INSTANCE, FvbProtocol, BLOCK_VARIABLE_SIGNATURE); *BlockSize = (UINTN) Instance->Media.BlockSize; *NumberOfBlocks = PcdGet32 (PcdNvStorageVariableBlockCount); return EFI_SUCCESS; } EFI_STATUS EFIAPI FvbRead ( IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL *This, IN EFI_LBA Lba, IN UINTN Offset, IN OUT UINTN *NumBytes, IN OUT UINT8 *Buffer ) { BLOCK_VARIABLE_INSTANCE *Instance; EFI_BLOCK_IO_PROTOCOL *BlockIo; EFI_STATUS Status; UINTN Bytes; VOID *DataPtr; Instance = CR (This, BLOCK_VARIABLE_INSTANCE, FvbProtocol, BLOCK_VARIABLE_SIGNATURE); BlockIo = Instance->BlockIoProtocol; Bytes = (Offset + *NumBytes + Instance->Media.BlockSize - 1) / Instance->Media.BlockSize * Instance->Media.BlockSize; DataPtr = AllocateZeroPool (Bytes); if (DataPtr == NULL) { DEBUG ((EFI_D_ERROR, "FvbWrite: failed to allocate buffer.\n")); return EFI_BUFFER_TOO_SMALL; } WriteBackDataCacheRange (DataPtr, Bytes); InvalidateDataCacheRange (Buffer, *NumBytes); Status = BlockIo->ReadBlocks (BlockIo, BlockIo->Media->MediaId, Instance->StartLba + Lba, Bytes, DataPtr); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_ERROR, "FvbRead StartLba:%x, Lba:%x, Offset:%x, Status:%x\n", Instance->StartLba, Lba, Offset, Status)); goto exit; } CopyMem (Buffer, DataPtr + Offset, *NumBytes); WriteBackDataCacheRange (Buffer, *NumBytes); exit: FreePool (DataPtr); return Status; } EFI_STATUS EFIAPI FvbWrite ( IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL *This, IN EFI_LBA Lba, IN UINTN Offset, IN OUT UINTN *NumBytes, IN UINT8 *Buffer ) { BLOCK_VARIABLE_INSTANCE *Instance; EFI_BLOCK_IO_PROTOCOL *BlockIo; EFI_STATUS Status; UINTN Bytes; VOID *DataPtr; Instance = CR (This, BLOCK_VARIABLE_INSTANCE, FvbProtocol, BLOCK_VARIABLE_SIGNATURE); BlockIo = Instance->BlockIoProtocol; Bytes = (Offset + *NumBytes + Instance->Media.BlockSize - 1) / Instance->Media.BlockSize * Instance->Media.BlockSize; DataPtr = AllocateZeroPool (Bytes); if (DataPtr == NULL) { DEBUG ((EFI_D_ERROR, "FvbWrite: failed to allocate buffer.\n")); return EFI_BUFFER_TOO_SMALL; } WriteBackDataCacheRange (DataPtr, Bytes); Status = BlockIo->ReadBlocks (BlockIo, BlockIo->Media->MediaId, Instance->StartLba + Lba, Bytes, DataPtr); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_ERROR, "FvbWrite: failed on reading blocks.\n")); goto exit; } CopyMem (DataPtr + Offset, Buffer, *NumBytes); WriteBackDataCacheRange (DataPtr, Bytes); Status = BlockIo->WriteBlocks (BlockIo, BlockIo->Media->MediaId, Instance->StartLba + Lba, Bytes, DataPtr); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_ERROR, "FvbWrite StartLba:%x, Lba:%x, Offset:%x, Status:%x\n", Instance->StartLba, Lba, Offset, Status)); } // Sometimes the variable isn't flushed into block device if it's the last flush operation. // So flush it again. Status = BlockIo->WriteBlocks (BlockIo, BlockIo->Media->MediaId, Instance->StartLba + Lba, Bytes, DataPtr); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_ERROR, "FvbWrite StartLba:%x, Lba:%x, Offset:%x, Status:%x\n", Instance->StartLba, Lba, Offset, Status)); } exit: FreePool (DataPtr); return Status; } EFI_STATUS EFIAPI FvbEraseBlocks ( IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL *This, ... ) { return EFI_SUCCESS; } STATIC BLOCK_VARIABLE_INSTANCE mBlockVariableInstance = { .Signature = BLOCK_VARIABLE_SIGNATURE, .Media = { .MediaId = 0, .RemovableMedia = FALSE, .MediaPresent = TRUE, .LogicalPartition = TRUE, .ReadOnly = FALSE, .WriteCaching = FALSE, .BlockSize = 0, .IoAlign = 4, .LastBlock = 0, .LowestAlignedLba = 0, .LogicalBlocksPerPhysicalBlock = 0, }, .FvbProtocol = { .GetAttributes = FvbGetAttributes, .SetAttributes = FvbSetAttributes, .GetPhysicalAddress = FvbGetPhysicalAddress, .GetBlockSize = FvbGetBlockSize, .Read = FvbRead, .Write = FvbWrite, .EraseBlocks = FvbEraseBlocks, } }; EFI_STATUS ValidateFvHeader ( IN EFI_FIRMWARE_VOLUME_HEADER *FwVolHeader ) { UINT16 Checksum, TempChecksum; VARIABLE_STORE_HEADER *VariableStoreHeader; UINTN VariableStoreLength; UINTN FvLength; FvLength = (UINTN) (PcdGet32(PcdFlashNvStorageVariableSize) + PcdGet32(PcdFlashNvStorageFtwWorkingSize) + PcdGet32(PcdFlashNvStorageFtwSpareSize)); // // Verify the header revision, header signature, length // Length of FvBlock cannot be 2**64-1 // HeaderLength cannot be an odd number // if ( (FwVolHeader->Revision != EFI_FVH_REVISION) || (FwVolHeader->Signature != EFI_FVH_SIGNATURE) || (FwVolHeader->FvLength != FvLength) ) { DEBUG ((EFI_D_ERROR, "ValidateFvHeader: No Firmware Volume header present\n")); return EFI_NOT_FOUND; } // Check the Firmware Volume Guid if( CompareGuid (&FwVolHeader->FileSystemGuid, &gEfiSystemNvDataFvGuid) == FALSE ) { DEBUG ((EFI_D_ERROR, "ValidateFvHeader: Firmware Volume Guid non-compatible\n")); return EFI_NOT_FOUND; } // Verify the header checksum TempChecksum = FwVolHeader->Checksum; FwVolHeader->Checksum = 0; Checksum = CalculateSum16((UINT16*)FwVolHeader, FwVolHeader->HeaderLength); if (Checksum != TempChecksum) { DEBUG ((EFI_D_ERROR, "ValidateFvHeader: FV checksum is invalid (Checksum:0x%X)\n",Checksum)); return EFI_NOT_FOUND; } FwVolHeader->Checksum = Checksum; VariableStoreHeader = (VARIABLE_STORE_HEADER*)((UINTN)FwVolHeader + FwVolHeader->HeaderLength); // Check the Variable Store Guid if( CompareGuid (&VariableStoreHeader->Signature, &gEfiVariableGuid) == FALSE ) { DEBUG ((EFI_D_ERROR, "ValidateFvHeader: Variable Store Guid non-compatible\n")); return EFI_NOT_FOUND; } VariableStoreLength = PcdGet32 (PcdFlashNvStorageVariableSize) - FwVolHeader->HeaderLength; if (VariableStoreHeader->Size != VariableStoreLength) { DEBUG ((EFI_D_ERROR, "ValidateFvHeader: Variable Store Length does not match\n")); return EFI_NOT_FOUND; } return EFI_SUCCESS; } EFI_STATUS InitNonVolatileVariableStore ( IN BLOCK_VARIABLE_INSTANCE *Instance, IN VOID *Headers, IN UINTN HeadersLength ) { EFI_FIRMWARE_VOLUME_HEADER *FirmwareVolumeHeader; EFI_STATUS Status; VARIABLE_STORE_HEADER *VariableStoreHeader; // Check if the size of the area is at least one block size ASSERT((PcdGet32(PcdFlashNvStorageVariableSize) > 0) && (PcdGet32(PcdFlashNvStorageVariableSize) / Instance->BlockIoProtocol->Media->BlockSize > 0)); ASSERT((PcdGet32(PcdFlashNvStorageFtwWorkingSize) > 0) && (PcdGet32(PcdFlashNvStorageFtwWorkingSize) / Instance->BlockIoProtocol->Media->BlockSize > 0)); ASSERT((PcdGet32(PcdFlashNvStorageFtwSpareSize) > 0) && (PcdGet32(PcdFlashNvStorageFtwSpareSize) / Instance->BlockIoProtocol->Media->BlockSize > 0)); // // EFI_FIRMWARE_VOLUME_HEADER // FirmwareVolumeHeader = (EFI_FIRMWARE_VOLUME_HEADER *)Headers; CopyGuid (&FirmwareVolumeHeader->FileSystemGuid, &gEfiSystemNvDataFvGuid); FirmwareVolumeHeader->FvLength = PcdGet32(PcdFlashNvStorageVariableSize) + PcdGet32(PcdFlashNvStorageFtwWorkingSize) + PcdGet32(PcdFlashNvStorageFtwSpareSize); FirmwareVolumeHeader->Signature = EFI_FVH_SIGNATURE; FirmwareVolumeHeader->Attributes = (EFI_FVB_ATTRIBUTES_2) ( EFI_FVB2_READ_ENABLED_CAP | // Reads may be enabled EFI_FVB2_READ_STATUS | // Reads are currently enabled EFI_FVB2_STICKY_WRITE | // A block erase is required to flip bits into EFI_FVB2_ERASE_POLARITY EFI_FVB2_MEMORY_MAPPED | // It is memory mapped EFI_FVB2_ERASE_POLARITY | // After erasure all bits take this value (i.e. '1') EFI_FVB2_WRITE_STATUS | // Writes are currently enabled EFI_FVB2_WRITE_ENABLED_CAP // Writes may be enabled ); FirmwareVolumeHeader->HeaderLength = sizeof(EFI_FIRMWARE_VOLUME_HEADER) + sizeof(EFI_FV_BLOCK_MAP_ENTRY); FirmwareVolumeHeader->Revision = EFI_FVH_REVISION; FirmwareVolumeHeader->BlockMap[0].NumBlocks = PcdGet32 (PcdNvStorageVariableBlockCount); FirmwareVolumeHeader->BlockMap[0].Length = Instance->BlockIoProtocol->Media->BlockSize; // BlockMap Terminator FirmwareVolumeHeader->BlockMap[1].NumBlocks = 0; FirmwareVolumeHeader->BlockMap[1].Length = 0; FirmwareVolumeHeader->Checksum = 0; FirmwareVolumeHeader->Checksum = CalculateSum16 ((UINT16*)FirmwareVolumeHeader, FirmwareVolumeHeader->HeaderLength); // // VARIABLE_STORE_HEADER // VariableStoreHeader = (VARIABLE_STORE_HEADER*)((UINTN)FirmwareVolumeHeader + FirmwareVolumeHeader->HeaderLength); CopyGuid (&VariableStoreHeader->Signature, &gEfiVariableGuid); VariableStoreHeader->Size = PcdGet32(PcdFlashNvStorageVariableSize) - FirmwareVolumeHeader->HeaderLength; VariableStoreHeader->Format = VARIABLE_STORE_FORMATTED; VariableStoreHeader->State = VARIABLE_STORE_HEALTHY; Status = FvbWrite (&Instance->FvbProtocol, 0, 0, &HeadersLength, Headers); return Status; } EFI_STATUS BlockVariableDxeInitialize ( IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable ) { EFI_HANDLE Handle; EFI_STATUS Status; BLOCK_VARIABLE_INSTANCE *Instance = &mBlockVariableInstance; UINT32 Count; EFI_LBA Lba; UINTN NvStorageSize; EFI_DEVICE_PATH_PROTOCOL *NvBlockDevicePath; UINT8 *NvStorageData; VOID *Headers; UINTN HeadersLength; Instance->Signature = BLOCK_VARIABLE_SIGNATURE; HeadersLength = sizeof(EFI_FIRMWARE_VOLUME_HEADER) + sizeof(EFI_FV_BLOCK_MAP_ENTRY) + sizeof(VARIABLE_STORE_HEADER); Headers = AllocateZeroPool(HeadersLength); if (Headers == NULL) { DEBUG ((EFI_D_ERROR, "%a: failed to allocate memory of Headers\n", __func__)); return EFI_OUT_OF_RESOURCES; } Lba = (EFI_LBA) PcdGet32 (PcdNvStorageVariableBlockLba); Count = PcdGet32 (PcdNvStorageVariableBlockCount); Instance->Media.BlockSize = PcdGet32 (PcdNvStorageVariableBlockSize); NvStorageSize = Count * Instance->Media.BlockSize; Instance->StartLba = Lba; HeadersLength = sizeof(EFI_FIRMWARE_VOLUME_HEADER) + sizeof(EFI_FV_BLOCK_MAP_ENTRY) + sizeof(VARIABLE_STORE_HEADER); if (NvStorageSize < HeadersLength) { return EFI_BAD_BUFFER_SIZE; } NvStorageData = (UINT8 *) (UINTN) PcdGet32(PcdFlashNvStorageVariableBase); mMapNvStorageVariableBase = PcdGet32(PcdFlashNvStorageVariableBase); NvBlockDevicePath = &Instance->DevicePath; NvBlockDevicePath = ConvertTextToDevicePath ((CHAR16*)FixedPcdGetPtr (PcdNvStorageVariableBlockDevicePath)); Status = gBS->LocateDevicePath (&gEfiBlockIoProtocolGuid, &NvBlockDevicePath, &Instance->Handle); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_ERROR, "Warning: Couldn't locate NVM device (status: %r)\n", Status)); return EFI_INVALID_PARAMETER; } Status = gBS->OpenProtocol ( Instance->Handle, &gEfiBlockIoProtocolGuid, (VOID **) &Instance->BlockIoProtocol, gImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL ); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_ERROR, "Warning: Couldn't open NVM device (status: %r)\n", Status)); return EFI_DEVICE_ERROR; } WriteBackDataCacheRange (Instance, sizeof(BLOCK_VARIABLE_INSTANCE)); Handle = NULL; Status = gBS->InstallMultipleProtocolInterfaces ( &Handle, &gEfiFirmwareVolumeBlockProtocolGuid, &Instance->FvbProtocol, NULL ); if (EFI_ERROR (Status)) { goto exit; } Status = FvbRead (&Instance->FvbProtocol, 0, 0, &HeadersLength, Headers); if (EFI_ERROR (Status)) { return Status; } Status = ValidateFvHeader (Headers); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_ERROR, "%a, Found invalid Fv Header\n", __func__)); // Erase all the block device that is reserved for variable storage Status = FvbEraseBlocks (&Instance->FvbProtocol, (EFI_LBA)0, Count, EFI_LBA_LIST_TERMINATOR); if (EFI_ERROR (Status)) { goto exit; } Status = InitNonVolatileVariableStore (Instance, Headers, HeadersLength); if (EFI_ERROR (Status)) { goto exit; } } if (NvStorageSize > ((EFI_FIRMWARE_VOLUME_HEADER*)Headers)->FvLength) { NvStorageSize = ((EFI_FIRMWARE_VOLUME_HEADER*)Headers)->FvLength; NvStorageSize = ((NvStorageSize + Instance->Media.BlockSize - 1) / Instance->Media.BlockSize) * Instance->Media.BlockSize; } Status = FvbRead (&Instance->FvbProtocol, 0, 0, &NvStorageSize, NvStorageData); exit: return Status; }