diff --git a/drivers/mtd/ftl.c b/drivers/mtd/ftl.c index 59f9d1ac1d..de1952a513 100644 --- a/drivers/mtd/ftl.c +++ b/drivers/mtd/ftl.c @@ -24,6 +24,7 @@ #include +#include #include #include #include @@ -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