/** * The following function presumes that the block has already been unlocked. **/ EFI_STATUS NorFlashUnlockAndEraseSingleBlock ( IN NOR_FLASH_INSTANCE *Instance, IN UINTN BlockAddress ) { EFI_STATUS Status; UINTN Index; EFI_TPL OriginalTPL; // Raise TPL to TPL_HIGH to stop anyone from interrupting us. OriginalTPL = gBS->RaiseTPL (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)) { Status = NorFlashEraseSingleBlock (Instance, BlockAddress); } Index++; } while ((Index < NOR_FLASH_ERASE_RETRY) && (Status == EFI_WRITE_PROTECTED)); if (Index == NOR_FLASH_ERASE_RETRY) { DEBUG((EFI_D_ERROR,"EraseSingleBlock(BlockAddress=0x%08x: Block Locked Error (try to erase %d times)\n", BlockAddress,Index)); } // Interruptions can resume. gBS->RestoreTPL (OriginalTPL); 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((EFI_D_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; }
/* Write a full or portion of a block. It must not span block boundaries; that is, Offset + *NumBytes <= Instance->Media.BlockSize. */ EFI_STATUS NorFlashWriteSingleBlock ( IN NOR_FLASH_INSTANCE *Instance, IN EFI_LBA Lba, IN UINTN Offset, IN OUT UINTN *NumBytes, IN UINT8 *Buffer ) { EFI_STATUS TempStatus; UINT32 Tmp; UINT32 TmpBuf; UINT32 WordToWrite; UINT32 Mask; BOOLEAN DoErase; UINTN BytesToWrite; UINTN CurOffset; UINTN WordAddr; UINTN BlockSize; UINTN BlockAddress; UINTN PrevBlockAddress; PrevBlockAddress = 0; if (!Instance->Initialized && Instance->Initialize) { Instance->Initialize(Instance); } DEBUG ((DEBUG_BLKIO, "NorFlashWriteSingleBlock(Parameters: Lba=%ld, Offset=0x%x, *NumBytes=0x%x, Buffer @ 0x%08x)\n", Lba, Offset, *NumBytes, Buffer)); // Detect WriteDisabled state if (Instance->Media.ReadOnly == TRUE) { DEBUG ((EFI_D_ERROR, "NorFlashWriteSingleBlock: 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, "NorFlashWriteSingleBlock: 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, "NorFlashWriteSingleBlock: ERROR - EFI_BAD_BUFFER_SIZE: (Offset=0x%x + NumBytes=0x%x) > BlockSize=0x%x\n", Offset, *NumBytes, BlockSize )); return EFI_BAD_BUFFER_SIZE; } // Pick 128bytes as a good start for word operations as opposed to erasing the // block and writing the data regardless if an erase is really needed. // It looks like most individual NV variable writes are smaller than 128bytes. if (*NumBytes <= 128) { // Check to see if we need to erase before programming the data into NOR. // If the destination bits are only changing from 1s to 0s we can just write. // After a block is erased all bits in the block is set to 1. // If any byte requires us to erase we just give up and rewrite all of it. DoErase = FALSE; BytesToWrite = *NumBytes; CurOffset = Offset; while (BytesToWrite > 0) { // Read full word from NOR, splice as required. A word is the smallest // unit we can write. TempStatus = NorFlashRead (Instance, Lba, CurOffset & ~(0x3), sizeof(Tmp), &Tmp); if (EFI_ERROR (TempStatus)) { return EFI_DEVICE_ERROR; } // Physical address of word in NOR to write. WordAddr = (CurOffset & ~(0x3)) + GET_NOR_BLOCK_ADDRESS (Instance->RegionBaseAddress, Lba, BlockSize); // The word of data that is to be written. TmpBuf = *((UINT32*)(Buffer + (*NumBytes - BytesToWrite))); // First do word aligned chunks. if ((CurOffset & 0x3) == 0) { if (BytesToWrite >= 4) { // Is the destination still in 'erased' state? if (~Tmp != 0) { // Check to see if we are only changing bits to zero. if ((Tmp ^ TmpBuf) & TmpBuf) { DoErase = TRUE; break; } } // Write this word to NOR WordToWrite = TmpBuf; CurOffset += sizeof(TmpBuf); BytesToWrite -= sizeof(TmpBuf); } else { // BytesToWrite < 4. Do small writes and left-overs Mask = ~((~0) << (BytesToWrite * 8)); // Mask out the bytes we want. TmpBuf &= Mask; // Is the destination still in 'erased' state? if ((Tmp & Mask) != Mask) { // Check to see if we are only changing bits to zero. if ((Tmp ^ TmpBuf) & TmpBuf) { DoErase = TRUE; break; } } // Merge old and new data. Write merged word to NOR WordToWrite = (Tmp & ~Mask) | TmpBuf; CurOffset += BytesToWrite; BytesToWrite = 0; } } else { // Do multiple words, but starting unaligned. if (BytesToWrite > (4 - (CurOffset & 0x3))) { Mask = ((~0) << ((CurOffset & 0x3) * 8)); // Mask out the bytes we want. TmpBuf &= Mask; // Is the destination still in 'erased' state? if ((Tmp & Mask) != Mask) { // Check to see if we are only changing bits to zero. if ((Tmp ^ TmpBuf) & TmpBuf) { DoErase = TRUE; break; } } // Merge old and new data. Write merged word to NOR WordToWrite = (Tmp & ~Mask) | TmpBuf; BytesToWrite -= (4 - (CurOffset & 0x3)); CurOffset += (4 - (CurOffset & 0x3)); } else { // Unaligned and fits in one word. Mask = (~((~0) << (BytesToWrite * 8))) << ((CurOffset & 0x3) * 8); // Mask out the bytes we want. TmpBuf = (TmpBuf << ((CurOffset & 0x3) * 8)) & Mask; // Is the destination still in 'erased' state? if ((Tmp & Mask) != Mask) { // Check to see if we are only changing bits to zero. if ((Tmp ^ TmpBuf) & TmpBuf) { DoErase = TRUE; break; } } // Merge old and new data. Write merged word to NOR WordToWrite = (Tmp & ~Mask) | TmpBuf; CurOffset += BytesToWrite; BytesToWrite = 0; } } // // Write the word to NOR. // BlockAddress = GET_NOR_BLOCK_ADDRESS (Instance->RegionBaseAddress, Lba, BlockSize); if (BlockAddress != PrevBlockAddress) { TempStatus = NorFlashUnlockSingleBlockIfNecessary (Instance, BlockAddress); if (EFI_ERROR (TempStatus)) { return EFI_DEVICE_ERROR; } PrevBlockAddress = BlockAddress; } TempStatus = NorFlashWriteSingleWord (Instance, WordAddr, WordToWrite); if (EFI_ERROR (TempStatus)) { return EFI_DEVICE_ERROR; } } // Exit if we got here and could write all the data. Otherwise do the // Erase-Write cycle. if (!DoErase) { return EFI_SUCCESS; } } // Check we did get some memory. Buffer is BlockSize. if (Instance->ShadowBuffer == NULL) { DEBUG ((EFI_D_ERROR, "FvbWrite: ERROR - Buffer not ready\n")); return EFI_DEVICE_ERROR; } // Read NOR Flash data into shadow buffer TempStatus = NorFlashReadBlocks (Instance, Lba, BlockSize, Instance->ShadowBuffer); if (EFI_ERROR (TempStatus)) { // Return one of the pre-approved error statuses return EFI_DEVICE_ERROR; } // Put the data at the appropriate location inside the buffer area CopyMem ((VOID*)((UINTN)Instance->ShadowBuffer + Offset), Buffer, *NumBytes); // Write the modified buffer back to the NorFlash TempStatus = NorFlashWriteBlocks (Instance, Lba, BlockSize, Instance->ShadowBuffer); if (EFI_ERROR (TempStatus)) { // Return one of the pre-approved error statuses return EFI_DEVICE_ERROR; } return EFI_SUCCESS; }