/** @file NorFlashStandaloneMm.c | |
Copyright (c) 2011 - 2021, Arm Limited. All rights reserved.<BR> | |
Copyright (c) 2020, Linaro, Ltd. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include <Library/BaseMemoryLib.h> | |
#include <Library/MemoryAllocationLib.h> | |
#include <Library/MmServicesTableLib.h> | |
#include "NorFlash.h" | |
// | |
// Global variable declarations | |
// | |
NOR_FLASH_INSTANCE **mNorFlashInstances; | |
UINT32 mNorFlashDeviceCount; | |
UINTN mFlashNvStorageVariableBase; | |
NOR_FLASH_INSTANCE mNorFlashInstanceTemplate = { | |
NOR_FLASH_SIGNATURE, // Signature | |
NULL, // Handle ... NEED TO BE FILLED | |
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, // Reset; | |
NULL, // ReadBlocks | |
NULL, // WriteBlocks | |
NULL // FlushBlocks | |
}, // BlockIoProtocol | |
{ | |
0, // MediaId ... NEED TO BE FILLED | |
FALSE, // RemovableMedia | |
TRUE, // MediaPresent | |
FALSE, // LogicalPartition | |
FALSE, // ReadOnly | |
FALSE, // WriteCaching; | |
0, // BlockSize ... NEED TO BE FILLED | |
4, // IoAlign | |
0, // LastBlock ... NEED TO BE FILLED | |
0, // LowestAlignedLba | |
1, // LogicalBlocksPerPhysicalBlock | |
}, //Media; | |
{ | |
EFI_DISK_IO_PROTOCOL_REVISION, // Revision | |
NULL, // ReadDisk | |
NULL // WriteDisk | |
}, | |
{ | |
FvbGetAttributes, // GetAttributes | |
FvbSetAttributes, // SetAttributes | |
FvbGetPhysicalAddress, // GetPhysicalAddress | |
FvbGetBlockSize, // GetBlockSize | |
FvbRead, // Read | |
FvbWrite, // Write | |
FvbEraseBlocks, // EraseBlocks | |
NULL, //ParentHandle | |
}, // FvbProtoccol; | |
NULL, // ShadowBuffer | |
{ | |
{ | |
{ | |
HARDWARE_DEVICE_PATH, | |
HW_VENDOR_DP, | |
{ | |
(UINT8)(OFFSET_OF (NOR_FLASH_DEVICE_PATH, End)), | |
(UINT8)(OFFSET_OF (NOR_FLASH_DEVICE_PATH, End) >> 8) | |
} | |
}, | |
{ 0x0, 0x0, 0x0, { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 } }, // GUID ... NEED TO BE FILLED | |
}, | |
0, // Index | |
{ | |
END_DEVICE_PATH_TYPE, | |
END_ENTIRE_DEVICE_PATH_SUBTYPE, | |
{ sizeof (EFI_DEVICE_PATH_PROTOCOL), 0 } | |
} | |
} // DevicePath | |
}; | |
EFI_STATUS | |
NorFlashCreateInstance ( | |
IN UINTN NorFlashDeviceBase, | |
IN UINTN NorFlashRegionBase, | |
IN UINTN NorFlashSize, | |
IN UINT32 Index, | |
IN UINT32 BlockSize, | |
IN BOOLEAN SupportFvb, | |
OUT NOR_FLASH_INSTANCE** NorFlashInstance | |
) | |
{ | |
EFI_STATUS Status; | |
NOR_FLASH_INSTANCE* Instance; | |
ASSERT(NorFlashInstance != NULL); | |
Instance = AllocateRuntimeCopyPool (sizeof(NOR_FLASH_INSTANCE),&mNorFlashInstanceTemplate); | |
if (Instance == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Instance->DeviceBaseAddress = NorFlashDeviceBase; | |
Instance->RegionBaseAddress = NorFlashRegionBase; | |
Instance->Size = NorFlashSize; | |
Instance->BlockIoProtocol.Media = &Instance->Media; | |
Instance->Media.MediaId = Index; | |
Instance->Media.BlockSize = BlockSize; | |
Instance->Media.LastBlock = (NorFlashSize / BlockSize)-1; | |
CopyGuid (&Instance->DevicePath.Vendor.Guid, &gEfiCallerIdGuid); | |
Instance->DevicePath.Index = (UINT8)Index; | |
Instance->ShadowBuffer = AllocateRuntimePool (BlockSize);; | |
if (Instance->ShadowBuffer == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
if (SupportFvb) { | |
NorFlashFvbInitialize (Instance); | |
Status = gMmst->MmInstallProtocolInterface ( | |
&Instance->Handle, | |
&gEfiSmmFirmwareVolumeBlockProtocolGuid, | |
EFI_NATIVE_INTERFACE, | |
&Instance->FvbProtocol | |
); | |
if (EFI_ERROR(Status)) { | |
FreePool (Instance); | |
return Status; | |
} | |
} else { | |
DEBUG((DEBUG_ERROR,"standalone MM NOR Flash driver only support FVB.\n")); | |
FreePool (Instance); | |
return EFI_UNSUPPORTED; | |
} | |
*NorFlashInstance = Instance; | |
return Status; | |
} | |
/** | |
* This function unlock and erase an entire NOR Flash block. | |
**/ | |
EFI_STATUS | |
NorFlashUnlockAndEraseSingleBlock ( | |
IN NOR_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 = NorFlashUnlockSingleBlockIfNecessary (Instance, BlockAddress); | |
if (EFI_ERROR (Status)) { | |
break; | |
} | |
Status = NorFlashEraseSingleBlock (Instance, BlockAddress); | |
Index++; | |
} while ((Index < NOR_FLASH_ERASE_RETRY) && (Status == EFI_WRITE_PROTECTED)); | |
if (Index == NOR_FLASH_ERASE_RETRY) { | |
DEBUG((DEBUG_ERROR,"EraseSingleBlock(BlockAddress=0x%08x: Block Locked Error (try to erase %d times)\n", BlockAddress,Index)); | |
} | |
return Status; | |
} | |
EFI_STATUS | |
NorFlashWriteFullBlock ( | |
IN NOR_FLASH_INSTANCE *Instance, | |
IN EFI_LBA Lba, | |
IN UINT32 *DataBuffer, | |
IN UINT32 BlockSizeInWords | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN WordAddress; | |
UINT32 WordIndex; | |
UINTN BufferIndex; | |
UINTN BlockAddress; | |
UINTN BuffersInBlock; | |
UINTN RemainingWords; | |
UINTN Cnt; | |
Status = EFI_SUCCESS; | |
// Get the physical address of the block | |
BlockAddress = GET_NOR_BLOCK_ADDRESS (Instance->RegionBaseAddress, Lba, BlockSizeInWords * 4); | |
// Start writing from the first address at the start of the block | |
WordAddress = BlockAddress; | |
Status = NorFlashUnlockAndEraseSingleBlock (Instance, BlockAddress); | |
if (EFI_ERROR(Status)) { | |
DEBUG((DEBUG_ERROR, "WriteSingleBlock: ERROR - Failed to Unlock and Erase the single block at 0x%X\n", BlockAddress)); | |
goto EXIT; | |
} | |
// To speed up the programming operation, NOR Flash is programmed using the Buffered Programming method. | |
// Check that the address starts at a 32-word boundary, i.e. last 7 bits must be zero | |
if ((WordAddress & BOUNDARY_OF_32_WORDS) == 0x00) { | |
// First, break the entire block into buffer-sized chunks. | |
BuffersInBlock = (UINTN)(BlockSizeInWords * 4) / P30_MAX_BUFFER_SIZE_IN_BYTES; | |
// Then feed each buffer chunk to the NOR Flash | |
// If a buffer does not contain any data, don't write it. | |
for(BufferIndex=0; | |
BufferIndex < BuffersInBlock; | |
BufferIndex++, WordAddress += P30_MAX_BUFFER_SIZE_IN_BYTES, DataBuffer += P30_MAX_BUFFER_SIZE_IN_WORDS | |
) { | |
// Check the buffer to see if it contains any data (not set all 1s). | |
for (Cnt = 0; Cnt < P30_MAX_BUFFER_SIZE_IN_WORDS; Cnt++) { | |
if (~DataBuffer[Cnt] != 0 ) { | |
// Some data found, write the buffer. | |
Status = NorFlashWriteBuffer (Instance, WordAddress, P30_MAX_BUFFER_SIZE_IN_BYTES, | |
DataBuffer); | |
if (EFI_ERROR(Status)) { | |
goto EXIT; | |
} | |
break; | |
} | |
} | |
} | |
// Finally, finish off any remaining words that are less than the maximum size of the buffer | |
RemainingWords = BlockSizeInWords % P30_MAX_BUFFER_SIZE_IN_WORDS; | |
if(RemainingWords != 0) { | |
Status = NorFlashWriteBuffer (Instance, WordAddress, (RemainingWords * 4), DataBuffer); | |
if (EFI_ERROR(Status)) { | |
goto EXIT; | |
} | |
} | |
} else { | |
// For now, use the single word programming algorithm | |
// It is unlikely that the NOR Flash will exist in an address which falls within a 32 word boundary range, | |
// i.e. which ends in the range 0x......01 - 0x......7F. | |
for(WordIndex=0; WordIndex<BlockSizeInWords; WordIndex++, DataBuffer++, WordAddress = WordAddress + 4) { | |
Status = NorFlashWriteSingleWord (Instance, WordAddress, *DataBuffer); | |
if (EFI_ERROR(Status)) { | |
goto EXIT; | |
} | |
} | |
} | |
EXIT: | |
if (EFI_ERROR(Status)) { | |
DEBUG((DEBUG_ERROR, "NOR FLASH Programming [WriteSingleBlock] failed at address 0x%08x. Exit Status = \"%r\".\n", WordAddress, Status)); | |
} | |
return Status; | |
} | |
EFI_STATUS | |
EFIAPI | |
NorFlashInitialise ( | |
IN EFI_HANDLE ImageHandle, | |
IN EFI_MM_SYSTEM_TABLE *MmSystemTable | |
) | |
{ | |
EFI_STATUS Status; | |
UINT32 Index; | |
NOR_FLASH_DESCRIPTION* NorFlashDevices; | |
BOOLEAN ContainVariableStorage; | |
Status = NorFlashPlatformInitialization (); | |
if (EFI_ERROR(Status)) { | |
DEBUG((DEBUG_ERROR,"NorFlashInitialise: Fail to initialize Nor Flash devices\n")); | |
return Status; | |
} | |
Status = NorFlashPlatformGetDevices (&NorFlashDevices, &mNorFlashDeviceCount); | |
if (EFI_ERROR(Status)) { | |
DEBUG((DEBUG_ERROR,"NorFlashInitialise: Fail to get Nor Flash devices\n")); | |
return Status; | |
} | |
mNorFlashInstances = AllocatePool (sizeof(NOR_FLASH_INSTANCE*) * mNorFlashDeviceCount); | |
for (Index = 0; Index < mNorFlashDeviceCount; Index++) { | |
// Check if this NOR Flash device contain the variable storage region | |
if (FixedPcdGet64 (PcdFlashNvStorageVariableBase64) != 0) { | |
ContainVariableStorage = | |
(NorFlashDevices[Index].RegionBaseAddress <= FixedPcdGet64 (PcdFlashNvStorageVariableBase64)) && | |
(FixedPcdGet64 (PcdFlashNvStorageVariableBase64) + FixedPcdGet32 (PcdFlashNvStorageVariableSize) <= | |
NorFlashDevices[Index].RegionBaseAddress + NorFlashDevices[Index].Size); | |
} else { | |
ContainVariableStorage = | |
(NorFlashDevices[Index].RegionBaseAddress <= FixedPcdGet32 (PcdFlashNvStorageVariableBase)) && | |
(FixedPcdGet32 (PcdFlashNvStorageVariableBase) + FixedPcdGet32 (PcdFlashNvStorageVariableSize) <= | |
NorFlashDevices[Index].RegionBaseAddress + NorFlashDevices[Index].Size); | |
} | |
Status = NorFlashCreateInstance ( | |
NorFlashDevices[Index].DeviceBaseAddress, | |
NorFlashDevices[Index].RegionBaseAddress, | |
NorFlashDevices[Index].Size, | |
Index, | |
NorFlashDevices[Index].BlockSize, | |
ContainVariableStorage, | |
&mNorFlashInstances[Index] | |
); | |
if (EFI_ERROR(Status)) { | |
DEBUG((DEBUG_ERROR,"NorFlashInitialise: Fail to create instance for NorFlash[%d]\n",Index)); | |
} | |
} | |
return Status; | |
} | |
EFI_STATUS | |
EFIAPI | |
NorFlashFvbInitialize ( | |
IN NOR_FLASH_INSTANCE* Instance | |
) | |
{ | |
EFI_STATUS Status; | |
UINT32 FvbNumLba; | |
ASSERT((Instance != NULL)); | |
mFlashNvStorageVariableBase = (FixedPcdGet64 (PcdFlashNvStorageVariableBase64) != 0) ? | |
FixedPcdGet64 (PcdFlashNvStorageVariableBase64) : FixedPcdGet32 (PcdFlashNvStorageVariableBase); | |
// Set the index of the first LBA for the FVB | |
Instance->StartLba = (mFlashNvStorageVariableBase - Instance->RegionBaseAddress) / Instance->Media.BlockSize; | |
// Determine if there is a valid header at the beginning of the NorFlash | |
Status = ValidateFvHeader (Instance); | |
// Install the Default FVB header if required | |
if (EFI_ERROR(Status)) { | |
// There is no valid header, so time to install one. | |
DEBUG ((DEBUG_INFO, "%a: The FVB Header is not valid.\n", __FUNCTION__)); | |
DEBUG ((DEBUG_INFO, "%a: Installing a correct one for this volume.\n", | |
__FUNCTION__)); | |
// Erase all the NorFlash that is reserved for variable storage | |
FvbNumLba = (PcdGet32(PcdFlashNvStorageVariableSize) + PcdGet32(PcdFlashNvStorageFtwWorkingSize) + 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; | |
} |