driver/mtd/ftl: support to skip bad block for earse/bread/bwrite

1.support to use dd commond to burn romfs image to nand flash
2.support bare-write nand flash device without file system

Signed-off-by: dongjiuzhu1 <dongjiuzhu1@xiaomi.com>
This commit is contained in:
dongjiuzhu1 2023-04-22 16:55:38 +08:00 committed by Alan Carvalho de Assis
parent 6ff4826492
commit dcf3f9de75

View File

@ -24,6 +24,7 @@
#include <nuttx/config.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <inttypes.h>
@ -74,6 +75,11 @@ struct ftl_struct_s
uint16_t refs; /* Number of references */
bool unlinked; /* The driver has been unlinked */
FAR uint8_t *eblock; /* One, in-memory erase block */
/* The nand block map between logic block and physical block */
FAR off_t *lptable;
off_t lpcount;
};
/****************************************************************************
@ -120,6 +126,81 @@ static const struct block_operations g_bops =
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: ftl_init_map
*
* Description: Allocate logical block and physical block mapping table
* space, and scan the entire nand flash device to establish
* the mapping relationship between logical block and physical
* good block.
*
****************************************************************************/
static int ftl_init_map(FAR struct ftl_struct_s *dev)
{
int j = 0;
int i;
if (dev->lptable == NULL)
{
dev->lptable = kmm_malloc(dev->geo.neraseblocks * sizeof(off_t));
if (dev->lptable == NULL)
{
return -ENOMEM;
}
}
for (i = 0; i < dev->geo.neraseblocks; i++)
{
if (!MTD_ISBAD(dev->mtd, i))
{
dev->lptable[j++] = i;
}
}
dev->lpcount = j;
return 0;
}
/****************************************************************************
* Name: ftl_update_map
*
* Description: Update the lptable from the specified location, remap the
* relationship between logical blocks and physical good blocks.
*
****************************************************************************/
static void ftl_update_map(FAR struct ftl_struct_s *dev, off_t start)
{
DEBUGASSERT(start < dev->lpcount);
memmove(&dev->lptable[start], &dev->lptable[start + 1],
(--dev->lpcount - start) * sizeof(dev->lptable[0]));
}
/****************************************************************************
* Name: ftl_get_cblock
*
* Description: Get the number of consecutive eraseblocks from lptable.
*
****************************************************************************/
static size_t ftl_get_cblock(FAR struct ftl_struct_s *dev, off_t start,
size_t count)
{
off_t i;
count = MIN(count, dev->lpcount - start);
for (i = start; i < start + count - 1; i++)
{
if (dev->lptable[i + 1] - dev->lptable[i] != 1)
{
return i - start + 1;
}
}
return count;
}
/****************************************************************************
* Name: ftl_open
*
@ -172,6 +253,163 @@ static int ftl_close(FAR struct inode *inode)
return OK;
}
/****************************************************************************
* Name: ftl_mtd_bread
*
* Description:
* Read the specified number of sectors. If mtd device is nor flash, it
* can be read once time. If mtd device is nand flash, it can be read one
* block every time and need to skip bad block until the specified number
* of sectors finish.
*
****************************************************************************/
static ssize_t ftl_mtd_bread(FAR struct ftl_struct_s *dev, off_t startblock,
size_t nblocks, FAR uint8_t *buffer)
{
off_t mask = dev->blkper - 1;
size_t nread = nblocks;
ssize_t ret = OK;
if (dev->lptable == NULL)
{
ret = MTD_BREAD(dev->mtd, startblock, nblocks, buffer);
if (ret != nblocks)
{
ferr("ERROR: Read %zu blocks starting at block %" PRIdOFF
" failed: %zd\n", nblocks, startblock, ret);
}
return ret;
}
while (nblocks > 0)
{
off_t startphysicalblock;
off_t starteraseblock;
size_t count;
starteraseblock = startblock / dev->blkper;
if (starteraseblock >= dev->lpcount)
{
ret = -ENOSPC;
break;
}
count = ftl_get_cblock(dev, starteraseblock, (nblocks + mask) & ~mask);
count = MIN(count * dev->blkper, nblocks);
startphysicalblock = dev->lptable[starteraseblock] *
dev->blkper + (startblock & mask);
ret = MTD_BREAD(dev->mtd, startphysicalblock, count, buffer);
if (ret == count || ret == -EUCLEAN)
{
nblocks -= count;
startblock += count;
buffer += count * dev->geo.blocksize;
}
else
{
ftl_update_map(dev, starteraseblock);
break;
}
}
return nblocks != nread ? nread - nblocks : ret;
}
/****************************************************************************
* Name: ftl_mtd_bwrite
*
* Description:
* Write the specified eraseblocks. If mtd device is nor flash, it
* can be written once time. If mtd device is nand flash, it can be write
* one block every time and need to skip bad block until writing success.
*
****************************************************************************/
static ssize_t ftl_mtd_bwrite(FAR struct ftl_struct_s *dev, off_t startblock,
FAR const uint8_t *buffer)
{
off_t starteraseblock;
ssize_t ret;
if (dev->lptable == NULL)
{
ret = MTD_BWRITE(dev->mtd, startblock, dev->blkper, buffer);
if (ret != dev->blkper)
{
ferr("ERROR: Write block %" PRIdOFF " failed: %zd\n",
startblock, ret);
}
return ret;
}
starteraseblock = startblock / dev->blkper;
while (1)
{
if (starteraseblock >= dev->lpcount)
{
return -ENOSPC;
}
ret = MTD_BWRITE(dev->mtd, dev->lptable[starteraseblock] * dev->blkper,
dev->blkper, buffer);
if (ret == dev->blkper)
{
return ret;
}
MTD_MARKBAD(dev->mtd, dev->lptable[starteraseblock]);
ftl_update_map(dev, starteraseblock);
}
}
/****************************************************************************
* Name: ftl_mtd_erase
*
* Description:
* Erase the specified number of sectors. If mtd device is nor flash, it
* can be erased once time. If mtd device is nand flash, it can be erased
* one block every time and need to skip bad block until the specified
* number of sectors finish.
*
****************************************************************************/
static ssize_t ftl_mtd_erase(FAR struct ftl_struct_s *dev, off_t startblock)
{
ssize_t ret;
if (dev->lptable == NULL)
{
ret = MTD_ERASE(dev->mtd, startblock, 1);
if (ret != 1)
{
ferr("ERROR: Erase block %" PRIdOFF " failed: %zd\n",
startblock, ret);
}
return ret;
}
while (1)
{
if (startblock >= dev->lpcount)
{
return -ENOSPC;
}
ret = MTD_ERASE(dev->mtd, dev->lptable[startblock], 1);
if (ret == 1)
{
return ret;
}
MTD_MARKBAD(dev->mtd, dev->lptable[startblock]);
ftl_update_map(dev, startblock);
}
}
/****************************************************************************
* Name: ftl_reload
*
@ -183,18 +421,10 @@ static ssize_t ftl_reload(FAR void *priv, FAR uint8_t *buffer,
off_t startblock, size_t nblocks)
{
struct ftl_struct_s *dev = (struct ftl_struct_s *)priv;
ssize_t nread;
/* Read the full erase block into the buffer */
nread = MTD_BREAD(dev->mtd, startblock, nblocks, buffer);
if (nread != nblocks)
{
ferr("ERROR: Read %zu blocks starting at block %" PRIdOFF
" failed: %zd\n", nblocks, startblock, nread);
}
return nread;
return ftl_mtd_bread(dev, startblock, nblocks, buffer);
}
/****************************************************************************
@ -281,22 +511,18 @@ static ssize_t ftl_flush(FAR void *priv, FAR const uint8_t *buffer,
/* Read the full erase block into the buffer */
rwblock = startblock & ~mask;
nxfrd = MTD_BREAD(dev->mtd, rwblock, dev->blkper, dev->eblock);
nxfrd = ftl_mtd_bread(dev, rwblock, dev->blkper, dev->eblock);
if (nxfrd != dev->blkper)
{
ferr("ERROR: Read erase block %" PRIdOFF " failed: %zd\n",
rwblock, nxfrd);
return -EIO;
}
/* Then erase the erase block */
eraseblock = rwblock / dev->blkper;
ret = MTD_ERASE(dev->mtd, eraseblock, 1);
ret = ftl_mtd_erase(dev, eraseblock);
if (ret < 0)
{
ferr("ERROR: Erase block=%" PRIdOFF "failed: %d\n",
eraseblock, ret);
return ret;
}
@ -320,11 +546,9 @@ static ssize_t ftl_flush(FAR void *priv, FAR const uint8_t *buffer,
/* And write the erase block back to flash */
nxfrd = MTD_BWRITE(dev->mtd, rwblock, dev->blkper, dev->eblock);
nxfrd = ftl_mtd_bwrite(dev, rwblock, dev->eblock);
if (nxfrd != dev->blkper)
{
ferr("ERROR: Write erase block %" PRIdOFF " failed: %zu\n",
rwblock, nxfrd);
return -EIO;
}
@ -349,11 +573,9 @@ static ssize_t ftl_flush(FAR void *priv, FAR const uint8_t *buffer,
/* Erase the erase block */
eraseblock = alignedblock / dev->blkper;
ret = MTD_ERASE(dev->mtd, eraseblock, 1);
ret = ftl_mtd_erase(dev, eraseblock);
if (ret < 0)
{
ferr("ERROR: Erase block=%" PRIdOFF " failed: %d\n",
eraseblock, ret);
return ret;
}
@ -362,11 +584,9 @@ static ssize_t ftl_flush(FAR void *priv, FAR const uint8_t *buffer,
finfo("Write %" PRId32 " bytes into erase block=%" PRIdOFF
" at offset=0\n", dev->geo.erasesize, alignedblock);
nxfrd = MTD_BWRITE(dev->mtd, alignedblock, dev->blkper, buffer);
nxfrd = ftl_mtd_bwrite(dev, alignedblock, buffer);
if (nxfrd != dev->blkper)
{
ferr("ERROR: Write erase block %" PRIdOFF " failed: %zu\n",
alignedblock, nxfrd);
return -EIO;
}
@ -390,22 +610,18 @@ static ssize_t ftl_flush(FAR void *priv, FAR const uint8_t *buffer,
/* Read the full erase block into the buffer */
nxfrd = MTD_BREAD(dev->mtd, alignedblock, dev->blkper, dev->eblock);
nxfrd = ftl_mtd_bread(dev, alignedblock, dev->blkper, dev->eblock);
if (nxfrd != dev->blkper)
{
ferr("ERROR: Read erase block %" PRIdOFF " failed: %zu\n",
alignedblock, nxfrd);
return -EIO;
}
/* Then erase the erase block */
eraseblock = alignedblock / dev->blkper;
ret = MTD_ERASE(dev->mtd, eraseblock, 1);
ret = ftl_mtd_erase(dev, eraseblock);
if (ret < 0)
{
ferr("ERROR: Erase block=%" PRIdOFF "failed: %d\n",
eraseblock, ret);
return ret;
}
@ -418,11 +634,9 @@ static ssize_t ftl_flush(FAR void *priv, FAR const uint8_t *buffer,
/* And write the erase back to flash */
nxfrd = MTD_BWRITE(dev->mtd, alignedblock, dev->blkper, dev->eblock);
nxfrd = ftl_mtd_bwrite(dev, alignedblock, dev->eblock);
if (nxfrd != dev->blkper)
{
ferr("ERROR: Write erase block %" PRIdOFF " failed: %zu\n",
alignedblock, nxfrd);
return -EIO;
}
}
@ -648,12 +862,23 @@ int ftl_initialize_by_path(FAR const char *path, FAR struct mtd_dev_s *mtd)
}
#endif
if (MTD_ISBAD(dev->mtd, 0) != -ENOSYS)
{
ret = ftl_init_map(dev);
if (ret < 0)
{
goto out;
}
}
/* Inode private data is a reference to the FTL device structure */
ret = register_blockdriver(path, &g_bops, 0, dev);
if (ret < 0)
{
ferr("ERROR: register_blockdriver failed: %d\n", -ret);
kmm_free(dev->lptable);
out:
#ifdef FTL_HAVE_RWBUFFER
rwb_uninitialize(&dev->rwb);
#endif