/* ---------------------------------------------------------------------------- | |
* ATMEL Microcontroller Software Support | |
* ---------------------------------------------------------------------------- | |
* Copyright (c) 2010, Atmel Corporation | |
* | |
* All rights reserved. | |
* | |
* Redistribution and use in source and binary forms, with or without | |
* modification, are permitted provided that the following conditions are met: | |
* | |
* - Redistributions of source code must retain the above copyright notice, | |
* this list of conditions and the disclaimer below. | |
* | |
* Atmel's name may not be used to endorse or promote products derived from | |
* this software without specific prior written permission. | |
* | |
* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR | |
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE | |
* DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT, | |
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, | |
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, | |
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
* ---------------------------------------------------------------------------- | |
*/ | |
/** | |
* \file | |
* | |
* Translate a nandflash physical block to logical block, it will call lower layer such as MappedNandFlash. | |
*/ | |
/*---------------------------------------------------------------------------- | |
* Headers | |
*----------------------------------------------------------------------------*/ | |
#include "memories.h" | |
#include <assert.h> | |
#include <string.h> | |
/*---------------------------------------------------------------------------- | |
* Local definitions | |
*----------------------------------------------------------------------------*/ | |
/** Casts */ | |
#define MAPPED(translated) ((struct MappedNandFlash *) translated) | |
#define MANAGED(translated) ((struct ManagedNandFlash *) translated) | |
#define ECC(translated) ((struct EccNandFlash *) translated) | |
#define RAW(translated) ((struct RawNandFlash *) translated) | |
#define MODEL(translated) ((struct NandFlashModel *) translated) | |
/** Minimum number of blocks that should be kept unallocated*/ | |
#define MINNUMUNALLOCATEDBLOCKS 32 | |
/** Maximum allowed erase count difference*/ | |
#define MAXERASEDIFFERENCE 5 | |
/*---------------------------------------------------------------------------- | |
* Local functions | |
*----------------------------------------------------------------------------*/ | |
/** | |
* \brief Check is there are enough free block could be allocated. | |
* | |
* \param translated Pointer to a TranslatedNandFlash instance. | |
* \return 1 if there are enough free blocks to perform a single block | |
* allocation; otherwise returns 0. | |
*/ | |
static uint8_t BlockCanBeAllocated( | |
const struct TranslatedNandFlash *translated) | |
{ | |
uint16_t count; | |
/* Count number of free and dirty blocks (unallocated blocks)*/ | |
count = ManagedNandFlash_CountBlocks(MANAGED(translated), NandBlockStatus_DIRTY) | |
+ ManagedNandFlash_CountBlocks(MANAGED(translated), NandBlockStatus_FREE); | |
/* Check that count is greater than minimum number of unallocated blocks*/ | |
if (count > MINNUMUNALLOCATEDBLOCKS) { | |
return 1; | |
} | |
else { | |
return 0; | |
} | |
} | |
/** | |
* \brief Check the given page inside the currently written block is clean or not. | |
* | |
* \param translated Pointer to a TranslatedNandFlash instance. | |
* \param page Page number. | |
* \return 1 if the given page inside the currently written block is clean (has | |
* not been written yet); otherwise returns 0. | |
*/ | |
static uint8_t PageIsClean( | |
const struct TranslatedNandFlash *translated, | |
uint16_t page) | |
{ | |
assert( page < NandFlashModel_GetBlockSizeInPages(MODEL(translated)) ) ; /* "PageIsClean: Page out-of-bounds\n\r" */ | |
return ((translated->currentBlockPageStatuses[page / 8] >> (page % 8)) & 1) == 0; | |
} | |
/** | |
* \brief Marks the given page as being dirty (i.e. written). | |
* | |
* \param translated Pointer to a TranslatedNandFlash instance. | |
* \param page Page number. | |
*/ | |
static void MarkPageDirty( | |
struct TranslatedNandFlash *translated, | |
uint16_t page) | |
{ | |
assert( page < NandFlashModel_GetBlockSizeInPages(MODEL(translated)) ) ; /* "PageIsClean: Page out-of-bounds\n\r" */ | |
translated->currentBlockPageStatuses[page / 8] |= 1 << (page % 8); | |
} | |
/** | |
* \brief Marks all pages as being clean. | |
* | |
* \param translated Pointer to a TranslatedNandFlash instance. | |
*/ | |
static void MarkAllPagesClean( struct TranslatedNandFlash *translated ) | |
{ | |
memset( translated->currentBlockPageStatuses, 0, sizeof( translated->currentBlockPageStatuses ) ) ; | |
} | |
/** | |
* \brief Allocates the best-fitting physical block for the given logical block. | |
* | |
* \param translated Pointer to a TranslatedNandFlash instance. | |
* \param block Logical block number. | |
* \return 0 if successful; otherwise returns NandCommon_ERROR_NOBLOCKFOUND if | |
* there are no more free blocks, or a NandCommon_ERROR code. | |
*/ | |
static uint8_t AllocateBlock( | |
struct TranslatedNandFlash *translated, | |
uint16_t block) | |
{ | |
uint16_t freeBlock, liveBlock; | |
uint8_t error; | |
int32_t eraseDifference; | |
TRACE_DEBUG("Allocating a new block\n\r"); | |
/* Find youngest free block and youngest live block*/ | |
if (ManagedNandFlash_FindYoungestBlock(MANAGED(translated), | |
NandBlockStatus_FREE, | |
&freeBlock)) { | |
TRACE_ERROR("AllocateBlock: Could not find a free block\n\r"); | |
return NandCommon_ERROR_NOBLOCKFOUND; | |
} | |
/* If this is the last free block, save the logical mapping in it and clean dirty blocks */ | |
TRACE_DEBUG("Number of FREE blocks: %d\n\r", | |
ManagedNandFlash_CountBlocks(MANAGED(translated), NandBlockStatus_FREE)); | |
if (ManagedNandFlash_CountBlocks(MANAGED(translated), | |
NandBlockStatus_FREE) == 1) { | |
/* Save mapping and clean dirty blocks*/ | |
TRACE_DEBUG("Last FREE block, cleaning up ...\n\r"); | |
error = MappedNandFlash_SaveLogicalMapping(MAPPED(translated), freeBlock); | |
if (error) | |
{ | |
TRACE_ERROR("AllocateBlock: Failed to save mapping\n\r"); | |
return error; | |
} | |
error = ManagedNandFlash_EraseDirtyBlocks(MANAGED(translated)); | |
if (error) | |
{ | |
TRACE_ERROR("AllocatedBlock: Failed to erase dirty blocks\n\r"); | |
return error; | |
} | |
/* Allocate new block*/ | |
return AllocateBlock(translated, block); | |
} | |
/* Find youngest LIVE block to check the erase count difference*/ | |
if (!ManagedNandFlash_FindYoungestBlock(MANAGED(translated), | |
NandBlockStatus_LIVE, | |
&liveBlock)) | |
{ | |
/* Calculate erase count difference*/ | |
TRACE_DEBUG("Free block erase count = %d\n\r", MANAGED(translated)->blockStatuses[freeBlock].eraseCount); | |
TRACE_DEBUG("Live block erase count = %d\n\r", MANAGED(translated)->blockStatuses[liveBlock].eraseCount); | |
eraseDifference = absv(MANAGED(translated)->blockStatuses[freeBlock].eraseCount | |
- MANAGED(translated)->blockStatuses[liveBlock].eraseCount); | |
/* Check if it is too big*/ | |
if (eraseDifference > MAXERASEDIFFERENCE) | |
{ | |
TRACE_WARNING("Erase difference too big, switching blocks\n\r"); | |
MappedNandFlash_Map( | |
MAPPED(translated), | |
MappedNandFlash_PhysicalToLogical( | |
MAPPED(translated), | |
liveBlock), | |
freeBlock); | |
ManagedNandFlash_CopyBlock(MANAGED(translated), | |
liveBlock, | |
freeBlock); | |
/* Allocate a new block*/ | |
return AllocateBlock(translated, block); | |
} | |
} | |
/* Map block*/ | |
TRACE_DEBUG("Allocating PB#%d for LB#%d\n\r", freeBlock, block); | |
MappedNandFlash_Map(MAPPED(translated), block, freeBlock); | |
return 0; | |
} | |
/*---------------------------------------------------------------------------- | |
* Exported functions | |
*----------------------------------------------------------------------------*/ | |
/** | |
* \brief Initializes a TranslatedNandFlash instance. | |
* | |
* \param translated Pointer to a TranslatedNandFlash instance. | |
* \param model Pointer to the underlying nand chip model. Can be 0. | |
* \param commandAddress Address at which commands are sent. | |
* \param addressAddress Address at which addresses are sent. | |
* \param dataAddress Address at which data is sent. | |
* \param pinChipEnable Pin controlling the CE signal of the NandFlash. | |
* \param pinReadyBusy Pin used to monitor the ready/busy signal of the Nand. | |
* \param baseBlock Basic physical block address of mapped area. | |
* \param sizeInBlocks Number of blocks that is mapped. | |
* \return 0 if successful; otherwise returns a NandCommon_ERROR_xxx code. | |
*/ | |
uint8_t TranslatedNandFlash_Initialize( | |
struct TranslatedNandFlash *translated, | |
const struct NandFlashModel *model, | |
uint32_t commandAddress, | |
uint32_t addressAddress, | |
uint32_t dataAddress, | |
const Pin pinChipEnable, | |
const Pin pinReadyBusy, | |
uint16_t baseBlock, | |
uint16_t sizeInBlocks) | |
{ | |
translated->currentLogicalBlock = -1; | |
translated->previousPhysicalBlock = -1; | |
MarkAllPagesClean(translated); | |
/* Initialize MappedNandFlash*/ | |
return MappedNandFlash_Initialize(MAPPED(translated), | |
model, | |
commandAddress, | |
addressAddress, | |
dataAddress, | |
pinChipEnable, | |
pinReadyBusy, | |
baseBlock, | |
sizeInBlocks); | |
} | |
/** | |
* \brief Reads the data and/or the spare area of a page on a translated nandflash. | |
* If the block is not currently mapped but could be (i.e. there are available | |
* physical blocks), then the data/spare is filled with 0xFF. | |
* | |
* \param translated Pointer to a TranslatedNandFlash instance. | |
* \param block Logical block number. | |
* \param page Number of page to read inside logical block. | |
* \param data Data area buffer, can be 0. | |
* \param spare Spare area buffer, can be 0. | |
* \return 0 if successful; otherwise returns NandCommon_ERROR_NOMOREBLOCKS | |
*/ | |
uint8_t TranslatedNandFlash_ReadPage( const struct TranslatedNandFlash *translated, uint16_t block, | |
uint16_t page, void *data, void *spare ) | |
{ | |
uint8_t error ; | |
TRACE_INFO("TranslatedNandFlash_ReadPage(B#%d:P#%d)\n\r", block, page); | |
/* If the page to read is in the current block, there is a previous physical | |
block and the page is clean -> read the page in the old block since the | |
new one does not contain meaningful data*/ | |
if ( (block == translated->currentLogicalBlock) && (translated->previousPhysicalBlock != -1) | |
&& (PageIsClean(translated, page)) ) | |
{ | |
TRACE_DEBUG("Reading page from current block\n\r"); | |
return ManagedNandFlash_ReadPage( MANAGED( translated ), translated->previousPhysicalBlock, | |
page, data, spare ) ; | |
} | |
else | |
{ | |
/* Try to read the page from the logical block*/ | |
error = MappedNandFlash_ReadPage(MAPPED(translated), block, page, data, spare); | |
/* Block was not mapped*/ | |
if ( error == NandCommon_ERROR_BLOCKNOTMAPPED ) | |
{ | |
assert( !spare ) ; /* "Cannot read the spare information of an unmapped block\n\r" */ | |
/* Check if a block can be allocated*/ | |
if ( BlockCanBeAllocated( translated ) ) | |
{ | |
/* Return 0xFF in buffers with no error*/ | |
TRACE_DEBUG("Block #%d is not mapped but can be allocated, filling buffer with 0xFF\n\r", block); | |
if (data) | |
{ | |
memset(data, 0xFF, NandFlashModel_GetPageDataSize(MODEL(translated))); | |
} | |
if (spare) | |
{ | |
memset(spare, 0xFF, NandFlashModel_GetPageSpareSize(MODEL(translated))); | |
} | |
} | |
else | |
{ | |
TRACE_ERROR("Block #%d is not mapped and there are no more blocks available\n\r", block); | |
return NandCommon_ERROR_NOMOREBLOCKS; | |
} | |
} | |
/* Error*/ | |
else | |
{ | |
if (error) | |
{ | |
return error; | |
} | |
} | |
} | |
return 0; | |
} | |
/** | |
* \brief Writes the data and/or spare area of a page on a translated nandflash. | |
* Allocates block has needed to keep the wear even between all blocks. | |
* | |
* \param translated Pointer to a TranslatedNandFlash instance. | |
* \param block Logical block number. | |
* \param page Number of page to read inside logical block. | |
* \param data Data area buffer, can be 0. | |
* \param spare Spare area buffer, can be 0. | |
* \return 0 if successful; otherwise returns error code | |
*/ | |
uint8_t TranslatedNandFlash_WritePage( struct TranslatedNandFlash *translated, uint16_t block, | |
uint16_t page, void *data, void *spare ) | |
{ | |
uint8_t allocate = 1; | |
uint8_t error; | |
TRACE_INFO("TranslatedNandFlash_WritePage(B#%d:P#%d)\n\r", block, page); | |
/* A new block must be allocated unless:*/ | |
/* 1. the block is not mapped and there are no more blocks to allocate*/ | |
if (MappedNandFlash_LogicalToPhysical(MAPPED(translated), block) == -1) | |
{ | |
/* Block is not mapped, check if it can be*/ | |
if (!BlockCanBeAllocated(translated)) | |
{ | |
TRACE_ERROR("TranslatedNandFlash_WritePage: Not enough free blocks\n\r"); | |
return NandCommon_ERROR_NOMOREBLOCKS; | |
} | |
TRACE_DEBUG("Allocate because block not mapped\n\r"); | |
} | |
/* or 2. the block to write is the current one and the page to write is clean*/ | |
else | |
{ | |
if (translated->currentLogicalBlock == block) | |
{ | |
if (PageIsClean(translated, page)) | |
{ | |
TRACE_DEBUG("NO allocate because write in current block\n\r"); | |
allocate = 0; | |
} | |
else | |
{ | |
TRACE_DEBUG("Allocate because page DIRTY in current block\n\r"); | |
} | |
} | |
else | |
{ | |
TRACE_DEBUG("Allocate because block is mapped and different from current block\n\r"); | |
} | |
} | |
/* Allocate block if needed*/ | |
if ( allocate ) | |
{ | |
/* Flush current block write (if any) and then allocate block*/ | |
error = TranslatedNandFlash_Flush(translated); | |
if (error) | |
{ | |
return error; | |
} | |
translated->previousPhysicalBlock = MappedNandFlash_LogicalToPhysical( MAPPED(translated), block ) ; | |
TRACE_DEBUG("Previous physical block is now #%d\n\r", translated->previousPhysicalBlock ) ; | |
error = AllocateBlock( translated, block ) ; | |
if ( error ) | |
{ | |
return error; | |
} | |
/* Block becomes the current block with all pages clean*/ | |
translated->currentLogicalBlock = block; | |
MarkAllPagesClean(translated); | |
} | |
/* Start writing page*/ | |
error = MappedNandFlash_WritePage(MAPPED(translated), | |
block, | |
page, | |
data, | |
spare); | |
if ( error ) | |
{ | |
return error; | |
} | |
/* If write went through, mark page as written*/ | |
MarkPageDirty(translated, page); | |
return 0; | |
} | |
/** | |
* \brief Terminates the current write operation by copying all the missing pages from | |
* the previous physical block. | |
* | |
* \param translated Pointer to a TranslatedNandFlash instance. | |
* \return 0 if successful; otherwise returns error code | |
*/ | |
uint8_t TranslatedNandFlash_Flush(struct TranslatedNandFlash *translated) | |
{ | |
uint32_t i; | |
uint8_t error; | |
uint32_t currentPhysicalBlock; | |
/* Check if there is a current block and a previous block*/ | |
if ((translated->currentLogicalBlock == -1) | |
|| (translated->previousPhysicalBlock == -1)) | |
{ | |
return 0; | |
} | |
TRACE_INFO("TranslatedNandFlash_Flush(PB#%d -> LB#%d)\n\r", | |
translated->previousPhysicalBlock, translated->currentLogicalBlock); | |
/* Copy missing pages in the current block*/ | |
currentPhysicalBlock = MappedNandFlash_LogicalToPhysical( | |
MAPPED(translated), | |
translated->currentLogicalBlock); | |
for (i=0; i < NandFlashModel_GetBlockSizeInPages(MODEL(translated)); i++) | |
{ | |
if (PageIsClean(translated, i)) | |
{ | |
TRACE_DEBUG("Copying back page #%d of block #%d\n\r", i, translated->previousPhysicalBlock); | |
/* Copy page*/ | |
error = ManagedNandFlash_CopyPage(MANAGED(translated), | |
translated->previousPhysicalBlock, | |
i, | |
currentPhysicalBlock, | |
i); | |
if (error) | |
{ | |
TRACE_ERROR("FinishCurrentWrite: copy page #%u\n\r", i); | |
return error; | |
} | |
} | |
} | |
translated->currentLogicalBlock = -1; | |
translated->previousPhysicalBlock = -1; | |
return 0; | |
} | |
/** | |
* \brief Erase all blocks in the tranalated area of nand flash. | |
* | |
* \param managed Pointer to a TranslatedNandFlash instance. | |
* \param level Erase level. | |
* \return 0 if successful; otherwise returns error code | |
*/ | |
uint8_t TranslatedNandFlash_EraseAll( | |
struct TranslatedNandFlash *translated, | |
uint8_t level) | |
{ | |
MappedNandFlash_EraseAll(MAPPED(translated), level); | |
if (level > NandEraseDIRTY) | |
{ | |
translated->currentLogicalBlock = -1; | |
translated->previousPhysicalBlock = -1; | |
MarkAllPagesClean(translated); | |
} | |
return 0; | |
} | |
/** | |
* \brief Allocates a free block to save the current logical mapping on it. | |
* | |
* \param translated Pointer to a TranslatedNandFlash instance. | |
* \return 0 if successful; otherwise returns a NandCommon_ERROR code. | |
*/ | |
uint8_t TranslatedNandFlash_SaveLogicalMapping( | |
struct TranslatedNandFlash *translated) | |
{ | |
uint8_t error; | |
uint16_t freeBlock; | |
TRACE_INFO("TranslatedNandFlash_SaveLogicalMapping()\n\r"); | |
/* Save logical mapping in the youngest free block*/ | |
/* Find the youngest block*/ | |
error = ManagedNandFlash_FindYoungestBlock(MANAGED(translated), | |
NandBlockStatus_FREE, | |
&freeBlock); | |
if (error) | |
{ | |
TRACE_ERROR("TranNF_SaveLogicalMapping: No free block\n\r"); | |
return error; | |
} | |
/* Check if this is the last free block, in which case dirty blocks are wiped | |
prior to saving the mapping */ | |
if (ManagedNandFlash_CountBlocks(MANAGED(translated), | |
NandBlockStatus_FREE) == 1) | |
{ | |
TranslatedNandFlash_Flush(translated); | |
error = ManagedNandFlash_EraseDirtyBlocks(MANAGED(translated)); | |
if (error) | |
{ | |
TRACE_ERROR("TranNF_Flush: Could not erase dirty blocks\n\r"); | |
return error; | |
} | |
} | |
/* Save the mapping*/ | |
error = MappedNandFlash_SaveLogicalMapping(MAPPED(translated), freeBlock); | |
if (error) | |
{ | |
TRACE_ERROR("TranNF_Flush: Failed to save mapping in #%d\n\r", freeBlock); | |
return error; | |
} | |
return 0; | |
} | |
/** | |
* \brief Returns the number of available blocks in a translated nandflash. | |
* | |
* \param translated Pointer to a TranslatedNandFlash instance. | |
* \return the number of available blocks in a translated nandflash. | |
*/ | |
uint16_t TranslatedNandFlash_GetDeviceSizeInBlocks( const struct TranslatedNandFlash *translated) | |
{ | |
return ManagedNandFlash_GetDeviceSizeInBlocks(MANAGED(translated)) | |
- MINNUMUNALLOCATEDBLOCKS | |
- ManagedNandFlash_CountBlocks(MANAGED(translated), | |
NandBlockStatus_BAD) | |
- 1; /* Logical mapping block*/ | |
} | |
/** | |
* \brief Returns the number of available pages in a translated nandflash. | |
* | |
* \param translated Pointer to a TranslatedNandFlash instance. | |
* \return the number of available pages in a translated nandflash. | |
*/ | |
uint32_t TranslatedNandFlash_GetDeviceSizeInPages( | |
const struct TranslatedNandFlash *translated) | |
{ | |
return TranslatedNandFlash_GetDeviceSizeInBlocks(translated) | |
* NandFlashModel_GetBlockSizeInPages(MODEL(translated)); | |
} | |
/** | |
* \brief Returns the number of available data bytes in a translated nandflash. | |
* | |
* \param translated Pointer to a TranslatedNandFlash instance. | |
* \return the number of available data bytes in a translated nandflash. | |
*/ | |
unsigned long long TranslatedNandFlash_GetDeviceSizeInBytes( | |
const struct TranslatedNandFlash *translated) | |
{ | |
return TranslatedNandFlash_GetDeviceSizeInPages(translated) | |
* NandFlashModel_GetPageDataSize(MODEL(translated)); | |
} | |