/** @file NorFlashDxe.c Copyright (c) 2011 - 2021, Arm Limited. All rights reserved.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #include #include #include #include #include "VirtNorFlash.h" STATIC EFI_EVENT mNorFlashVirtualAddrChangeEvent; // // Global variable declarations // NOR_FLASH_INSTANCE **mNorFlashInstances; UINT32 mNorFlashDeviceCount; UINTN mFlashNvStorageVariableBase; EFI_EVENT mFvbVirtualAddrChangeEvent; 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 0, // LastBlock 0, // BlockSize { 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->BlockSize = BlockSize; Instance->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 = gBS->InstallMultipleProtocolInterfaces ( &Instance->Handle, &gEfiDevicePathProtocolGuid, &Instance->DevicePath, &gEfiFirmwareVolumeBlockProtocolGuid, &Instance->FvbProtocol, NULL ); if (EFI_ERROR (Status)) { FreePool (Instance); return Status; } } else { Status = gBS->InstallMultipleProtocolInterfaces ( &Instance->Handle, &gEfiDevicePathProtocolGuid, &Instance->DevicePath, NULL ); if (EFI_ERROR (Status)) { FreePool (Instance); return Status; } } *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; EFI_TPL OriginalTPL; if (!EfiAtRuntime ()) { // Raise TPL to TPL_HIGH to stop anyone from interrupting us. OriginalTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL); } else { // This initialization is only to prevent the compiler to complain about the // use of uninitialized variables OriginalTPL = TPL_HIGH_LEVEL; } 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)); } if (!EfiAtRuntime ()) { // Interruptions can resume. gBS->RestoreTPL (OriginalTPL); } 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; EFI_TPL OriginalTPL; 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; if (!EfiAtRuntime ()) { // Raise TPL to TPL_HIGH to stop anyone from interrupting us. OriginalTPL = gBS->RaiseTPL (TPL_HIGH_LEVEL); } else { // This initialization is only to prevent the compiler to complain about the // use of uninitialized variables OriginalTPL = TPL_HIGH_LEVEL; } 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: // Put device back into Read Array mode SEND_NOR_COMMAND (Instance->DeviceBaseAddress, 0, P30_CMD_READ_ARRAY); if (!EfiAtRuntime ()) { // Interruptions can resume. gBS->RestoreTPL (OriginalTPL); } 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_SYSTEM_TABLE *SystemTable ) { EFI_STATUS Status; UINT32 Index; VIRT_NOR_FLASH_DESCRIPTION *NorFlashDevices; BOOLEAN ContainVariableStorage; Status = VirtNorFlashPlatformInitialization (); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "NorFlashInitialise: Fail to initialize Nor Flash devices\n")); return Status; } Status = VirtNorFlashPlatformGetDevices (&NorFlashDevices, &mNorFlashDeviceCount); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "NorFlashInitialise: Fail to get Nor Flash devices\n")); return Status; } mNorFlashInstances = AllocateRuntimePool (sizeof (NOR_FLASH_INSTANCE *) * mNorFlashDeviceCount); for (Index = 0; Index < mNorFlashDeviceCount; Index++) { // Check if this NOR Flash device contain the variable storage region if (PcdGet64 (PcdFlashNvStorageVariableBase64) != 0) { ContainVariableStorage = (NorFlashDevices[Index].RegionBaseAddress <= PcdGet64 (PcdFlashNvStorageVariableBase64)) && (PcdGet64 (PcdFlashNvStorageVariableBase64) + PcdGet32 (PcdFlashNvStorageVariableSize) <= NorFlashDevices[Index].RegionBaseAddress + NorFlashDevices[Index].Size); } else { ContainVariableStorage = (NorFlashDevices[Index].RegionBaseAddress <= PcdGet32 (PcdFlashNvStorageVariableBase)) && (PcdGet32 (PcdFlashNvStorageVariableBase) + PcdGet32 (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)); } } // // Register for the virtual address change event // Status = gBS->CreateEventEx ( EVT_NOTIFY_SIGNAL, TPL_NOTIFY, NorFlashVirtualNotifyEvent, NULL, &gEfiEventVirtualAddressChangeGuid, &mNorFlashVirtualAddrChangeEvent ); ASSERT_EFI_ERROR (Status); return Status; } EFI_STATUS EFIAPI NorFlashFvbInitialize ( IN NOR_FLASH_INSTANCE *Instance ) { EFI_STATUS Status; UINT32 FvbNumLba; EFI_BOOT_MODE BootMode; UINTN RuntimeMmioRegionSize; DEBUG ((DEBUG_BLKIO, "NorFlashFvbInitialize\n")); ASSERT ((Instance != NULL)); // // Declare the Non-Volatile storage as EFI_MEMORY_RUNTIME // // Note: all the NOR Flash region needs to be reserved into the UEFI Runtime memory; // even if we only use the small block region at the top of the NOR Flash. // The reason is when the NOR Flash memory is set into program mode, the command // is written as the base of the flash region (ie: Instance->DeviceBaseAddress) RuntimeMmioRegionSize = (Instance->RegionBaseAddress - Instance->DeviceBaseAddress) + Instance->Size; Status = gDS->AddMemorySpace ( EfiGcdMemoryTypeMemoryMappedIo, Instance->DeviceBaseAddress, RuntimeMmioRegionSize, EFI_MEMORY_UC | EFI_MEMORY_RUNTIME ); ASSERT_EFI_ERROR (Status); Status = gDS->SetMemorySpaceAttributes ( Instance->DeviceBaseAddress, RuntimeMmioRegionSize, EFI_MEMORY_UC | EFI_MEMORY_RUNTIME ); ASSERT_EFI_ERROR (Status); mFlashNvStorageVariableBase = (PcdGet64 (PcdFlashNvStorageVariableBase64) != 0) ? PcdGet64 (PcdFlashNvStorageVariableBase64) : PcdGet32 (PcdFlashNvStorageVariableBase); // Set the index of the first LBA for the FVB Instance->StartLba = (mFlashNvStorageVariableBase - Instance->RegionBaseAddress) / Instance->BlockSize; BootMode = GetBootModeHob (); if (BootMode == BOOT_WITH_DEFAULT_SETTINGS) { Status = EFI_INVALID_PARAMETER; } else { // 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", __func__)); DEBUG (( DEBUG_INFO, "%a: Installing a correct one for this volume.\n", __func__ )); // Erase all the NorFlash that is reserved for variable storage FvbNumLba = (PcdGet32 (PcdFlashNvStorageVariableSize) + PcdGet32 (PcdFlashNvStorageFtwWorkingSize) + PcdGet32 (PcdFlashNvStorageFtwSpareSize)) / Instance->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; } } // // The driver implementing the variable read service can now be dispatched; // the varstore headers are in place. // Status = gBS->InstallProtocolInterface ( &gImageHandle, &gEdkiiNvVarStoreFormattedGuid, EFI_NATIVE_INTERFACE, NULL ); ASSERT_EFI_ERROR (Status); // // Register for the virtual address change event // Status = gBS->CreateEventEx ( EVT_NOTIFY_SIGNAL, TPL_NOTIFY, FvbVirtualNotifyEvent, NULL, &gEfiEventVirtualAddressChangeGuid, &mFvbVirtualAddrChangeEvent ); ASSERT_EFI_ERROR (Status); return Status; }