walnux/drivers/mtd/ftl.c
jingfei c3e87dd3d1 drivers/fs: Control the behavior of FTL by passing oflags during the open process.
To save more space (equivalent to the size of one erase sector of
MTD device) and to achieve faster read and write speeds, a method
for direct writing was introduced at the FTL layer.
This can be accomplished simply by using the following oflags during
the open operation:

1. O_DIRECT. when this flag is passed in, ftl internally uses
   the direct write strategy and no read cache is used in ftl;
   otherwise, each write will be executed with the minimum
   granularity of flash erase sector size which means a
   "sector read back - erase sector - write sector" operation
   is performed by using a read cache buffer in heap.

2. O_SYNC. When this flag is passed in, we assume that the
   flash has been erased in advance and no erasure operation
   will be performed internally within ftl. O_SYNC will take
   effect only when both O_DIRECT and O_SYNC are passed in
   simultaneously.

3. For uniformity, we remapped the mount flag in mount.h and
   unified it with the open flag in fcntl.h. The repetitive
   parts of their definitions were reused, and the remaining
   part of the mount flag redefine to the unused bit of open
   flags.

Signed-off-by: jingfei <jingfei@xiaomi.com>
2025-07-16 14:11:41 +08:00

1014 lines
27 KiB
C

/****************************************************************************
* drivers/mtd/ftl.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <fcntl.h>
#include <nuttx/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/mtd/mtd.h>
#include <nuttx/drivers/rwbuffer.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Check if read/write buffer support is needed */
#if defined(CONFIG_FTL_READAHEAD) || defined(CONFIG_FTL_WRITEBUFFER)
# define FTL_HAVE_RWBUFFER 1
#endif
/* The maximum length of the device name paths is the maximum length of a
* name plus 5 for the the length of "/dev/" and a NUL terminator.
*/
#define DEV_NAME_MAX (NAME_MAX + 5)
/****************************************************************************
* Private Types
****************************************************************************/
struct ftl_struct_s
{
FAR struct mtd_dev_s *mtd; /* Contained MTD interface */
struct mtd_geometry_s geo; /* Device geometry */
#ifdef FTL_HAVE_RWBUFFER
struct rwbuffer_s rwb; /* Read-ahead/write buffer support */
#endif
uint16_t blkper; /* R/W blocks per erase block */
uint16_t refs; /* Number of references */
bool unlinked; /* The driver has been unlinked */
FAR uint8_t *eblock; /* One, in-memory erase block */
int oflags;
/* The nand block map between logic block and physical block */
FAR off_t *lptable;
off_t lpcount;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static int ftl_open(FAR struct inode *inode);
static int ftl_close(FAR struct inode *inode);
static ssize_t ftl_reload(FAR void *priv, FAR uint8_t *buffer,
off_t startblock, size_t nblocks);
static ssize_t ftl_read(FAR struct inode *inode, FAR unsigned char *buffer,
blkcnt_t start_sector, unsigned int nsectors);
static ssize_t ftl_flush(FAR void *priv, FAR const uint8_t *buffer,
off_t startblock, size_t nblocks);
static ssize_t ftl_flush_direct(FAR struct ftl_struct_s *dev,
FAR const uint8_t *buffer,
off_t startblock, size_t nblocks);
static ssize_t ftl_write(FAR struct inode *inode,
FAR const unsigned char *buffer, blkcnt_t start_sector,
unsigned int nsectors);
static int ftl_geometry(FAR struct inode *inode,
FAR struct geometry *geometry);
static int ftl_ioctl(FAR struct inode *inode, int cmd,
unsigned long arg);
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
static int ftl_unlink(FAR struct inode *inode);
#endif
/****************************************************************************
* Private Data
****************************************************************************/
static const struct block_operations g_bops =
{
ftl_open, /* open */
ftl_close, /* close */
ftl_read, /* read */
ftl_write, /* write */
ftl_geometry, /* geometry */
ftl_ioctl /* ioctl */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
, ftl_unlink /* unlink */
#endif
};
/****************************************************************************
* 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
*
* Description: Open the block device
*
****************************************************************************/
static int ftl_open(FAR struct inode *inode)
{
FAR struct ftl_struct_s *dev;
DEBUGASSERT(inode->i_private);
dev = inode->i_private;
dev->refs++;
return OK;
}
/****************************************************************************
* Name: ftl_close
*
* Description: close the block device
*
****************************************************************************/
static int ftl_close(FAR struct inode *inode)
{
FAR struct ftl_struct_s *dev;
DEBUGASSERT(inode->i_private);
dev = inode->i_private;
#ifdef CONFIG_FTL_WRITEBUFFER
rwb_flush(&dev->rwb);
#endif
if (--dev->refs == 0 && dev->unlinked)
{
#ifdef FTL_HAVE_RWBUFFER
rwb_uninitialize(&dev->rwb);
#endif
if (dev->eblock)
{
kmm_free(dev->eblock);
}
kmm_free(dev);
}
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;
off_t offset;
size_t count;
starteraseblock = startblock / dev->blkper;
if (starteraseblock >= dev->lpcount)
{
ret = -ENOSPC;
break;
}
offset = startblock & mask;
count = ftl_get_cblock(dev, starteraseblock,
(offset + nblocks + mask) / dev->blkper);
count = MIN(count * dev->blkper - offset, nblocks);
startphysicalblock = dev->lptable[starteraseblock] *
dev->blkper + offset;
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 < 0)
{
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
*
* Description: Read the specified number of sectors
*
****************************************************************************/
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;
/* Read the full erase block into the buffer */
return ftl_mtd_bread(dev, startblock, nblocks, buffer);
}
/****************************************************************************
* Name: ftl_read
*
* Description: Read the specified number of sectors
*
****************************************************************************/
static ssize_t ftl_read(FAR struct inode *inode, unsigned char *buffer,
blkcnt_t start_sector, unsigned int nsectors)
{
FAR struct ftl_struct_s *dev;
finfo("sector: %" PRIuOFF " nsectors: %u\n", start_sector, nsectors);
DEBUGASSERT(inode->i_private);
dev = inode->i_private;
#ifdef FTL_HAVE_RWBUFFER
return rwb_read(&dev->rwb, start_sector, nsectors, buffer);
#else
return ftl_reload(dev, buffer, start_sector, nsectors);
#endif
}
/****************************************************************************
* Name: ftl_flush
*
* Description: Write the specified number of sectors
*
****************************************************************************/
static int ftl_alloc_eblock(FAR struct ftl_struct_s *dev)
{
if (dev->eblock == NULL)
{
/* Allocate one, in-memory erase block buffer */
dev->eblock = kmm_malloc(dev->geo.erasesize);
}
return dev->eblock != NULL ? OK : -ENOMEM;
}
/****************************************************************************
* Name: ftl_flush_direct
*
* Description: Write the specified number of sectors without cache
*
****************************************************************************/
static ssize_t ftl_flush_direct(FAR struct ftl_struct_s *dev,
FAR const uint8_t *buffer,
off_t startblock, size_t nblocks)
{
size_t blocksize = dev->geo.blocksize;
off_t starteraseblock;
off_t offset;
ssize_t ret;
size_t count;
while (nblocks)
{
starteraseblock = startblock / dev->blkper;
offset = startblock & (dev->blkper - 1);
count = MIN(dev->blkper - offset, nblocks);
if (offset == 0 && dev->mtd->erase != NULL && !(dev->oflags & O_SYNC))
{
ret = ftl_mtd_erase(dev, starteraseblock);
if (ret < 0)
{
return ret;
}
}
if (dev->lptable == NULL)
{
ret = MTD_BWRITE(dev->mtd, startblock, count, buffer);
if (ret != count)
{
ferr("ERROR: Write block %"PRIdOFF" failed: %zd\n",
startblock, ret);
return ret;
}
}
else
{
if (starteraseblock >= dev->lpcount)
{
return -ENOSPC;
}
ret = MTD_BWRITE(dev->mtd,
dev->lptable[starteraseblock] * dev->blkper
+ offset, count, buffer);
if (ret != count)
{
MTD_MARKBAD(dev->mtd, dev->lptable[starteraseblock]);
ftl_update_map(dev, starteraseblock);
continue;
}
}
nblocks -= count;
startblock += count;
buffer += count * blocksize;
}
return nblocks;
}
static ssize_t ftl_flush(FAR void *priv, FAR const uint8_t *buffer,
off_t startblock, size_t nblocks)
{
struct ftl_struct_s *dev = (struct ftl_struct_s *)priv;
off_t alignedblock;
off_t mask;
off_t rwblock;
off_t eraseblock;
off_t offset;
size_t remaining;
size_t nxfrd;
int nbytes;
int ret;
if (dev->oflags & O_DIRECT)
{
/* Direct write mode */
return ftl_flush_direct(dev, buffer, startblock, nblocks);
}
/* Get the aligned block. Here is is assumed: (1) The number of R/W blocks
* per erase block is a power of 2, and (2) the erase begins with that same
* alignment.
*/
mask = dev->blkper - 1;
alignedblock = (startblock + mask) & ~mask;
/* Handle partial erase blocks before the first unaligned block */
remaining = nblocks;
if (alignedblock > startblock)
{
/* Check if the write is shorter than to the end of the erase block */
bool short_write = (remaining < (alignedblock - startblock));
ret = ftl_alloc_eblock(dev);
if (ret < 0)
{
ferr("ERROR: Failed to allocate an erase block buffer\n");
return ret;
}
/* Read the full erase block into the buffer */
rwblock = startblock & ~mask;
nxfrd = ftl_mtd_bread(dev, rwblock, dev->blkper, dev->eblock);
if (nxfrd != dev->blkper)
{
return -EIO;
}
/* Then erase the erase block */
eraseblock = rwblock / dev->blkper;
ret = ftl_mtd_erase(dev, eraseblock);
if (ret < 0)
{
return ret;
}
/* Copy the user data at the end of the buffered erase block */
offset = (startblock & mask) * dev->geo.blocksize;
if (short_write)
{
nbytes = remaining * dev->geo.blocksize;
}
else
{
nbytes = dev->geo.erasesize - offset;
}
finfo("Copy %d bytes into erase block=%" PRIdOFF
" at offset=%" PRIdOFF "\n", nbytes, eraseblock, offset);
memcpy(dev->eblock + offset, buffer, nbytes);
/* And write the erase block back to flash */
nxfrd = ftl_mtd_bwrite(dev, rwblock, dev->eblock);
if (nxfrd != dev->blkper)
{
return -EIO;
}
/* Then update for amount written */
if (short_write)
{
remaining = 0;
}
else
{
remaining -= dev->blkper - (startblock & mask);
}
buffer += nbytes;
}
/* How handle full erase pages in the middle */
while (remaining >= dev->blkper)
{
/* Erase the erase block */
eraseblock = alignedblock / dev->blkper;
ret = ftl_mtd_erase(dev, eraseblock);
if (ret < 0)
{
return ret;
}
/* Write a full erase back to flash */
finfo("Write %" PRId32 " bytes into erase block=%" PRIdOFF
" at offset=0\n", dev->geo.erasesize, alignedblock);
nxfrd = ftl_mtd_bwrite(dev, alignedblock, buffer);
if (nxfrd != dev->blkper)
{
return -EIO;
}
/* Then update for amount written */
alignedblock += dev->blkper;
remaining -= dev->blkper;
buffer += dev->geo.erasesize;
}
/* Finally, handle any partial blocks after the last full erase block */
if (remaining > 0)
{
ret = ftl_alloc_eblock(dev);
if (ret < 0)
{
ferr("ERROR: Failed to allocate an erase block buffer\n");
return ret;
}
/* Read the full erase block into the buffer */
nxfrd = ftl_mtd_bread(dev, alignedblock, dev->blkper, dev->eblock);
if (nxfrd != dev->blkper)
{
return -EIO;
}
/* Then erase the erase block */
eraseblock = alignedblock / dev->blkper;
ret = ftl_mtd_erase(dev, eraseblock);
if (ret < 0)
{
return ret;
}
/* Copy the user data at the beginning the buffered erase block */
nbytes = remaining * dev->geo.blocksize;
finfo("Copy %d bytes into erase block=%" PRIdOFF " at offset=0\n",
nbytes, alignedblock);
memcpy(dev->eblock, buffer, nbytes);
/* And write the erase back to flash */
nxfrd = ftl_mtd_bwrite(dev, alignedblock, dev->eblock);
if (nxfrd != dev->blkper)
{
return -EIO;
}
}
return nblocks;
}
/****************************************************************************
* Name: ftl_write
*
* Description: Write (or buffer) the specified number of sectors
*
****************************************************************************/
static ssize_t ftl_write(FAR struct inode *inode,
FAR const unsigned char *buffer,
blkcnt_t start_sector, unsigned int nsectors)
{
struct ftl_struct_s *dev;
finfo("sector: %" PRIuOFF " nsectors: %u\n", start_sector, nsectors);
DEBUGASSERT(inode->i_private);
dev = inode->i_private;
#ifdef FTL_HAVE_RWBUFFER
return rwb_write(&dev->rwb, start_sector, nsectors, buffer);
#else
return ftl_flush(dev, buffer, start_sector, nsectors);
#endif
}
/****************************************************************************
* Name: ftl_geometry
*
* Description: Return device geometry
*
****************************************************************************/
static int ftl_geometry(FAR struct inode *inode,
FAR struct geometry *geometry)
{
FAR struct ftl_struct_s *dev;
finfo("Entry\n");
if (geometry)
{
dev = inode->i_private;
geometry->geo_available = true;
geometry->geo_mediachanged = false;
geometry->geo_writeenabled = true;
geometry->geo_nsectors = dev->geo.neraseblocks * dev->blkper;
geometry->geo_sectorsize = dev->geo.blocksize;
strlcpy(geometry->geo_model, dev->geo.model,
sizeof(geometry->geo_model));
finfo("available: true mediachanged: false writeenabled: %s\n",
geometry->geo_writeenabled ? "true" : "false");
finfo("nsectors: %" PRIuOFF " sectorsize: %u\n",
geometry->geo_nsectors, geometry->geo_sectorsize);
return OK;
}
return -EINVAL;
}
/****************************************************************************
* Name: ftl_ioctl
*
* Description: Return device geometry
*
****************************************************************************/
static int ftl_ioctl(FAR struct inode *inode, int cmd, unsigned long arg)
{
FAR struct ftl_struct_s *dev;
int ret;
finfo("Entry\n");
DEBUGASSERT(inode->i_private);
dev = inode->i_private;
if (cmd == BIOC_DISCARD)
{
#ifdef CONFIG_FTL_READAHEAD
rwb_discard(&dev->rwb);
#endif
}
if (cmd == BIOC_FLUSH)
{
#ifdef CONFIG_FTL_WRITEBUFFER
rwb_flush(&dev->rwb);
#endif
}
/* No other block driver ioctl commands are not recognized by this
* driver. Other possible MTD driver ioctl commands are passed through
* to the MTD driver (unchanged).
*/
ret = MTD_IOCTL(dev->mtd, cmd, arg);
if (ret < 0 && ret != -ENOTTY)
{
ferr("ERROR: MTD ioctl(%04x) failed: %d\n", cmd, ret);
}
return ret;
}
/****************************************************************************
* Name: ftl_unlink
*
* Description: Unlink the device
*
****************************************************************************/
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
static int ftl_unlink(FAR struct inode *inode)
{
FAR struct ftl_struct_s *dev;
DEBUGASSERT(inode->i_private);
dev = inode->i_private;
dev->unlinked = true;
if (dev->refs == 0)
{
#ifdef FTL_HAVE_RWBUFFER
rwb_uninitialize(&dev->rwb);
#endif
if (dev->eblock)
{
kmm_free(dev->eblock);
}
kmm_free(dev);
}
return OK;
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: ftl_initialize_by_path
*
* Description:
* Initialize to provide a block driver wrapper around an MTD interface
*
* Input Parameters:
* path - The block device path.
* mtd - The MTD device that supports the FLASH interface.
*
****************************************************************************/
int ftl_initialize_by_path(FAR const char *path, FAR struct mtd_dev_s *mtd,
int oflags)
{
struct ftl_struct_s *dev;
int ret = -ENOMEM;
/* Sanity check */
if (path == NULL || mtd == NULL)
{
return -EINVAL;
}
finfo("path=\"%s\"\n", path);
/* Allocate a FTL device structure */
dev = kmm_zalloc(sizeof(struct ftl_struct_s));
if (dev)
{
/* Initialize the FTL device structure */
dev->mtd = mtd;
dev->oflags = oflags;
/* Get the device geometry. (casting to uintptr_t first eliminates
* complaints on some architectures where the sizeof long is different
* from the size of a pointer).
*/
ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY,
(unsigned long)((uintptr_t)&dev->geo));
if (ret < 0)
{
ferr("ERROR: MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret);
kmm_free(dev);
return ret;
}
/* Get the number of R/W blocks per erase block */
dev->blkper = dev->geo.erasesize / dev->geo.blocksize;
DEBUGASSERT(dev->blkper * dev->geo.blocksize == dev->geo.erasesize);
/* Configure read-ahead/write buffering */
#ifdef FTL_HAVE_RWBUFFER
dev->rwb.blocksize = dev->geo.blocksize;
dev->rwb.nblocks = dev->geo.neraseblocks * dev->blkper;
dev->rwb.dev = (FAR void *)dev;
dev->rwb.wrflush = ftl_flush;
dev->rwb.rhreload = ftl_reload;
#if defined(CONFIG_FTL_WRITEBUFFER)
dev->rwb.wrmaxblocks = dev->blkper;
dev->rwb.wralignblocks = dev->blkper;
#endif
#ifdef CONFIG_FTL_READAHEAD
dev->rwb.rhmaxblocks = dev->blkper;
#endif
ret = rwb_initialize(&dev->rwb);
if (ret < 0)
{
ferr("ERROR: rwb_initialize failed: %d\n", ret);
kmm_free(dev);
return ret;
}
#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
kmm_free(dev);
}
}
return ret;
}
/****************************************************************************
* Name: ftl_initialize
*
* Description:
* Initialize to provide a block driver wrapper around an MTD interface
*
* Input Parameters:
* minor - The minor device number. The MTD block device will be
* registered as as /dev/mtdblockN where N is the minor number.
* mtd - The MTD device that supports the FLASH interface.
*
****************************************************************************/
int ftl_initialize(int minor, FAR struct mtd_dev_s *mtd)
{
char path[DEV_NAME_MAX];
#ifdef CONFIG_DEBUG_FEATURES
/* Sanity check */
if (minor < 0 || minor > 255)
{
return -EINVAL;
}
#endif
/* Do the real work by ftl_initialize_by_path */
snprintf(path, DEV_NAME_MAX, "/dev/mtdblock%d", minor);
return ftl_initialize_by_path(path, mtd, O_RDWR);
}