/** @file | |
* | |
* Copyright (c) 2011-2015, ARM Limited. All rights reserved. | |
* Copyright (c) 2015, Hisilicon Limited. All rights reserved. | |
* Copyright (c) 2015, Linaro 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. | |
* | |
* Based on files under ArmPlatformPkg/Drivers/NorFlashDxe/ | |
**/ | |
#include "FlashFvbDxe.h" | |
STATIC EFI_EVENT mFlashFvbVirtualAddrChangeEvent; | |
STATIC UINTN mFlashNvStorageVariableBase; | |
// | |
// Global variable declarations | |
// | |
FLASH_DESCRIPTION mFlashDevices[FLASH_DEVICE_COUNT] = | |
{ | |
{ | |
// UEFI Variable Services non-volatile storage | |
0xa4000000, | |
FixedPcdGet32(PcdFlashNvStorageVariableBase), | |
0x20000, | |
SIZE_64KB, | |
{0xCC2CBF29, 0x1498, 0x4CDD, {0x81, 0x71, 0xF8, 0xB6, 0xB4, 0x1D, 0x09, 0x09}} | |
} | |
}; | |
FLASH_INSTANCE** mFlashInstances; | |
FLASH_INSTANCE mFlashInstanceTemplate = | |
{ | |
FLASH_SIGNATURE, // Signature | |
NULL, // Handle ... NEED TO BE FILLED | |
FALSE, // Initialized | |
NULL, // Initialize | |
0, // DeviceBaseAddress ... NEED TO BE FILLED | |
0, // RegionBaseAddress ... NEED TO BE FILLED | |
0, // Size ... NEED TO BE FILLED | |
0, // StartLba | |
{ | |
EFI_BLOCK_IO_PROTOCOL_REVISION2, // Revision | |
NULL, // Media ... NEED TO BE FILLED | |
NULL, //NorFlashBlockIoReset | |
FlashBlockIoReadBlocks, | |
FlashBlockIoWriteBlocks, | |
FlashBlockIoFlushBlocks | |
}, // BlockIoProtocol | |
{ | |
0, // MediaId ... NEED TO BE FILLED | |
FALSE, // RemovableMedia | |
TRUE, // MediaPresent | |
FALSE, // LogicalPartition | |
FALSE, // ReadOnly | |
FALSE, // WriteCaching; | |
SIZE_64KB, // BlockSize ... NEED TO BE FILLED | |
4, // IoAlign | |
0, // LastBlock ... NEED TO BE FILLED | |
0, // LowestAlignedLba | |
1, // LogicalBlocksPerPhysicalBlock | |
}, //Media; | |
FALSE, // SupportFvb ... NEED TO BE FILLED | |
{ | |
FvbGetAttributes, | |
FvbSetAttributes, | |
FvbGetPhysicalAddress, | |
FvbGetBlockSize, | |
FvbRead, | |
FvbWrite, | |
FvbEraseBlocks, | |
NULL, //ParentHandle | |
}, // FvbProtoccol; | |
{ | |
{ | |
{ | |
HARDWARE_DEVICE_PATH, | |
HW_VENDOR_DP, | |
{(UINT8)(sizeof(VENDOR_DEVICE_PATH)), | |
(UINT8)((sizeof(VENDOR_DEVICE_PATH)) >> 8)}, | |
}, | |
{ 0x0, 0x0, 0x0, {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }}, // GUID ... NEED TO BE FILLED | |
}, | |
{ | |
END_DEVICE_PATH_TYPE, | |
END_ENTIRE_DEVICE_PATH_SUBTYPE, | |
{sizeof (EFI_DEVICE_PATH_PROTOCOL), | |
0} | |
} | |
} // DevicePath | |
}; | |
HISI_SPI_FLASH_PROTOCOL* mFlash; | |
/// | |
/// The Firmware Volume Block Protocol is the low-level interface | |
/// to a firmware volume. File-level access to a firmware volume | |
/// should not be done using the Firmware Volume Block Protocol. | |
/// Normal access to a firmware volume must use the Firmware | |
/// Volume Protocol. Typically, only the file system driver that | |
/// produces the Firmware Volume Protocol will bind to the | |
/// Firmware Volume Block Protocol. | |
/// | |
/** | |
Initialises the FV Header and Variable Store Header | |
to support variable operations. | |
@param[in] Ptr - Location to initialise the headers | |
**/ | |
EFI_STATUS | |
InitializeFvAndVariableStoreHeaders ( | |
IN FLASH_INSTANCE* Instance | |
) | |
{ | |
EFI_STATUS Status; | |
VOID* Headers; | |
UINTN HeadersLength; | |
EFI_FIRMWARE_VOLUME_HEADER* FirmwareVolumeHeader; | |
VARIABLE_STORE_HEADER* VariableStoreHeader; | |
if (!Instance->Initialized && Instance->Initialize) | |
{ | |
Instance->Initialize (Instance); | |
} | |
HeadersLength = sizeof(EFI_FIRMWARE_VOLUME_HEADER) + sizeof(EFI_FV_BLOCK_MAP_ENTRY) + sizeof(VARIABLE_STORE_HEADER); | |
Headers = AllocateZeroPool(HeadersLength); | |
// FirmwareVolumeHeader->FvLength is declared to have the Variable area AND the FTW working area AND the FTW Spare contiguous. | |
ASSERT(PcdGet32(PcdFlashNvStorageVariableBase) + PcdGet32(PcdFlashNvStorageVariableSize) == PcdGet32(PcdFlashNvStorageFtwWorkingBase)); | |
ASSERT(PcdGet32(PcdFlashNvStorageFtwWorkingBase) + PcdGet32(PcdFlashNvStorageFtwWorkingSize) == PcdGet32(PcdFlashNvStorageFtwSpareBase)); | |
// Check if the size of the area is at least one block size | |
ASSERT((PcdGet32(PcdFlashNvStorageVariableSize) > 0) && ((UINT32)PcdGet32(PcdFlashNvStorageVariableSize) / Instance->Media.BlockSize > 0)); | |
ASSERT((PcdGet32(PcdFlashNvStorageFtwWorkingSize) > 0) && ((UINT32)PcdGet32(PcdFlashNvStorageFtwWorkingSize) / Instance->Media.BlockSize > 0)); | |
ASSERT((PcdGet32(PcdFlashNvStorageFtwSpareSize) > 0) && ((UINT32)PcdGet32(PcdFlashNvStorageFtwSpareSize) / Instance->Media.BlockSize > 0)); | |
// Ensure the Variable area Base Addresses are aligned on a block size boundaries | |
ASSERT((UINT32)PcdGet32(PcdFlashNvStorageVariableBase) % Instance->Media.BlockSize == 0); | |
ASSERT((UINT32)PcdGet32(PcdFlashNvStorageFtwWorkingBase) % Instance->Media.BlockSize == 0); | |
ASSERT((UINT32)PcdGet32(PcdFlashNvStorageFtwSpareBase) % Instance->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 = Instance->Media.LastBlock + 1; | |
FirmwareVolumeHeader->BlockMap[0].Length = Instance->Media.BlockSize; | |
FirmwareVolumeHeader->BlockMap[1].NumBlocks = 0; | |
FirmwareVolumeHeader->BlockMap[1].Length = 0; | |
FirmwareVolumeHeader->Checksum = CalculateCheckSum16 ((UINT16*)FirmwareVolumeHeader, FirmwareVolumeHeader->HeaderLength); | |
// | |
// VARIABLE_STORE_HEADER | |
// | |
VariableStoreHeader = (VARIABLE_STORE_HEADER*)((UINTN)Headers + (UINTN)FirmwareVolumeHeader->HeaderLength); | |
CopyGuid (&VariableStoreHeader->Signature, &gEfiVariableGuid); | |
VariableStoreHeader->Size = PcdGet32(PcdFlashNvStorageVariableSize) - FirmwareVolumeHeader->HeaderLength; | |
VariableStoreHeader->Format = VARIABLE_STORE_FORMATTED; | |
VariableStoreHeader->State = VARIABLE_STORE_HEALTHY; | |
// Install the combined super-header in the NorFlash | |
Status = FvbWrite (&Instance->FvbProtocol, 0, 0, &HeadersLength, Headers); | |
FreePool (Headers); | |
return Status; | |
} | |
/** | |
Check the integrity of firmware volume header. | |
@param[in] FwVolHeader - A pointer to a firmware volume header | |
@retval EFI_SUCCESS - The firmware volume is consistent | |
@retval EFI_NOT_FOUND - The firmware volume has been corrupted. | |
**/ | |
EFI_STATUS | |
ValidateFvHeader ( | |
IN FLASH_INSTANCE* Instance | |
) | |
{ | |
UINT16 Checksum; | |
EFI_FIRMWARE_VOLUME_HEADER* FwVolHeader; | |
VARIABLE_STORE_HEADER* VariableStoreHeader; | |
UINTN VariableStoreLength; | |
UINTN FvLength; | |
FwVolHeader = (EFI_FIRMWARE_VOLUME_HEADER*)Instance->RegionBaseAddress; | |
FvLength = 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 | |
Checksum = CalculateSum16((UINT16*)FwVolHeader, FwVolHeader->HeaderLength); | |
if (Checksum != 0) | |
{ | |
DEBUG ((EFI_D_ERROR, "ValidateFvHeader: FV checksum is invalid (Checksum:0x%X)\n", Checksum)); | |
return EFI_NOT_FOUND; | |
} | |
VariableStoreHeader = (VARIABLE_STORE_HEADER*)((UINTN)FwVolHeader + (UINTN)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; | |
} | |
/** | |
The FvbGetAttributes() function retrieves the attributes and | |
current settings of the block. | |
@param This Indicates the EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL instance. | |
@param Attributes Pointer to EFI_FVB_ATTRIBUTES_2 in which the attributes and | |
current settings are returned. | |
Type EFI_FVB_ATTRIBUTES_2 is defined in EFI_FIRMWARE_VOLUME_HEADER. | |
@retval EFI_SUCCESS The firmware volume attributes were returned. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
FvbGetAttributes( | |
IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL* This, | |
OUT EFI_FVB_ATTRIBUTES_2* Attributes | |
) | |
{ | |
EFI_FVB_ATTRIBUTES_2 FlashFvbAttributes; | |
FLASH_INSTANCE* Instance; | |
Instance = INSTANCE_FROM_FVB_THIS(This); | |
FlashFvbAttributes = (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') | |
); | |
// Check if it is write protected | |
if (Instance->Media.ReadOnly != TRUE) | |
{ | |
FlashFvbAttributes = FlashFvbAttributes | | |
EFI_FVB2_WRITE_STATUS | // Writes are currently enabled | |
EFI_FVB2_WRITE_ENABLED_CAP; // Writes may be enabled | |
} | |
*Attributes = FlashFvbAttributes; | |
return EFI_SUCCESS; | |
} | |
/** | |
The FvbSetAttributes() function sets configurable firmware volume attributes | |
and returns the new settings of the firmware volume. | |
@param This Indicates the EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL instance. | |
@param Attributes On input, Attributes is a pointer to EFI_FVB_ATTRIBUTES_2 | |
that contains the desired firmware volume settings. | |
On successful return, it contains the new settings of | |
the firmware volume. | |
Type EFI_FVB_ATTRIBUTES_2 is defined in EFI_FIRMWARE_VOLUME_HEADER. | |
@retval EFI_SUCCESS The firmware volume attributes were returned. | |
@retval EFI_INVALID_PARAMETER The attributes requested are in conflict with the capabilities | |
as declared in the firmware volume header. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
FvbSetAttributes( | |
IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL* This, | |
IN OUT EFI_FVB_ATTRIBUTES_2* Attributes | |
) | |
{ | |
DEBUG ((EFI_D_ERROR, "FvbSetAttributes(0x%X) is not supported\n", *Attributes)); | |
return EFI_UNSUPPORTED; | |
} | |
/** | |
The GetPhysicalAddress() function retrieves the base address of | |
a memory-mapped firmware volume. This function should be called | |
only for memory-mapped firmware volumes. | |
@param This Indicates the EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL instance. | |
@param Address Pointer to a caller-allocated | |
EFI_PHYSICAL_ADDRESS that, on successful | |
return from GetPhysicalAddress(), contains the | |
base address of the firmware volume. | |
@retval EFI_SUCCESS The firmware volume base address was returned. | |
@retval EFI_NOT_SUPPORTED The firmware volume is not memory mapped. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
FvbGetPhysicalAddress ( | |
IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL* This, | |
OUT EFI_PHYSICAL_ADDRESS* Address | |
) | |
{ | |
if(NULL == Address) | |
{ | |
return EFI_UNSUPPORTED; | |
}; | |
*Address = mFlashNvStorageVariableBase; | |
return EFI_SUCCESS; | |
} | |
/** | |
The GetBlockSize() function retrieves the size of the requested | |
block. It also returns the number of additional blocks with | |
the identical size. The GetBlockSize() function is used to | |
retrieve the block map (see EFI_FIRMWARE_VOLUME_HEADER). | |
@param This Indicates the EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL instance. | |
@param Lba Indicates the block for which to return the size. | |
@param BlockSize Pointer to a caller-allocated UINTN in which | |
the size of the block is returned. | |
@param NumberOfBlocks Pointer to a caller-allocated UINTN in | |
which the number of consecutive blocks, | |
starting with Lba, is returned. All | |
blocks in this range have a size of | |
BlockSize. | |
@retval EFI_SUCCESS The firmware volume base address was returned. | |
@retval EFI_INVALID_PARAMETER The requested LBA is out of range. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
FvbGetBlockSize ( | |
IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL* This, | |
IN EFI_LBA Lba, | |
OUT UINTN* BlockSize, | |
OUT UINTN* NumberOfBlocks | |
) | |
{ | |
EFI_STATUS Status; | |
FLASH_INSTANCE* Instance; | |
Instance = INSTANCE_FROM_FVB_THIS(This); | |
if (Lba > Instance->Media.LastBlock) | |
{ | |
Status = EFI_INVALID_PARAMETER; | |
} | |
else | |
{ | |
// This is easy because in this platform each NorFlash device has equal sized blocks. | |
*BlockSize = (UINTN) Instance->Media.BlockSize; | |
*NumberOfBlocks = (UINTN) (Instance->Media.LastBlock - Lba + 1); | |
Status = EFI_SUCCESS; | |
} | |
return Status; | |
} | |
/** | |
Reads the specified number of bytes into a buffer from the specified block. | |
The Read() function reads the requested number of bytes from the | |
requested block and stores them in the provided buffer. | |
Implementations should be mindful that the firmware volume | |
might be in the ReadDisabled state. If it is in this state, | |
the Read() function must return the status code | |
EFI_ACCESS_DENIED without modifying the contents of the | |
buffer. The Read() function must also prevent spanning block | |
boundaries. If a read is requested that would span a block | |
boundary, the read must read up to the boundary but not | |
beyond. The output parameter NumBytes must be set to correctly | |
indicate the number of bytes actually read. The caller must be | |
aware that a read may be partially completed. | |
@param This Indicates the EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL instance. | |
@param Lba The starting logical block index from which to read. | |
@param Offset Offset into the block at which to begin reading. | |
@param NumBytes Pointer to a UINTN. | |
At entry, *NumBytes contains the total size of the buffer. | |
At exit, *NumBytes contains the total number of bytes read. | |
@param Buffer Pointer to a caller-allocated buffer that will be used | |
to hold the data that is read. | |
@retval EFI_SUCCESS The firmware volume was read successfully, and contents are | |
in Buffer. | |
@retval EFI_BAD_BUFFER_SIZE Read attempted across an LBA boundary. | |
On output, NumBytes contains the total number of bytes | |
returned in Buffer. | |
@retval EFI_ACCESS_DENIED The firmware volume is in the ReadDisabled state. | |
@retval EFI_DEVICE_ERROR The block device is not functioning correctly and could not be read. | |
**/ | |
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 | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN BlockSize; | |
FLASH_INSTANCE* Instance; | |
UINTN StartAddress; | |
UINTN ReadAddress; | |
Instance = INSTANCE_FROM_FVB_THIS(This); | |
if (!Instance->Initialized && Instance->Initialize) | |
{ | |
if (EfiAtRuntime ()) { | |
DEBUG ((EFI_D_ERROR, "[%a]:[%dL] Initialize at runtime is not supported!\n", __FUNCTION__, __LINE__)); | |
return EFI_UNSUPPORTED; | |
} | |
Instance->Initialize(Instance); | |
} | |
Status = EFI_SUCCESS; | |
// Cache the block size to avoid de-referencing pointers all the time | |
BlockSize = Instance->Media.BlockSize; | |
// The read must not span block boundaries. | |
// We need to check each variable individually because adding two large values together overflows. | |
if ((Offset >= BlockSize) || | |
(*NumBytes > BlockSize) || | |
((Offset + *NumBytes) > BlockSize)) | |
{ | |
DEBUG ((EFI_D_ERROR, "[%a]:[%dL] ERROR - EFI_BAD_BUFFER_SIZE: (Offset=0x%x + NumBytes=0x%x) > BlockSize=0x%x\n", __FUNCTION__, __LINE__, Offset, *NumBytes, BlockSize )); | |
return EFI_BAD_BUFFER_SIZE; | |
} | |
// We must have some bytes to read | |
if (*NumBytes == 0) | |
{ | |
return EFI_BAD_BUFFER_SIZE; | |
} | |
// Get the address to start reading from | |
StartAddress = GET_BLOCK_ADDRESS (Instance->RegionBaseAddress, | |
Lba, | |
BlockSize | |
); | |
ReadAddress = StartAddress - Instance->DeviceBaseAddress + Offset; | |
Status = mFlash->Read(mFlash, (UINT32)ReadAddress, Buffer, *NumBytes); | |
if (EFI_SUCCESS != Status) | |
{ | |
// Return one of the pre-approved error statuses | |
Status = EFI_DEVICE_ERROR; | |
return Status; | |
} | |
return Status; | |
} | |
/** | |
Writes the specified number of bytes from the input buffer to the block. | |
The Write() function writes the specified number of bytes from | |
the provided buffer to the specified block and offset. If the | |
firmware volume is sticky write, the caller must ensure that | |
all the bits of the specified range to write are in the | |
EFI_FVB_ERASE_POLARITY state before calling the Write() | |
function, or else the result will be unpredictable. This | |
unpredictability arises because, for a sticky-write firmware | |
volume, a write may negate a bit in the EFI_FVB_ERASE_POLARITY | |
state but cannot flip it back again. Before calling the | |
Write() function, it is recommended for the caller to first call | |
the EraseBlocks() function to erase the specified block to | |
write. A block erase cycle will transition bits from the | |
(NOT)EFI_FVB_ERASE_POLARITY state back to the | |
EFI_FVB_ERASE_POLARITY state. Implementations should be | |
mindful that the firmware volume might be in the WriteDisabled | |
state. If it is in this state, the Write() function must | |
return the status code EFI_ACCESS_DENIED without modifying the | |
contents of the firmware volume. The Write() function must | |
also prevent spanning block boundaries. If a write is | |
requested that spans a block boundary, the write must store up | |
to the boundary but not beyond. The output parameter NumBytes | |
must be set to correctly indicate the number of bytes actually | |
written. The caller must be aware that a write may be | |
partially completed. All writes, partial or otherwise, must be | |
fully flushed to the hardware before the Write() service | |
returns. | |
@param This Indicates the EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL instance. | |
@param Lba The starting logical block index to write to. | |
@param Offset Offset into the block at which to begin writing. | |
@param NumBytes The pointer to a UINTN. | |
At entry, *NumBytes contains the total size of the buffer. | |
At exit, *NumBytes contains the total number of bytes actually written. | |
@param Buffer The pointer to a caller-allocated buffer that contains the source for the write. | |
@retval EFI_SUCCESS The firmware volume was written successfully. | |
@retval EFI_BAD_BUFFER_SIZE The write was attempted across an LBA boundary. | |
On output, NumBytes contains the total number of bytes | |
actually written. | |
@retval EFI_ACCESS_DENIED The firmware volume is in the WriteDisabled state. | |
@retval EFI_DEVICE_ERROR The block device is malfunctioning and could not be written. | |
**/ | |
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 | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN BlockSize; | |
FLASH_INSTANCE* Instance; | |
UINTN BlockAddress; | |
UINTN WriteAddress; | |
Instance = INSTANCE_FROM_FVB_THIS(This); | |
if (NULL == Instance) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
if (!Instance->Initialized && Instance->Initialize) | |
{ | |
if (EfiAtRuntime ()) { | |
DEBUG ((EFI_D_ERROR, "[%a]:[%dL] Initialize at runtime is not supported!\n", __FUNCTION__, __LINE__)); | |
return EFI_UNSUPPORTED; | |
} | |
Instance->Initialize(Instance); | |
} | |
Status = EFI_SUCCESS; | |
// Detect WriteDisabled state | |
if (Instance->Media.ReadOnly == TRUE) | |
{ | |
DEBUG ((EFI_D_ERROR, "FvbWrite: ERROR - Can not write: Device is in WriteDisabled state.\n")); | |
// It is in WriteDisabled state, return an error right away | |
return EFI_ACCESS_DENIED; | |
} | |
// Cache the block size to avoid de-referencing pointers all the time | |
BlockSize = Instance->Media.BlockSize; | |
// The write must not span block boundaries. | |
// We need to check each variable individually because adding two large values together overflows. | |
if ( ( Offset >= BlockSize ) || | |
( *NumBytes > BlockSize ) || | |
( (Offset + *NumBytes) > BlockSize ) ) | |
{ | |
DEBUG ((EFI_D_ERROR, "FvbWrite: ERROR - EFI_BAD_BUFFER_SIZE: (Offset=0x%x + NumBytes=0x%x) > BlockSize=0x%x\n", Offset, *NumBytes, BlockSize )); | |
return EFI_BAD_BUFFER_SIZE; | |
} | |
// We must have some bytes to write | |
if (*NumBytes == 0) | |
{ | |
DEBUG ((EFI_D_ERROR, "FvbWrite: ERROR - EFI_BAD_BUFFER_SIZE: (Offset=0x%x + NumBytes=0x%x) > BlockSize=0x%x\n", Offset, *NumBytes, BlockSize )); | |
return EFI_BAD_BUFFER_SIZE; | |
} | |
BlockAddress = GET_BLOCK_ADDRESS (Instance->RegionBaseAddress, Lba, BlockSize); | |
WriteAddress = BlockAddress - Instance->DeviceBaseAddress + Offset; | |
Status = mFlash->Write(mFlash, (UINT32)WriteAddress, (UINT8*)Buffer, *NumBytes); | |
if (EFI_SUCCESS != Status) | |
{ | |
DEBUG((EFI_D_ERROR, "%s - %d Status=%r\n", __FILE__, __LINE__, Status)); | |
return Status; | |
} | |
return Status; | |
} | |
/** | |
Erases and initialises a firmware volume block. | |
The EraseBlocks() function erases one or more blocks as denoted | |
by the variable argument list. The entire parameter list of | |
blocks must be verified before erasing any blocks. If a block is | |
requested that does not exist within the associated firmware | |
volume (it has a larger index than the last block of the | |
firmware volume), the EraseBlocks() function must return the | |
status code EFI_INVALID_PARAMETER without modifying the contents | |
of the firmware volume. Implementations should be mindful that | |
the firmware volume might be in the WriteDisabled state. If it | |
is in this state, the EraseBlocks() function must return the | |
status code EFI_ACCESS_DENIED without modifying the contents of | |
the firmware volume. All calls to EraseBlocks() must be fully | |
flushed to the hardware before the EraseBlocks() service | |
returns. | |
@param This Indicates the EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL | |
instance. | |
@param ... The variable argument list is a list of tuples. | |
Each tuple describes a range of LBAs to erase | |
and consists of the following: | |
- An EFI_LBA that indicates the starting LBA | |
- A UINTN that indicates the number of blocks to erase. | |
The list is terminated with an EFI_LBA_LIST_TERMINATOR. | |
For example, the following indicates that two ranges of blocks | |
(5-7 and 10-11) are to be erased: | |
EraseBlocks (This, 5, 3, 10, 2, EFI_LBA_LIST_TERMINATOR); | |
@retval EFI_SUCCESS The erase request 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 | |
EFIAPI | |
FvbEraseBlocks ( | |
IN CONST EFI_FIRMWARE_VOLUME_BLOCK2_PROTOCOL* This, | |
... | |
) | |
{ | |
EFI_STATUS Status; | |
VA_LIST Args; | |
UINTN BlockAddress; // Physical address of Lba to erase | |
EFI_LBA StartingLba; // Lba from which we start erasing | |
UINTN NumOfLba; // Number of Lba blocks to erase | |
FLASH_INSTANCE* Instance; | |
Instance = INSTANCE_FROM_FVB_THIS(This); | |
Status = EFI_SUCCESS; | |
// Detect WriteDisabled state | |
if (Instance->Media.ReadOnly == TRUE) | |
{ | |
// Firmware volume is in WriteDisabled state | |
return EFI_ACCESS_DENIED; | |
} | |
// Before erasing, check the entire list of parameters to ensure all specified blocks are valid | |
VA_START (Args, This); | |
do | |
{ | |
// Get the Lba from which we start erasing | |
StartingLba = VA_ARG (Args, EFI_LBA); | |
// Have we reached the end of the list? | |
if (StartingLba == EFI_LBA_LIST_TERMINATOR) | |
{ | |
//Exit the while loop | |
break; | |
} | |
// How many Lba blocks are we requested to erase? | |
NumOfLba = VA_ARG (Args, UINT32); | |
// All blocks must be within range | |
if ((NumOfLba == 0) || ((Instance->StartLba + StartingLba + NumOfLba - 1) > Instance->Media.LastBlock)) | |
{ | |
VA_END (Args); | |
Status = EFI_INVALID_PARAMETER; | |
goto EXIT; | |
} | |
} | |
while (TRUE); | |
VA_END (Args); | |
// | |
// To get here, all must be ok, so start erasing | |
// | |
VA_START (Args, This); | |
do | |
{ | |
// Get the Lba from which we start erasing | |
StartingLba = VA_ARG (Args, EFI_LBA); | |
// Have we reached the end of the list? | |
if (StartingLba == EFI_LBA_LIST_TERMINATOR) | |
{ | |
// Exit the while loop | |
break; | |
} | |
// How many Lba blocks are we requested to erase? | |
NumOfLba = VA_ARG (Args, UINT32); | |
// Go through each one and erase it | |
while (NumOfLba > 0) | |
{ | |
// Get the physical address of Lba to erase | |
BlockAddress = GET_BLOCK_ADDRESS ( | |
Instance->RegionBaseAddress, | |
Instance->StartLba + StartingLba, | |
Instance->Media.BlockSize | |
); | |
// Erase it | |
Status = FlashUnlockAndEraseSingleBlock (Instance, BlockAddress); | |
if (EFI_ERROR(Status)) | |
{ | |
VA_END (Args); | |
Status = EFI_DEVICE_ERROR; | |
goto EXIT; | |
} | |
// Move to the next Lba | |
StartingLba++; | |
NumOfLba--; | |
} | |
} | |
while (TRUE); | |
VA_END (Args); | |
EXIT: | |
return Status; | |
} | |
EFI_STATUS | |
EFIAPI | |
FvbInitialize ( | |
IN FLASH_INSTANCE* Instance | |
) | |
{ | |
EFI_STATUS Status; | |
UINT32 FvbNumLba; | |
Instance->Initialized = TRUE; | |
mFlashNvStorageVariableBase = FixedPcdGet32 (PcdFlashNvStorageVariableBase); | |
// Set the index of the first LBA for the FVB | |
Instance->StartLba = (PcdGet32 (PcdFlashNvStorageVariableBase) - Instance->RegionBaseAddress) / Instance->Media.BlockSize; | |
// Determine if there is a valid header at the beginning of the Flash | |
Status = ValidateFvHeader (Instance); | |
if (EFI_ERROR(Status)) | |
{ | |
// There is no valid header, so time to install one. | |
// Erase all the Flash that is reserved for variable storage | |
FvbNumLba = (PcdGet32(PcdFlashNvStorageVariableSize) + PcdGet32(PcdFlashNvStorageFtwWorkingSize) + (UINT32)PcdGet32(PcdFlashNvStorageFtwSpareSize)) / Instance->Media.BlockSize; | |
Status = FvbEraseBlocks (&Instance->FvbProtocol, (EFI_LBA)0, FvbNumLba, EFI_LBA_LIST_TERMINATOR); | |
if (EFI_ERROR(Status)) | |
{ | |
return Status; | |
} | |
// Install all appropriate headers | |
Status = InitializeFvAndVariableStoreHeaders (Instance); | |
if (EFI_ERROR(Status)) | |
{ | |
return Status; | |
} | |
} | |
return Status; | |
} | |
EFI_STATUS | |
FlashPlatformGetDevices ( | |
OUT FLASH_DESCRIPTION** FlashDevices, | |
OUT UINT32* Count | |
) | |
{ | |
if ((FlashDevices == NULL) || (Count == NULL)) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
*FlashDevices = mFlashDevices; | |
*Count = FLASH_DEVICE_COUNT; | |
return EFI_SUCCESS; | |
} | |
EFI_STATUS | |
FlashCreateInstance ( | |
IN UINTN FlashDeviceBase, | |
IN UINTN FlashRegionBase, | |
IN UINTN FlashSize, | |
IN UINT32 MediaId, | |
IN UINT32 BlockSize, | |
IN BOOLEAN SupportFvb, | |
IN CONST GUID* FlashGuid, | |
OUT FLASH_INSTANCE** FlashInstance | |
) | |
{ | |
EFI_STATUS Status; | |
FLASH_INSTANCE* Instance; | |
if (FlashInstance == NULL) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
Instance = AllocateRuntimeCopyPool (sizeof(FLASH_INSTANCE), &mFlashInstanceTemplate); | |
if (Instance == NULL) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
Instance->DeviceBaseAddress = FlashDeviceBase; | |
Instance->RegionBaseAddress = FlashRegionBase; | |
Instance->Size = FlashSize; | |
Instance->BlockIoProtocol.Media = &Instance->Media; | |
Instance->Media.MediaId = MediaId; | |
Instance->Media.BlockSize = BlockSize; | |
Instance->Media.LastBlock = (FlashSize / BlockSize) - 1; | |
CopyGuid (&Instance->DevicePath.Vendor.Guid, FlashGuid); | |
if (SupportFvb) | |
{ | |
Instance->SupportFvb = TRUE; | |
Instance->Initialize = FvbInitialize; | |
Status = gBS->InstallMultipleProtocolInterfaces ( | |
&Instance->Handle, | |
&gEfiDevicePathProtocolGuid, &Instance->DevicePath, | |
&gEfiBlockIoProtocolGuid, &Instance->BlockIoProtocol, | |
&gEfiFirmwareVolumeBlockProtocolGuid, &Instance->FvbProtocol, | |
NULL | |
); | |
if (EFI_ERROR(Status)) | |
{ | |
FreePool(Instance); | |
return Status; | |
} | |
} | |
else | |
{ | |
Instance->Initialized = TRUE; | |
Status = gBS->InstallMultipleProtocolInterfaces ( | |
&Instance->Handle, | |
&gEfiDevicePathProtocolGuid, &Instance->DevicePath, | |
&gEfiBlockIoProtocolGuid, &Instance->BlockIoProtocol, | |
NULL | |
); | |
if (EFI_ERROR(Status)) | |
{ | |
FreePool(Instance); | |
return Status; | |
} | |
} | |
*FlashInstance = Instance; | |
return Status; | |
} | |
EFI_STATUS | |
FlashUnlockSingleBlockIfNecessary ( | |
IN FLASH_INSTANCE* Instance, | |
IN UINTN BlockAddress | |
) | |
{ | |
return EFI_SUCCESS; | |
} | |
EFI_STATUS | |
FlashEraseSingleBlock ( | |
IN FLASH_INSTANCE* Instance, | |
IN UINTN BlockAddress | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN EraseAddress; | |
Status = EFI_SUCCESS; | |
EraseAddress = BlockAddress - Instance->DeviceBaseAddress; | |
Status = mFlash->Erase(mFlash, (UINT32)EraseAddress, Instance->Media.BlockSize); | |
if (EFI_SUCCESS != Status) | |
{ | |
DEBUG((EFI_D_ERROR, "%s - %d Status=%r\n", __FILE__, __LINE__, Status)); | |
return Status; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
* The following function presumes that the block has already been unlocked. | |
**/ | |
EFI_STATUS | |
FlashUnlockAndEraseSingleBlock ( | |
IN FLASH_INSTANCE* Instance, | |
IN UINTN BlockAddress | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN Index; | |
Index = 0; | |
// The block erase might fail a first time (SW bug ?). Retry it ... | |
do | |
{ | |
// Unlock the block if we have to | |
Status = FlashUnlockSingleBlockIfNecessary (Instance, BlockAddress); | |
if (!EFI_ERROR(Status)) | |
{ | |
Status = FlashEraseSingleBlock (Instance, BlockAddress); | |
} | |
Index++; | |
} | |
while ((Index < FLASH_ERASE_RETRY) && (Status == EFI_WRITE_PROTECTED)); | |
if (Index == FLASH_ERASE_RETRY) | |
{ | |
DEBUG((EFI_D_ERROR, "EraseSingleBlock(BlockAddress=0x%08x: Block Locked Error (try to erase %d times)\n", BlockAddress, Index)); | |
} | |
return Status; | |
} | |
EFI_STATUS | |
FlashWriteBlocks ( | |
IN FLASH_INSTANCE* Instance, | |
IN EFI_LBA Lba, | |
IN UINTN BufferSizeInBytes, | |
IN VOID* Buffer | |
) | |
{ | |
EFI_STATUS Status = EFI_SUCCESS; | |
UINTN BlockAddress; | |
UINT32 NumBlocks; | |
UINTN WriteAddress; | |
// The buffer must be valid | |
if (Buffer == NULL) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
if (Instance->Media.ReadOnly == TRUE) | |
{ | |
return EFI_WRITE_PROTECTED; | |
} | |
// We must have some bytes to read | |
if (BufferSizeInBytes == 0) | |
{ | |
return EFI_BAD_BUFFER_SIZE; | |
} | |
// The size of the buffer must be a multiple of the block size | |
if ((BufferSizeInBytes % Instance->Media.BlockSize) != 0) | |
{ | |
return EFI_BAD_BUFFER_SIZE; | |
} | |
// All blocks must be within the device | |
NumBlocks = ((UINT32)BufferSizeInBytes) / Instance->Media.BlockSize ; | |
if ((Lba + NumBlocks) > (Instance->Media.LastBlock + 1)) | |
{ | |
DEBUG((EFI_D_ERROR, "[%a]:[%dL]ERROR - Write will exceed last block.\n", __FUNCTION__, __LINE__ )); | |
return EFI_INVALID_PARAMETER; | |
} | |
BlockAddress = GET_BLOCK_ADDRESS (Instance->RegionBaseAddress, Lba, Instance->Media.BlockSize); | |
WriteAddress = BlockAddress - Instance->DeviceBaseAddress; | |
Status = mFlash->Write(mFlash, (UINT32)WriteAddress, (UINT8*)Buffer, BufferSizeInBytes); | |
if (EFI_SUCCESS != Status) | |
{ | |
DEBUG((EFI_D_ERROR, "%s - %d Status=%r\n", __FILE__, __LINE__, Status)); | |
return Status; | |
} | |
return Status; | |
} | |
EFI_STATUS | |
FlashReadBlocks ( | |
IN FLASH_INSTANCE* Instance, | |
IN EFI_LBA Lba, | |
IN UINTN BufferSizeInBytes, | |
OUT VOID* Buffer | |
) | |
{ | |
UINT32 NumBlocks; | |
UINTN StartAddress; | |
UINTN ReadAddress; | |
EFI_STATUS Status; | |
// The buffer must be valid | |
if (Buffer == NULL) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
// We must have some bytes to read | |
if (BufferSizeInBytes == 0) | |
{ | |
return EFI_BAD_BUFFER_SIZE; | |
} | |
// The size of the buffer must be a multiple of the block size | |
if ((BufferSizeInBytes % Instance->Media.BlockSize) != 0) | |
{ | |
return EFI_BAD_BUFFER_SIZE; | |
} | |
// All blocks must be within the device | |
NumBlocks = ((UINT32)BufferSizeInBytes) / Instance->Media.BlockSize ; | |
if ((Lba + NumBlocks) > (Instance->Media.LastBlock + 1)) | |
{ | |
DEBUG((EFI_D_ERROR, "FlashReadBlocks: ERROR - Read will exceed last block\n")); | |
return EFI_INVALID_PARAMETER; | |
} | |
// Get the address to start reading from | |
StartAddress = GET_BLOCK_ADDRESS (Instance->RegionBaseAddress, | |
Lba, | |
Instance->Media.BlockSize | |
); | |
ReadAddress = StartAddress - Instance->DeviceBaseAddress; | |
Status = mFlash->Read(mFlash, (UINT32)ReadAddress, Buffer, BufferSizeInBytes); | |
if (EFI_SUCCESS != Status) | |
{ | |
DEBUG((EFI_D_ERROR, "%s - %d Status=%r\n", __FILE__, __LINE__, Status)); | |
return Status; | |
} | |
return EFI_SUCCESS; | |
} | |
VOID | |
EFIAPI | |
FlashFvbVirtualNotifyEvent ( | |
IN EFI_EVENT Event, | |
IN VOID *Context | |
) | |
{ | |
EfiConvertPointer (0x0, (VOID**)&mFlash); | |
EfiConvertPointer (0x0, (VOID**)&mFlashNvStorageVariableBase); | |
return; | |
} | |
EFI_STATUS | |
EFIAPI | |
FlashFvbInitialize ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_SYSTEM_TABLE* SystemTable | |
) | |
{ | |
EFI_STATUS Status; | |
UINT32 Index; | |
FLASH_DESCRIPTION* FlashDevices; | |
UINT32 FlashDeviceCount; | |
BOOLEAN ContainVariableStorage; | |
Status = FlashPlatformGetDevices (&FlashDevices, &FlashDeviceCount); | |
if (EFI_ERROR(Status)) | |
{ | |
DEBUG((EFI_D_ERROR, "[%a]:[%dL] Fail to get Flash devices\n", __FUNCTION__, __LINE__)); | |
return Status; | |
} | |
mFlashInstances = AllocatePool ((UINT32)(sizeof(FLASH_INSTANCE*) * FlashDeviceCount)); | |
Status = gBS->LocateProtocol (&gHisiSpiFlashProtocolGuid, NULL, (VOID*) &mFlash); | |
if (EFI_ERROR(Status)) | |
{ | |
DEBUG((EFI_D_ERROR, "[%a]:[%dL] Status=%r\n", __FUNCTION__, __LINE__, Status)); | |
return Status; | |
} | |
for (Index = 0; Index < FlashDeviceCount; Index++) | |
{ | |
// Check if this Flash device contain the variable storage region | |
ContainVariableStorage = | |
(FlashDevices[Index].RegionBaseAddress <= (UINT32)PcdGet32 (PcdFlashNvStorageVariableBase)) && | |
((UINT32)(PcdGet32 (PcdFlashNvStorageVariableBase) + PcdGet32 (PcdFlashNvStorageVariableSize)) <= FlashDevices[Index].RegionBaseAddress + FlashDevices[Index].Size); | |
Status = FlashCreateInstance ( | |
FlashDevices[Index].DeviceBaseAddress, | |
FlashDevices[Index].RegionBaseAddress, | |
FlashDevices[Index].Size, | |
Index, | |
FlashDevices[Index].BlockSize, | |
ContainVariableStorage, | |
&FlashDevices[Index].Guid, | |
&mFlashInstances[Index] | |
); | |
if (EFI_ERROR(Status)) | |
{ | |
DEBUG((EFI_D_ERROR, "[%a]:[%dL] Fail to create instance for Flash[%d]\n", __FUNCTION__, __LINE__, Index)); | |
} | |
} | |
// | |
// Register for the virtual address change event | |
// | |
Status = gBS->CreateEventEx ( | |
EVT_NOTIFY_SIGNAL, | |
TPL_NOTIFY, | |
FlashFvbVirtualNotifyEvent, | |
NULL, | |
&gEfiEventVirtualAddressChangeGuid, | |
&mFlashFvbVirtualAddrChangeEvent | |
); | |
ASSERT_EFI_ERROR (Status); | |
return Status; | |
} |