/****************************************************************************
 * drivers/mtd/smart.c
 *
 * Sector Mapped Allocation for Really Tiny (SMART) Flash block driver.
 *
 *   Copyright (C) 2013 Ken Pettit. All rights reserved.
 *   Author: Ken Pettit <pettitkd@gmail.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <debug.h>
#include <errno.h>

#include <nuttx/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/mtd/mtd.h>
#include <nuttx/fs/smart.h>

/****************************************************************************
 * Private Definitions
 ****************************************************************************/

#define SMART_STATUS_COMMITTED    0x80
#define SMART_STATUS_RELEASED     0x40
#define SMART_STATUS_SIZEBITS     0x1C
#define SMART_STATUS_VERBITS      0x03
#define SMART_STATUS_VERSION      0x01

#define SMART_SECTSIZE_256        0x00
#define SMART_SECTSIZE_512        0x04
#define SMART_SECTSIZE_1024       0x08
#define SMART_SECTSIZE_2048       0x0C
#define SMART_SECTSIZE_4096       0x10
#define SMART_SECTSIZE_8192       0x14
#define SMART_SECTSIZE_16384      0x18

#define SMART_FMT_STAT_UNKNOWN    0
#define SMART_FMT_STAT_FORMATTED  1
#define SMART_FMT_STAT_NOFMT      2

#define SMART_FMT_POS1            sizeof(struct smart_sect_header_s)
#define SMART_FMT_POS2            (SMART_FMT_POS1 + 1)
#define SMART_FMT_POS3            (SMART_FMT_POS1 + 2)
#define SMART_FMT_POS4            (SMART_FMT_POS1 + 3)

#define SMART_FMT_SIG1            'S'
#define SMART_FMT_SIG2            'M'
#define SMART_FMT_SIG3            'R'
#define SMART_FMT_SIG4            'T'

#define SMART_FMT_VERSION_POS     (SMART_FMT_POS1 + 4)
#define SMART_FMT_NAMESIZE_POS    (SMART_FMT_POS1 + 5)
#define SMART_FMT_ROOTDIRS_POS    (SMART_FMT_POS1 + 6)
#define SMARTFS_FMT_AGING_POS     32

#define SMART_FMT_VERSION           1
#define SMART_PARTNAME_SIZE         4

#define SMART_FIRST_ALLOC_SECTOR    12      /* First logical sector number we will
                                             * use for assignment of requested Alloc
                                             * sectors.  All enries below this are
                                             * reserved (some for root dir entries,
                                             * other for our use, such as format
                                             * sector, etc. */

#if defined(CONFIG_MTD_SMART_READAHEAD) || (defined(CONFIG_DRVR_WRITABLE) && \
    defined(CONFIG_MTD_SMART_WRITEBUFFER))
#  define SMART_HAVE_RWBUFFER 1
#endif

#ifndef CONFIG_MTD_SMART_SECTOR_SIZE
#  define  CONFIG_MTD_SMART_SECTOR_SIZE 1024
#endif

#ifndef offsetof
#define offsetof(type, member) ( (size_t) &( ( (type *) 0)->member))
#endif

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct smart_struct_s
{
  FAR struct mtd_dev_s *mtd;              /* Contained MTD interface */
  struct mtd_geometry_s geo;              /* Device geometry */
  uint16_t              neraseblocks;     /* Number of erase blocks or sub-sectors */
  uint16_t              freesectors;      /* Total number of free sectors */
  uint16_t              mtdBlksPerSector; /* Number of MTD blocks per SMART Sector */
  uint16_t              sectorsPerBlk;    /* Number of sectors per erase block */
  uint16_t              sectorsize;       /* Sector size on device */
  uint16_t              totalsectors;     /* Total number of sectors on device */
  FAR uint16_t         *sMap;             /* Virtual to physical sector map */
  FAR uint8_t          *releasecount;     /* Count of released sectors per erase block */
  FAR uint8_t          *freecount;        /* Count of free sectors per erase block */
  FAR char             *rwbuffer;         /* Our sector read/write buffer */
  char                  partname[SMART_PARTNAME_SIZE]; /* Optional partition name */
  uint8_t               formatversion;    /* Format version on the device */
  uint8_t               formatstatus;     /* Indicates the status of the device format */
  uint8_t               namesize;         /* Length of filenames on this device */
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
  uint8_t               rootdirentries;   /* Number of root directory entries */
  uint8_t               minor;            /* Minor number of the block entry */
#endif
};

#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
struct smart_multiroot_device_s
{
  FAR struct smart_struct_s*    dev;
  uint8_t                       rootdirnum;
};
#endif

struct smart_sect_header_s
{
  uint8_t               logicalsector[2]; /* The logical sector number */
  uint8_t               seq[2];           /* Incrementing sequence number */
  uint8_t               status;           /* Status of this sector:
                                           * Bit 7:   1 = Not commited
                                           *          0 = commited
                                           * Bit 6:   1 = Not released
                                           *          0 = released
                                           * Bit 5:   Reserved - 1
                                           * Bit 4:   Reserved - 1
                                           * Bit 3:   Reserved - 1
                                           * Bit 2:   Reserved - 1
                                           * Bit 1-0: Format version    */
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static int     smart_open(FAR struct inode *inode);
static int     smart_close(FAR struct inode *inode);
static ssize_t smart_reload(struct smart_struct_s *dev, FAR uint8_t *buffer,
                 off_t startblock, size_t nblocks);
static ssize_t smart_read(FAR struct inode *inode, unsigned char *buffer,
                 size_t start_sector, unsigned int nsectors);
#ifdef CONFIG_FS_WRITABLE
static ssize_t smart_write(FAR struct inode *inode, const unsigned char *buffer,
                 size_t start_sector, unsigned int nsectors);
#endif
static int     smart_geometry(FAR struct inode *inode, struct geometry *geometry);
static int     smart_ioctl(FAR struct inode *inode, int cmd, unsigned long arg);

/****************************************************************************
 * Private Data
 ****************************************************************************/

static const struct block_operations g_bops =
{
  smart_open,     /* open     */
  smart_close,    /* close    */
  smart_read,     /* read     */
#ifdef CONFIG_FS_WRITABLE
  smart_write,    /* write    */
#else
  NULL,           /* write    */
#endif
  smart_geometry, /* geometry */
  smart_ioctl     /* ioctl    */
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: smart_open
 *
 * Description: Open the block device
 *
 ****************************************************************************/

static int smart_open(FAR struct inode *inode)
{
  fvdbg("Entry\n");
  return OK;
}

/****************************************************************************
 * Name: smart_close
 *
 * Description: close the block device
 *
 ****************************************************************************/

static int smart_close(FAR struct inode *inode)
{
  fvdbg("Entry\n");
  return OK;
}

/****************************************************************************
 * Name: smart_reload
 *
 * Description:  Read the specified numer of sectors
 *
 ****************************************************************************/

static ssize_t smart_reload(struct smart_struct_s *dev, FAR uint8_t *buffer,
                            off_t startblock, size_t nblocks)
{
  ssize_t nread;
  ssize_t mtdBlocks, mtdStartBlock;

  /* Calculate the number of MTD blocks to read */

  mtdBlocks = nblocks * dev->mtdBlksPerSector;

  /* Calculate the first MTD block number */

  mtdStartBlock = startblock * dev->mtdBlksPerSector;

  /* Read the full erase block into the buffer */

  fdbg("Read %d blocks starting at block %d\n", mtdBlocks, mtdStartBlock);
  nread   = MTD_BREAD(dev->mtd, mtdStartBlock, mtdBlocks, buffer);
  if (nread != mtdBlocks)
    {
      fdbg("Read %d blocks starting at block %d failed: %d\n",
           nblocks, startblock, nread);
    }

  return nread;
}

/****************************************************************************
 * Name: smart_read
 *
 * Description:  Read the specified numer of sectors
 *
 ****************************************************************************/

static ssize_t smart_read(FAR struct inode *inode, unsigned char *buffer,
                          size_t start_sector, unsigned int nsectors)
{
  struct smart_struct_s *dev;

  fvdbg("SMART: sector: %d nsectors: %d\n", start_sector, nsectors);

  DEBUGASSERT(inode && inode->i_private);
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
  dev = ((struct smart_multiroot_device_s*) inode->i_private)->dev;
#else
  dev = (struct smart_struct_s *)inode->i_private;
#endif
  return smart_reload(dev, buffer, start_sector, nsectors);
}

/****************************************************************************
 * Name: smart_write
 *
 * Description: Write (or buffer) the specified number of sectors
 *
 ****************************************************************************/

#ifdef CONFIG_FS_WRITABLE
static ssize_t smart_write(FAR struct inode *inode, const unsigned char *buffer,
                           size_t start_sector, unsigned int nsectors)
{
  struct smart_struct_s *dev;
  off_t  alignedblock;
  off_t  mask;
  off_t  blkstowrite;
  off_t  offset;
  off_t  nextblock;
  off_t  mtdBlksPerErase;
  off_t  eraseblock;
  size_t remaining;
  size_t nxfrd;
  int    ret;
  off_t  mtdstartblock, mtdblockcount;

  fvdbg("sector: %d nsectors: %d\n", start_sector, nsectors);

  DEBUGASSERT(inode && inode->i_private);
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
  dev = ((struct smart_multiroot_device_s*) inode->i_private)->dev;
#else
  dev = (struct smart_struct_s *)inode->i_private;
#endif

  /* I think maybe we need to lock on a mutex here */

  /* 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->sectorsPerBlk - 1;
  alignedblock = ((start_sector + mask) & ~mask) * dev->mtdBlksPerSector;

  /* Convert SMART blocks into MTD blocks */

  mtdstartblock = start_sector * dev->mtdBlksPerSector;
  mtdblockcount = nsectors * dev->mtdBlksPerSector;
  mtdBlksPerErase = dev->mtdBlksPerSector * dev->sectorsPerBlk;

  fvdbg("mtdsector: %d mtdnsectors: %d\n", mtdstartblock, mtdblockcount);

  /* Start at first block to be written */

  remaining = mtdblockcount;
  nextblock = mtdstartblock;
  offset = 0;

  /* Loop for all blocks to be written */

  while (remaining > 0)
    {
      /* If this is an aligned block, then erase the block */

      if (alignedblock == nextblock)
        {
          /* Erase the erase block */

          eraseblock = alignedblock / mtdBlksPerErase;
          ret = MTD_ERASE(dev->mtd, eraseblock, 1);
          if (ret < 0)
            {
              fdbg("Erase block=%d failed: %d\n", eraseblock, ret);

              /* Unlock the mutex if we add one */

              return ret;
            }
        }

      /* Calculate the number of blocks to write. */

      blkstowrite = mtdBlksPerErase;
      if (nextblock != alignedblock)
        {
          blkstowrite = alignedblock - nextblock;
        }

      if (blkstowrite > remaining)
        {
          blkstowrite = remaining;
        }

      /* Try to write to the sector. */

      fdbg("Write MTD block %d from offset %d\n", nextblock, offset);
      nxfrd = MTD_BWRITE(dev->mtd, nextblock, blkstowrite, &buffer[offset]);
      if (nxfrd != blkstowrite)
        {
          /* The block is not empty!!  What to do? */

          fdbg("Write block %d failed: %d.\n", nextblock, nxfrd);

          /* Unlock the mutex if we add one */

          return -EIO;
        }

      /* Then update for amount written */

      nextblock += blkstowrite;
      remaining -= blkstowrite;
      offset += blkstowrite * dev->geo.blocksize;
      alignedblock += mtdBlksPerErase;
    }

  return nsectors;
}
#endif /* CONFIG_FS_WRITABLE */

/****************************************************************************
 * Name: smart_geometry
 *
 * Description: Return device geometry
 *
 ****************************************************************************/

static int smart_geometry(FAR struct inode *inode, struct geometry *geometry)
{
  struct smart_struct_s *dev;
  uint32_t  erasesize;

  fvdbg("Entry\n");

  DEBUGASSERT(inode);
  if (geometry)
    {
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
      dev = ((struct smart_multiroot_device_s*) inode->i_private)->dev;
#else
      dev = (struct smart_struct_s *)inode->i_private;
#endif
      geometry->geo_available     = true;
      geometry->geo_mediachanged  = false;
#ifdef CONFIG_FS_WRITABLE
      geometry->geo_writeenabled  = true;
#else
      geometry->geo_writeenabled  = false;
#endif

      erasesize = dev->geo.erasesize;
      if (erasesize == 0)
        {
          erasesize = 65536;
        }

      geometry->geo_nsectors      = dev->geo.neraseblocks * erasesize /
                                     dev->sectorsize;
      geometry->geo_sectorsize    = dev->sectorsize;

      fvdbg("available: true mediachanged: false writeenabled: %s\n",
            geometry->geo_writeenabled ? "true" : "false");
      fvdbg("nsectors: %d sectorsize: %d\n",
            geometry->geo_nsectors, geometry->geo_sectorsize);

      return OK;
    }

  return -EINVAL;
}

/****************************************************************************
 * Name: smart_setsectorsize
 *
 * Description: Sets the device's sector size and recalculates sector size
 *              dependant variables.
 *
 ****************************************************************************/

static int smart_setsectorsize(struct smart_struct_s *dev, uint16_t size)
{
  uint32_t  erasesize;
  uint32_t  totalsectors;

  /* Validate the size isn't zero so we don't divide by zero below */

  if (size == 0)
    {
      size = CONFIG_MTD_SMART_SECTOR_SIZE;
    }

  erasesize = dev->geo.erasesize;
  dev->neraseblocks = dev->geo.neraseblocks;

  /* Most FLASH devices have erase size of 64K, but geo.erasesize is only
   * 16 bits, so it will be zero
   */

  if (erasesize == 0)
    {
      erasesize = 65536;
    }

  dev->sectorsize = size;
  dev->mtdBlksPerSector = dev->sectorsize / dev->geo.blocksize;
  dev->sectorsPerBlk = erasesize / dev->sectorsize;

  /* Release any existing rwbuffer and sMap */

  if (dev->sMap != NULL)
    {
      kfree(dev->sMap);
    }

  if (dev->rwbuffer != NULL)
    {
      kfree(dev->rwbuffer);
    }

  /* Allocate a virtual to physical sector map buffer.  Also allocate
   * the storage space for releasecount and freecounts.
   */

  totalsectors = dev->neraseblocks * dev->sectorsPerBlk;
  dev->totalsectors = (uint16_t) totalsectors;

  dev->sMap = (uint16_t *) kmalloc(totalsectors * sizeof(uint16_t) +
              (dev->neraseblocks << 1));
  if (!dev->sMap)
    {
      fdbg("Error allocating SMART virtual map buffer\n");
      kfree(dev);
      return -EINVAL;
    }

  dev->releasecount = (uint8_t *) dev->sMap + (totalsectors * sizeof(uint16_t));
  dev->freecount = dev->releasecount + dev->neraseblocks;

  /* Allocate a read/write buffer */

  dev->rwbuffer = (char *) kmalloc(size);
  if (!dev->rwbuffer)
    {
      fdbg("Error allocating SMART read/write buffer\n");
      kfree(dev->sMap);
      kfree(dev);
      return -EINVAL;
    }

  return OK;
}

/****************************************************************************
 * Name: smart_bytewrite
 *
 * Description: Writes a non-page size count of bytes to the underlying
 *              MTD device.  If the MTD driver supports a direct impl of
 *              write, then it uses it, otherwise it does a read-modify-write
 *              and depends on the architecture of the flash to only program
 *              bits that acutally changed.
 *
 ****************************************************************************/

static ssize_t smart_bytewrite(struct smart_struct_s *dev, size_t offset,
        int nbytes, const uint8_t *buffer)
{
  ssize_t       ret;

#ifdef CONFIG_MTD_BYTE_WRITE
  /* Check if the underlying MTD device supports write */

  if (dev->mtd->write != NULL)
    {
      /* Use the MTD's write method to write individual bytes */

      ret = dev->mtd->write(dev->mtd, offset, nbytes, buffer);
    }
  else
#endif
    {
      /* Perform block-based read-modify-write */

      uint32_t  startblock;
      uint16_t  nblocks;

      /* First calculate the start block and number of blocks affected */

      startblock = offset / dev->geo.blocksize;
      nblocks    = (offset - startblock * dev->geo.blocksize + nbytes +
                    dev->geo.blocksize-1) / dev->geo.blocksize;

      DEBUGASSERT(nblocks <= dev->mtdBlksPerSector);

      /* Do a block read */

      ret = MTD_BREAD(dev->mtd, startblock, nblocks, (uint8_t *) dev->rwbuffer);
      if (ret < 0)
        {
          fdbg("Error %d reading from device\n", -ret);
          goto errout;
        }

      /* Modify the data */

      memcpy(&dev->rwbuffer[offset - startblock * dev->geo.blocksize], buffer, nbytes);

      /* Write the data back to the device */

      ret = MTD_BWRITE(dev->mtd, startblock, nblocks, (uint8_t *) dev->rwbuffer);
      if (ret < 0)
        {
          fdbg("Error %d writing to device\n", -ret);
          goto errout;
        }
    }

  ret = nbytes;

errout:
  return ret;
}

/****************************************************************************
 * Name: smart_scan
 *
 * Description: Performs a scan of the MTD device searching for format
 *              information and fills in logical sector mapping, freesector
 *              count, etc.
 *
 ****************************************************************************/

static int smart_scan(struct smart_struct_s *dev)
{
  int       sector;
  int       ret;
  int       offset;
  uint16_t  totalsectors;
  uint16_t  sectorsize;
  uint16_t  logicalsector;
  uint16_t  seq1;
  uint16_t  seq2;
  size_t    readaddress;
  struct    smart_sect_header_s header;
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
  int       x;
  char      devname[22];
  struct    smart_multiroot_device_s *rootdirdev;
#endif

  fvdbg("Entry\n");

  /* Read the 1st header from the device.  We always keep the
   * 1st sector's header's sectorsize field accurate, even
   * after we erase an MTD block/sector */

  ret = MTD_READ(dev->mtd, 0, sizeof(struct smart_sect_header_s),
                 (uint8_t *) &header);
  if (ret != sizeof(struct smart_sect_header_s))
    {
      goto err_out;
    }

  /* Now set the sectorsize and other sectorsize derived variables */

  if (header.status == CONFIG_SMARTFS_ERASEDSTATE)
    {
      sectorsize = CONFIG_MTD_SMART_SECTOR_SIZE;
    }
  else
    {
      sectorsize = (header.status & SMART_STATUS_SIZEBITS) << 7;
    }

  ret = smart_setsectorsize(dev, sectorsize);
  if (ret != OK)
    {
      goto err_out;
    }

  /* Initialize the device variables */

  totalsectors = dev->neraseblocks * dev->sectorsPerBlk;
  dev->formatstatus = SMART_FMT_STAT_NOFMT;
  dev->freesectors = totalsectors;

  /* Initialize the freecount and releasecount arrays */

  for (sector = 0; sector < dev->neraseblocks; sector++)
    {
      dev->freecount[sector] = dev->sectorsPerBlk;
      dev->releasecount[sector] = 0;
    }

  /* Initialize the sector map */

  for (sector = 0; sector < totalsectors; sector++)
    {
      dev->sMap[sector] = -1;
    }

  /* Now scan the MTD device */

  for (sector = 0; sector < totalsectors; sector++)
    {
      fvdbg("Scan sector %d\n", sector);

      /* Calculate the read address for this sector */

      readaddress = sector * dev->mtdBlksPerSector * dev->geo.blocksize;

      /* Read the header for this sector */

      ret = MTD_READ(dev->mtd, readaddress, sizeof(struct smart_sect_header_s),
                     (uint8_t *) &header);
      if (ret != sizeof(struct smart_sect_header_s))
        {
          goto err_out;
        }

      /* Get the logical sector number for this physical sector */

      logicalsector = *((uint16_t *) header.logicalsector);
#if CONFIG_SMARTFS_ERASEDSTATE == 0x00
      if (logicalsector == 0)
        {
          logicalsector = -1;
        }
#endif

      /* Test if this sector has been committed */

      if ((header.status & SMART_STATUS_COMMITTED) ==
              (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED))
        {
          continue;
        }

      /* This block is commited, therefore not free.  Update the
       * erase block's freecount.
       */

      dev->freecount[sector / dev->sectorsPerBlk]--;
      dev->freesectors--;

      /* Test if this sector has been release and if it has,
       * update the erase block's releasecount.
       */

      if ((header.status & SMART_STATUS_RELEASED) !=
              (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_RELEASED))
        {
          dev->releasecount[sector / dev->sectorsPerBlk]++;
          continue;
        }

      if ((header.status & SMART_STATUS_VERBITS) != SMART_STATUS_VERSION)
        {
          continue;
        }

      /* Validate the logical sector number is in bounds */

      if (logicalsector >= totalsectors)
        {
          /* Error in logical sector read from the MTD device */

          fdbg("Invalid logical sector %d at physical %d.\n",
               logicalsector, sector);
          continue;
        }

      /* If this is logical sector zero, then read in the signature
       * information to validate the format signature.
       */

      if (logicalsector == 0)
        {
          /* Read the sector data */

          ret = MTD_READ(dev->mtd, readaddress, 32,
                         (uint8_t*) dev->rwbuffer);
          if (ret != 32)
            {
              fdbg("Error reading physical sector %d.\n", sector);
              goto err_out;
            }

          /* Validate the format signature */

          if (dev->rwbuffer[SMART_FMT_POS1] != SMART_FMT_SIG1 ||
              dev->rwbuffer[SMART_FMT_POS2] != SMART_FMT_SIG2 ||
              dev->rwbuffer[SMART_FMT_POS3] != SMART_FMT_SIG3 ||
              dev->rwbuffer[SMART_FMT_POS4] != SMART_FMT_SIG4)
           {
             /* Invalid signature on a sector claiming to be sector 0!
              * What should we do?  Release it?*/

             continue;
           }

          /* TODO: May want to validate / save the erase block aging info */

          /* Mark the volume as formatted and set the sector size */

          dev->formatstatus = SMART_FMT_STAT_FORMATTED;
          dev->namesize = dev->rwbuffer[SMART_FMT_NAMESIZE_POS];
          dev->formatversion = dev->rwbuffer[SMART_FMT_VERSION_POS];

#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
          dev->rootdirentries = dev->rwbuffer[SMART_FMT_ROOTDIRS_POS];

          /* If rootdirentries is greater than 1, then we need to register
           * additional block devices.
           */

          for (x = 1; x < dev->rootdirentries; x++)
            {
              if (dev->partname[0] != '\0')
                {
                  snprintf(dev->rwbuffer, sizeof(devname), "/dev/smart%d%sd%d",
                          dev->minor, dev->partname, x+1);
                }
              else
                {
                  snprintf(devname, sizeof(devname), "/dev/smart%dd%d", dev->minor,
                           x + 1);
                }

              /* Inode private data is a reference to a struct containing
               * the SMART device structure and the root directory number.
               */

              rootdirdev = (struct smart_multiroot_device_s*) kmalloc(sizeof(*rootdirdev));
              if (rootdirdev == NULL)
                {
                  fdbg("Memory alloc failed\n");
                  ret = -ENOMEM;
                  goto err_out;
                }

              /* Populate the rootdirdev */

              rootdirdev->dev = dev;
              rootdirdev->rootdirnum = x;
              ret = register_blockdriver(dev->rwbuffer, &g_bops, 0, rootdirdev);

              /* Inode private data is a reference to the SMART device structure */

              ret = register_blockdriver(devname, &g_bops, 0, rootdirdev);
            }
#endif
        }

      /* Test for duplicate logical sectors on the device */

      if (dev->sMap[logicalsector] != 0xFFFF)
        {
          /* Uh-oh, we found more than 1 physical sector claiming to be
           * the * same logical sector.  Use the sequence number information
           * to resolve who wins.
           */

          uint16_t loser;

          seq2 = *((uint16_t *) header.seq);

          /* We must re-read the 1st physical sector to get it's seq number */

          readaddress = dev->sMap[logicalsector]  * dev->mtdBlksPerSector * dev->geo.blocksize;
          ret = MTD_READ(dev->mtd, readaddress, sizeof(struct smart_sect_header_s),
                  (uint8_t *) &header);
          if (ret != sizeof(struct smart_sect_header_s))
            {
              goto err_out;
            }

          seq1 = *((uint16_t *) header.seq);

          /* Now determine who wins */

          if (seq1 > 0xFFF0 && seq2 < 10)
            {
              /* Seq 2 is the winner ... we assume it wrapped */

              loser = dev->sMap[logicalsector];
              dev->sMap[logicalsector] = sector;
            }
          else if (seq2 > seq1)
            {
              /* Seq 2 is bigger, so it's the winner */

              loser = dev->sMap[logicalsector];
              dev->sMap[logicalsector] = sector;
            }
          else
            {
              /* We keep the original mapping and seq2 is the loser */

              loser = sector;
            }

          /* Now release the loser sector */

          readaddress = loser  * dev->mtdBlksPerSector * dev->geo.blocksize;
          ret = MTD_READ(dev->mtd, readaddress, sizeof(struct smart_sect_header_s),
                  (uint8_t *) &header);
          if (ret != sizeof(struct smart_sect_header_s))
            {
              goto err_out;
            }

#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF
          header.status &= ~SMART_STATUS_RELEASED;
#else
          header.status |= SMART_STATUS_RELEASED;
#endif
          offset = readaddress + offsetof(struct smart_sect_header_s, status);
          ret = smart_bytewrite(dev, offset, 1, &header.status);
          if (ret < 0)
            {
              fdbg("Error %d releasing duplicate sector\n", -ret);
              goto err_out;
            }
        }

      /* Update the logical to physical sector map */

      dev->sMap[logicalsector] = sector;
    }

  fdbg("SMART Scan\n");
  fdbg("   Erase size:   %10d\n", dev->sectorsPerBlk * dev->sectorsize);
  fdbg("   Erase count:  %10d\n", dev->neraseblocks);
  fdbg("   Sect/block:   %10d\n", dev->sectorsPerBlk);
  fdbg("   MTD Blk/Sect: %10d\n", dev->mtdBlksPerSector);

  ret = OK;

err_out:
  return ret;
}

/****************************************************************************
 * Name: smart_getformat
 *
 * Description:  Populates the SMART format structure based on the format
 *               information for the inode.
 *
 ****************************************************************************/

#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
static inline int smart_getformat(struct smart_struct_s *dev,
                                  struct smart_format_s *fmt,
                                  uint8_t rootdirnum)
#else
static inline int smart_getformat(struct smart_struct_s *dev,
                                  struct smart_format_s *fmt)
#endif
{
  int ret;
  int x;

  fvdbg("Entry\n");
  DEBUGASSERT(fmt);

  /* Test if we know the format status or not.  If we don't know the
   * status, then we must perform a scan of the device to search
   * for the format marker
   */

  if (dev->formatstatus != SMART_FMT_STAT_FORMATTED)
    {
      /* Perform the scan */

      ret = smart_scan(dev);

      if (ret != OK)
        {
          goto err_out;
        }
    }

  /* Now fill in the structure */

  if (dev->formatstatus == SMART_FMT_STAT_FORMATTED)
    {
      fmt->flags = SMART_FMT_ISFORMATTED;
    }
  else
    {
      fmt->flags = 0;
    }

  fmt->sectorsize = dev->sectorsize;
  fmt->availbytes = dev->sectorsize - sizeof(struct smart_sect_header_s);
  fmt->nsectors = dev->neraseblocks * dev->sectorsPerBlk;
  fmt->nfreesectors = dev->freesectors;
  fmt->namesize = dev->namesize;
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
  fmt->nrootdirentries = dev->rootdirentries;
  fmt->rootdirnum = rootdirnum;
#endif

  /* Add the released sectors to the reported free sector count */

  for (x = 0; x < dev->neraseblocks; x++)
    {
      fmt->nfreesectors += dev->releasecount[x];
    }

  /* Subtract the reserved sector count */

  fmt->nfreesectors -= dev->sectorsPerBlk + 4;

  ret = OK;

err_out:
  return ret;
}

/****************************************************************************
 * Name: smart_llformat
 *
 * Description:  Performs a low-level format of the flash device.  This
 *               involves erasing the device and writing a valid sector
 *               zero (logical) with proper format signature.
 *
 ****************************************************************************/

#ifdef CONFIG_FS_WRITABLE
static inline int smart_llformat(struct smart_struct_s *dev, unsigned long arg)
{
  struct    smart_sect_header_s  *sectorheader;
  size_t    wrcount;
  size_t    totalsectors;
  int       x;
  int       ret;
  uint8_t   sectsize;

  fvdbg("Entry\n");

  /* Erase the MTD device */

  ret = MTD_IOCTL(dev->mtd, MTDIOC_BULKERASE, 0);
  if (ret < 0)
    {
      return ret;
    }

  /* Now construct a logical sector zero header to write to the device.
   * We fill it with zero so when we add sector aging, all the sector
   * ages will already be initialized to zero without needing special
   * logic to deal with a 0xFF erased-state value.
   */

  sectorheader = (struct smart_sect_header_s *) dev->rwbuffer;
  memset(dev->rwbuffer, 0, dev->sectorsize);
  memset(dev->rwbuffer, CONFIG_SMARTFS_ERASEDSTATE, SMARTFS_FMT_AGING_POS);
  *((uint16_t *) sectorheader->seq) = 0;

  sectsize = (CONFIG_MTD_SMART_SECTOR_SIZE >> 9) << 2;
#if ( CONFIG_SMARTFS_ERASEDSTATE == 0xFF )
  *((uint16_t *) sectorheader->logicalsector) = 0;
  sectorheader->status = (uint8_t) ~(SMART_STATUS_COMMITTED | SMART_STATUS_VERBITS |
          SMART_STATUS_SIZEBITS) | SMART_STATUS_VERSION |
          sectsize;
#else
  *((uint16_t *) sectorheader->logicalsector) = 0xFFFF;
  sectorheader->status = (uint8_t) (SMART_STATUS_COMMITTED | SMART_STATUS_VERSION |
          sectsize);
#endif

  /* Now add the format signature to the sector */

  dev->rwbuffer[SMART_FMT_POS1] = SMART_FMT_SIG1;
  dev->rwbuffer[SMART_FMT_POS2] = SMART_FMT_SIG2;
  dev->rwbuffer[SMART_FMT_POS3] = SMART_FMT_SIG3;
  dev->rwbuffer[SMART_FMT_POS4] = SMART_FMT_SIG4;

  dev->rwbuffer[SMART_FMT_VERSION_POS] = SMART_FMT_VERSION;
  dev->rwbuffer[SMART_FMT_NAMESIZE_POS] = CONFIG_SMARTFS_MAXNAMLEN;

  /* Record the number of root directory entries we have */

  dev->rwbuffer[SMART_FMT_ROOTDIRS_POS] = (uint8_t) arg;

  /* Write the sector to the flash */

  wrcount = MTD_BWRITE(dev->mtd, 0, dev->mtdBlksPerSector,
          (uint8_t *) dev->rwbuffer);
  if (wrcount != dev->mtdBlksPerSector)
    {
      /* The block is not empty!!  What to do? */

      fdbg("Write block 0 failed: %d.\n", wrcount);

      /* Unlock the mutex if we add one */

      return -EIO;
    }

  /* Now initialize our internal control variables */

  ret = smart_setsectorsize(dev, CONFIG_MTD_SMART_SECTOR_SIZE);
  if (ret != OK)
    {
      return ret;
    }

  dev->formatstatus = SMART_FMT_STAT_UNKNOWN;
  dev->freesectors = dev->neraseblocks * dev->sectorsPerBlk - 1;
  for (x = 0; x < dev->neraseblocks; x++)
    {
      /* Initialize the released and free counts */

      dev->releasecount[x] = 0;
      dev->freecount[x] = dev->sectorsPerBlk;
    }

  /* Account for the format sector */

  dev->freecount[0]--;

  /* Now initialize the logical to physical sector map */

  dev->sMap[0] = 0;     /* Logical sector zero = physical sector 0 */

  totalsectors = dev->neraseblocks * dev->sectorsPerBlk;
  for (x = 1; x < totalsectors; x++)
    {
      /* Mark all other logical sectors as non-existant */

      dev->sMap[x] = -1;
    }

#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS

  /* Un-register any extra directory device entries */

  for (x = 2; x < 8; x++)
    {
      snprintf(dev->rwbuffer, 18, "/dev/smart%dd%d", dev->minor, x);
      unregister_blockdriver(dev->rwbuffer);
    }
#endif

  return OK;
}
#endif /* CONFIG_FS_WRITABLE */

/****************************************************************************
 * Name: smart_findfreephyssector
 *
 * Description:  Finds a free physical sector based on free and released
 *               count logic, taking into account reserved sectors.
 *
 ****************************************************************************/

static int smart_findfreephyssector(struct smart_struct_s *dev)
{
  uint16_t  allocfreecount;
  uint16_t  allocblock;
  uint16_t  physicalsector;
  uint16_t  x;
  uint32_t  readaddr;
  struct    smart_sect_header_s header;
  int       ret;

  /* Determine which erase block we should allocate the new
   * sector from. This is based on the number of free sectors
   * available in each erase block. */

  allocfreecount = 0;
  allocblock = 0xFFFF;
  physicalsector = 0xFFFF;
  for (x = 0; x < dev->neraseblocks; x++)
    {
      /* Test if this block has more free blocks than the
       * currently selected block */

      if (dev->freecount[x] > allocfreecount)
        {
          /* Assign this block to alloc from */

          allocblock = x;
          allocfreecount = dev->freecount[x];
        }
    }

  /* Check if we found an allocblock. */

  if (allocblock == 0xFFFF)
    {
      /* No free sectors found!  Bug? */
      return -EIO;
    }

  /* Now find a free physical sector within this selected
   * erase block to allocate. */

  for (x = allocblock * dev->sectorsPerBlk;
          x < (allocblock+1) * dev->sectorsPerBlk; x++)
    {
      /* Check if this physical sector is available */

      readaddr = x * dev->mtdBlksPerSector * dev->geo.blocksize;
      ret = MTD_READ(dev->mtd, readaddr, sizeof(struct smart_sect_header_s),
              (uint8_t *) &header);
      if (ret != sizeof(struct smart_sect_header_s))
        {
          fvdbg("Error reading phys sector %d\n", physicalsector);
          return -EIO;
        }

      if ((*((uint16_t *) header.logicalsector) == 0xFFFF) &&
          (*((uint16_t *) header.seq) == 0xFFFF) &&
          ((header.status & SMART_STATUS_COMMITTED) ==
           (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED)))
        {
          physicalsector = x;
          break;
        }
    }

  return physicalsector;
}

/****************************************************************************
 * Name: smart_garbagecollect
 *
 * Description:  Performs garbage collection if needed.  This is determined
 *               by the count of released sectors relative to free and
 *               total sectors.
 *
 ****************************************************************************/

#ifdef CONFIG_FS_WRITABLE
static int smart_garbagecollect(struct smart_struct_s *dev)
{
  uint16_t  releasedsectors;
  uint16_t  collectblock;
  uint16_t  releasemax;
  uint16_t  newsector;
  bool      collect = TRUE;
  int       x;
  int       ret;
  size_t    offset;
  struct    smart_sect_header_s *header;
  uint8_t   newstatus;

  while (collect)
    {
      collect = FALSE;

      /* Calculate the number of released sectors on the device */

      releasedsectors = 0;
      collectblock = 0xFFFF;
      releasemax = 0;
      for (x = 0; x < dev->neraseblocks; x++)
        {
          releasedsectors += dev->releasecount[x];
          if (dev->releasecount[x] > releasemax)
            {
              releasemax = dev->releasecount[x];
              collectblock = x;
            }
        }

      /* Test if the released sectors count is greater than the
       * free sectors.  If it is, then we will do garbage collection.
       */

      if (releasedsectors > dev->freesectors)
        collect = TRUE;

      /* Test if we have more reached our reserved free sector limit */

      if (dev->freesectors <= (dev->sectorsPerBlk << 0) + 4)
        collect = TRUE;

      /* Test if we need to garbage collect */

      if (collect)
        {
          if (collectblock == 0xFFFF)
            {
              /* Need to collect, but no sectors with released blocks! */

              ret = -ENOSPC;
              goto errout;
            }

          fdbg("Collecting block %d, free=%d released=%d\n",
              collectblock, dev->freecount[collectblock],
              dev->releasecount[collectblock]);

          if (dev->freecount[collectblock] == 6)
            {
              fdbg("here!\n");
            }

          /* Perform collection on block with the most released sectors.
           * First mark the block as having no free sectors so we don't
           * try to move sectors into the block we are trying to erase.
           */

          dev->freecount[collectblock] = 0;

          /* Next move all live data in the block to a new home. */

          for (x = collectblock * dev->sectorsPerBlk; x <
             (collectblock + 1) * dev->sectorsPerBlk; x++)
            {
              /* Read the next sector from this erase block */

              ret = MTD_BREAD(dev->mtd, x * dev->mtdBlksPerSector,
                  dev->mtdBlksPerSector, (uint8_t *) dev->rwbuffer);
              if (ret != dev->mtdBlksPerSector)
                {
                  fdbg("Error reading sector %d\n", x);
                  ret = -EIO;
                  goto errout;
                }

              /* Test if if the block is in use */

              header = (struct smart_sect_header_s *) dev->rwbuffer;
              if (((header->status & SMART_STATUS_COMMITTED) ==
                  (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED)) ||
                  ((header->status & SMART_STATUS_RELEASED) !=
                   (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_RELEASED)))
                {
                  /* This sector doesn't have live data (free or released).
                   * just continue to the next sector and don't move it.
                   */

                  continue;
                }

              /* Find a new sector where it can live, NOT in this erase block */

              newsector = smart_findfreephyssector(dev);
              if (newsector == 0xFFFF)
                {
                  /* Unable to find a free sector!!! */

                  fdbg("Can't find a free sector for relocation\n");
                  ret = -EIO;
                  goto errout;
                }

              /* Increment the sequence number and clear the "commit" flag */

              (*((uint16_t *) header->seq))++;
              if (*((uint16_t *) header->seq) == 0xFFFF)
                {
                  *((uint16_t *) header->seq) = 1;
                }
#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF
              header->status |= SMART_STATUS_COMMITTED;
#else
              header->status &= ~SMART_STATUS_COMMITTED;
#endif

              /* Write the data to the new physical sector location */

              ret = MTD_BWRITE(dev->mtd, newsector * dev->mtdBlksPerSector,
                               dev->mtdBlksPerSector, (uint8_t *) dev->rwbuffer);

              /* Commit the sector */

              offset = newsector * dev->mtdBlksPerSector * dev->geo.blocksize +
                  offsetof(struct smart_sect_header_s, status);
#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF
              newstatus = header->status & ~SMART_STATUS_COMMITTED;
#else
              newstatus = header->status | SMART_STATUS_COMMITTED;
#endif
              ret = smart_bytewrite(dev, offset, 1, &newstatus);
              if (ret < 0)
                {
                  fdbg("Error %d committing new sector %d\n" -ret, newsector);
                  goto errout;
                }

              /* Release the old physical sector */

#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF
              newstatus = header->status & ~SMART_STATUS_RELEASED;
#else
              newstatus = header->status | SMART_STATUS_RELEASED;
#endif
              offset = x * dev->mtdBlksPerSector * dev->geo.blocksize +
                  offsetof(struct smart_sect_header_s, status);
              ret = smart_bytewrite(dev, offset, 1, &newstatus);
              if (ret < 0)
                {
                  fdbg("Error %d releasing old sector %d\n" -ret, x);
                  goto errout;
                }

              /* Update the variables */

              dev->sMap[*((uint16_t *) header->logicalsector)] = newsector;
              dev->freecount[newsector / dev->sectorsPerBlk]--;
            }

          /* Now erase the erase block */

          MTD_ERASE(dev->mtd, collectblock, 1);

          dev->freesectors += dev->releasecount[collectblock];
          dev->freecount[collectblock] = dev->sectorsPerBlk;
          dev->releasecount[collectblock] = 0;

          /* If this is block zero, then be sure to write the sector size */

          if (collectblock == 0)
            {
              /* Set the sector size in the 1st header */

              uint8_t sectsize = dev->sectorsize >> 7;
#if ( CONFIG_SMARTFS_ERASEDSTATE == 0xFF )
              newstatus = (uint8_t) ~SMART_STATUS_SIZEBITS | sectsize;
#else
              newstatus = (uint8_t) sectsize;
#endif
              /* Write the sector size to the device */

              offset = offsetof(struct smart_sect_header_s, status);
              ret = smart_bytewrite(dev, offset, 1, &newstatus);
              if (ret < 0)
                {
                  fdbg("Error %d setting sector 0 size\n", -ret);
                }
            }

          /* Update the block aging information in the format signature sector */
        }
      else
        {
          /* Test for aging sectors and push them to a new location
           * so we wear evenly.
           */
        }
    }

  return OK;

errout:
  return ret;
}
#endif /* CONFIG_FS_WRITABLE */

/****************************************************************************
 * Name: smart_writesector
 *
 * Description:  Writes data to the specified logical sector.  The sector
 *               should have already been allocated prior to the write.  If
 *               the logical sector already has data on the device, it will
 *               be released and a new physical sector will be created and
 *               mapped to the logical sector.
 *
 ****************************************************************************/

#ifdef CONFIG_FS_WRITABLE
static inline int smart_writesector(struct smart_struct_s *dev, unsigned long arg)
{
  int       ret;
  uint16_t  x;
  bool      needsrelocate = FALSE;
  uint32_t  mtdblock;
  uint16_t  physsector;
  struct    smart_read_write_s *req;
  struct    smart_sect_header_s *header;
  size_t    offset;
  uint8_t   byte;

  fvdbg("Entry\n");
  req = (struct smart_read_write_s *) arg;
  DEBUGASSERT(req->offset <= dev->sectorsize);
  DEBUGASSERT(req->offset+req->count <= dev->sectorsize);

  /* Ensure the logical sector has been allocated */

  if (req->logsector >= dev->totalsectors)
    {
      fdbg("Logical sector %d too large\n", req->logsector);

      ret = -EINVAL;
      goto errout;
    }

  physsector = dev->sMap[req->logsector];
  if (physsector == 0xFFFF)
    {
      fdbg("Logical sector %d not allocated\n", req->logsector);
      ret = -EINVAL;
      goto errout;
    }

  /* Read the sector data into our buffer */

  mtdblock = physsector * dev->mtdBlksPerSector;
  ret = MTD_BREAD(dev->mtd, mtdblock, dev->mtdBlksPerSector, (uint8_t *)
          dev->rwbuffer);
  if (ret != dev->mtdBlksPerSector)
    {
      fdbg("Error reading phys sector %d\n", physsector);
      ret = -EIO;
      goto errout;
    }

  /* Test if we need to relocate the sector to perform the write */

  for (x = 0; x < req->count; x++)
    {
      /* Test if the next byte can be written to the flash */

      byte = dev->rwbuffer[sizeof(struct smart_sect_header_s) + req->offset + x];
#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF
      if (((byte ^ req->buffer[x]) | byte) != byte)
        {
          needsrelocate = TRUE;
          break;
        }
#else
      if (((byte ^ req->buffer[x]) | req->buffer[x]) != req->buffer[x])
        {
          needsrelocate = TRUE;
          break;
        }
#endif
    }

  if (needsrelocate)
    {
      /* Find a new physical sector to save data to */

      physsector = smart_findfreephyssector(dev);
      if (physsector == 0xFFFF)
        {
          fdbg("Error relocating sector %d\n", req->logsector);
          ret = -EIO;
          goto errout;
        }

      /* Update the sequence number to indicate the sector was moved */

      header = (struct smart_sect_header_s *) dev->rwbuffer;
      (*((uint16_t *) header->seq))++;
#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF
      header->status |= SMART_STATUS_COMMITTED;
#else
      header->status &= SMART_STATUS_COMMITTED;
#endif
    }

  /* Now copy the data to the sector buffer. */

  memcpy(&dev->rwbuffer[sizeof(struct smart_sect_header_s) + req->offset],
          req->buffer, req->count);

  /* Now write the sector buffer to the device. */

  if (needsrelocate)
    {
      /* Write the entire sector to the new physical location, uncommitted. */

      ret = MTD_BWRITE(dev->mtd, physsector * dev->mtdBlksPerSector,
              dev->mtdBlksPerSector, (uint8_t *) dev->rwbuffer);
      if (ret != dev->mtdBlksPerSector)
        {
          fdbg("Error writing to physical sector %d\n", physsector);
          ret = -EIO;
          goto errout;
        }

      /* Commit the new physical sector */

#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF
      byte = header->status & ~SMART_STATUS_COMMITTED;
#else
      byte = header->status | SMART_STATUS_COMMITTED;
#endif
      offset = physsector * dev->mtdBlksPerSector * dev->geo.blocksize +
          offsetof(struct smart_sect_header_s, status);
      ret = smart_bytewrite(dev, offset, 1, &byte);
      if (ret != 1)
        {
          fvdbg("Error committing physical sector %d\n", physsector);
          ret = -EIO;
          goto errout;
        }

      /* Release the old physical sector */

#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF
      byte = header->status & ~SMART_STATUS_RELEASED;
#else
      byte = header->status | SMART_STATUS_RELEASED;
#endif
      offset = mtdblock * dev->geo.blocksize +
          offsetof(struct smart_sect_header_s, status);
      ret = smart_bytewrite(dev, offset, 1, &byte);

      /* Update releasecount for released sector and freecount for the
       * newly allocated physical sector. */

      dev->releasecount[dev->sMap[req->logsector] / dev->sectorsPerBlk]++;
      dev->freecount[physsector / dev->sectorsPerBlk]--;
      dev->freesectors--;

      /* Update the sector map */

      dev->sMap[req->logsector] = physsector;

      /* Since we performed a relocation, do garbage collection to
       * ensure we don't fill up our flash with released blocks.
       */

      smart_garbagecollect(dev);
    }
  else
    {
      /* Not relocated.  Just write the portion of the sector that needs
       * to be written. */

      offset = mtdblock * dev->geo.blocksize +
          sizeof(struct smart_sect_header_s) + req->offset;
      ret = smart_bytewrite(dev, offset, req->count, req->buffer);
    }

  ret = OK;

errout:
  return ret;
}
#endif /* CONFIG_FS_WRITABLE */

/****************************************************************************
 * Name: smart_readsector
 *
 * Description:  Reads data from the specified logical sector.  The sector
 *               should have already been allocated prior to the read.
 *
 ****************************************************************************/

static inline int smart_readsector(struct smart_struct_s *dev, unsigned long arg)
{
  int       ret;
  uint32_t  readaddr;
  uint16_t  physsector;
  struct smart_read_write_s *req;
  struct smart_sect_header_s header;

  fvdbg("Entry\n");
  req = (struct smart_read_write_s *) arg;
  DEBUGASSERT(req->offset < dev->sectorsize);
  DEBUGASSERT(req->offset+req->count < dev->sectorsize);

  /* Ensure the logical sector has been allocated */

  if (req->logsector >= dev->totalsectors)
    {
      fdbg("Logical sector %d too large\n", req->logsector);

      ret = -EINVAL;
      goto errout;
    }

  physsector = dev->sMap[req->logsector];
  if (physsector == 0xFFFF)
    {
      fdbg("Logical sector %d not allocated\n", req->logsector);
      ret = -EINVAL;
      goto errout;
    }

  /* Read the sector header data to validate as a sanity check */

  ret = MTD_READ(dev->mtd, physsector * dev->mtdBlksPerSector * dev->geo.blocksize,
          sizeof(struct smart_sect_header_s), (uint8_t *) &header);
  if (ret != sizeof(struct smart_sect_header_s))
    {
      fvdbg("Error reading sector %d header\n", physsector);
      ret = -EIO;
      goto errout;
    }

  /* Do a sanity check on the header data */

  if (((*(uint16_t *) header.logicalsector) != req->logsector) ||
      ((header.status & SMART_STATUS_COMMITTED) ==
       (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED)))
    {
      /* Error in sector header! How do we handle this? */

      fdbg("Error in logical sector %d header, phys=%d\n",
          req->logsector, physsector);
      ret = -EIO;
      goto errout;
    }

  /* Read the sector data into the buffer */

  readaddr = (uint32_t) physsector * dev->mtdBlksPerSector * dev->geo.blocksize +
    req->offset + sizeof(struct smart_sect_header_s);;

  ret = MTD_READ(dev->mtd, readaddr, req->count, (uint8_t *)
          req->buffer);
  if (ret != req->count)
    {
      fdbg("Error reading phys sector %d\n", physsector);
      ret = -EIO;
      goto errout;
    }

errout:
    return ret;
}

/****************************************************************************
 * Name: smart_allocsector
 *
 * Description:  Allocates a new logical sector.  If an argument is given,
 *               then it tries to allocate the specified sector number.
 *
 ****************************************************************************/

#ifdef CONFIG_FS_WRITABLE
static inline int smart_allocsector(struct smart_struct_s *dev, unsigned long requested)
{
  int       x;
  int       ret;
  uint16_t  logsector = 0xFFFF; /* Logical sector number selected */
  uint16_t  physicalsector;     /* The selected physical sector */
  uint16_t  releasecount;
  struct    smart_sect_header_s  *header;
  uint8_t   sectsize;

  /* Validate that we have enough sectors available to perform an
   * allocation.  We have to ensure we keep enough reserved sectors
   * on hand to do released sector garbage collection. */

  releasecount = 0;
  for (x = 0; x < dev->neraseblocks; x++)
    {
      releasecount += dev->releasecount[x];
    }

  if (dev->freesectors <= (dev->sectorsPerBlk << 0) + 4)
    {
      /* We are at our free sector limit.  Test if we have
       * sectors we can release */

      if (releasecount == 0)
        {
          /* No space left!! */

          return -ENOSPC;
        }
    }

  /* Check if a specific sector is being requested and allocate that
   * sector if it isn't already in use */

  if ((requested > 2) && (requested < dev->totalsectors))
    {
      /* Validate the sector is not already allocated */

      if (dev->sMap[requested] == (uint16_t) -1)
        {
          logsector = requested;
        }
    }

  /* Check if we need to scan for an available logical sector */

  if (logsector == 0xFFFF)
    {
      /* Loop through all sectors and find one to allocate */

      for (x = SMART_FIRST_ALLOC_SECTOR; x < dev->totalsectors; x++)
        {
          if (dev->sMap[x] == (uint16_t) -1)
            {
              /* Unused logical sector found.  Use this one */

              logsector = x;
              break;
            }
        }
    }

  /* Test for an error allocating a sector */

  if (logsector == 0xFFFF)
    {
      /* Hmmm.  We think we had enough logical sectors, but
       * something happened and we didn't find any free
       * logical sectors.  What do do?  Report an error?
       * rescan and try again to "self heal" in case of a
       * bug in our code? */

      fdbg("No free logical sector numbers!  Free sectors = %d\n",
              dev->freesectors);

      return -EIO;
    }

  /* Check if we need to do garbage collection.  We have to
   * ensure we keep enough reserved free sectors to per garbage
   * collection as it involves moving sectors from blocks with
   * released sectors into blocks with free sectors, then
   * erasing the vacated block. */

  smart_garbagecollect(dev);

  /* Find a free physical sector */

  physicalsector = smart_findfreephyssector(dev);
  fvdbg("Alloc: log=%d, phys=%d, erase block=%d, free=%d, released=%d\n",
          logsector, physicalsector, physicalsector /
          dev->sectorsPerBlk, dev->freesectors, releasecount);

  /* Create a header to assign the logical sector */

  memset(dev->rwbuffer, CONFIG_SMARTFS_ERASEDSTATE, dev->sectorsize);
  header = (struct smart_sect_header_s *) dev->rwbuffer;
  *((uint16_t *) header->logicalsector) = logsector;
  *((uint16_t *) header->seq) = 0;
  sectsize = dev->sectorsize >> 7;

#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF
  header->status = ~(SMART_STATUS_COMMITTED | SMART_STATUS_SIZEBITS |
          SMART_STATUS_VERBITS) | SMART_STATUS_VERSION | sectsize;
#else
  header->status = SMART_STATUS_COMMITTED | SMART_STATUS_VERSION | sectsize;
#endif

  /* Write the header to the physical sector location */

  x = physicalsector * dev->mtdBlksPerSector;

  fvdbg("Write MTD block %d\n", x);
  ret = MTD_BWRITE(dev->mtd, x, 1, (uint8_t *) dev->rwbuffer);
  if (ret != 1)
    {
      /* The block is not empty!!  What to do? */

      fdbg("Write block %d failed: %d.\n", x, ret);

      /* Unlock the mutex if we add one */

      return -EIO;
    }

  /* Map the sector and update the free sector counts */

  dev->sMap[logsector] = physicalsector;
  dev->freecount[physicalsector / dev->sectorsPerBlk]--;
  dev->freesectors--;

  /* Return the logical sector number */

  return logsector;
}
#endif /* CONFIG_FS_WRITABLE */

/****************************************************************************
 * Name: smart_freesector
 *
 * Description:  Frees a logical sector from the device.  Freeing (also
 *               called releasing) is performed by programming the released
 *               bit in the sector header's status byte.
 *
 ****************************************************************************/

#ifdef CONFIG_FS_WRITABLE
static inline int smart_freesector(struct smart_struct_s *dev, unsigned long
        logicalsector)
{
  int       ret;
  int       readaddr;
  uint16_t  physsector;
  uint16_t  block;
  struct    smart_sect_header_s  header;
  size_t    offset;

  /* Check if the logical sector is within bounds */

  if ((logicalsector > 2) && (logicalsector < dev->totalsectors))
    {
      /* Validate the sector is actually allocated */

      if (dev->sMap[logicalsector] == (uint16_t) -1)
        {
          fdbg("Invalid release - sector %d not allocated\n", logicalsector);
          ret = -EINVAL;
          goto errout;
        }
    }

  /* Okay to release the sector.  Read the sector header info */

  physsector = dev->sMap[logicalsector];
  readaddr = physsector * dev->mtdBlksPerSector * dev->geo.blocksize;
  ret = MTD_READ(dev->mtd, readaddr, sizeof(struct smart_sect_header_s),
                 (uint8_t *) &header);
  if (ret != sizeof(struct smart_sect_header_s))
    {
      goto errout;
    }

  /* Do a sanity check on the logical sector number */

  if (*((uint16_t *) header.logicalsector) != (uint16_t) logicalsector)
    {
      /* Hmmm... something is wrong.  This should always match!  Bug in our code? */

      fdbg("Sector %d logical sector in header doesn't match\n", logicalsector);
      ret = -EINVAL;
      goto errout;
    }

  /* Mark the sector as released */

#if CONFIG_SMARTFS_ERASEDSTATE == 0xFF
  header.status &= ~SMART_STATUS_RELEASED;
#else
  header.status |= SMART_STATUS_RELEASED;
#endif

  /* Write the status back to the device */

  offset = readaddr + offsetof(struct smart_sect_header_s, status);
  ret = smart_bytewrite(dev, offset, 1, &header.status);
  if (ret != 1)
    {
      fdbg("Error updating physicl sector %d status\n", physsector);
      goto errout;
    }

  /* Update the erase block's release count */

  block = physsector / dev->sectorsPerBlk;
  dev->releasecount[block]++;

  /* Unmap this logical sector */

  dev->sMap[logicalsector] = (uint16_t) -1;

  /* If this block has only released blocks, then erase it */

  if (dev->releasecount[block] + dev->freecount[block] == dev->sectorsPerBlk)
    {
      /* Erase the block */

      MTD_ERASE(dev->mtd, block, 1);

      dev->freesectors += dev->releasecount[block];
      dev->releasecount[block] = 0;
      dev->freecount[block] = dev->sectorsPerBlk;
    }

  ret = OK;

errout:
  return ret;
}
#endif /* CONFIG_FS_WRITABLE */

/****************************************************************************
 * Name: smart_ioctl
 *
 * Description: Return device geometry
 *
 ****************************************************************************/

static int smart_ioctl(FAR struct inode *inode, int cmd, unsigned long arg)
{
  struct smart_struct_s *dev ;
  int ret;

  fvdbg("Entry\n");
  DEBUGASSERT(inode && inode->i_private);

#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
  dev = ((struct smart_multiroot_device_s*) inode->i_private)->dev;
#else
  dev = (struct smart_struct_s *)inode->i_private;
#endif

  /* Process the ioctl's we care about first, pass any we don't respond
   * to directly to the underlying MTD device.
   */

  switch (cmd)
    {
    case BIOC_XIPBASE:
      /* The argument accompanying the BIOC_XIPBASE should be non-NULL.  If
       * DEBUG is enabled, we will catch it here instead of in the MTD
       * driver.
       */

#ifdef CONFIG_DEBUG
      if (arg == 0)
        {
          fdbg("ERROR: BIOC_XIPBASE argument is NULL\n");
          return -EINVAL;
        }
#endif

      /* Just change the BIOC_XIPBASE command to the MTDIOC_XIPBASE command. */

      cmd = MTDIOC_XIPBASE;
      break;

    case BIOC_GETFORMAT:

      /* Return the format information for the device */

#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
      ret = smart_getformat(dev, (struct smart_format_s *) arg,
        ((struct smart_multiroot_device_s*) inode->i_private)->rootdirnum);
#else
      ret = smart_getformat(dev, (struct smart_format_s *) arg);
#endif
      goto ok_out;

    case BIOC_READSECT:

      /* Do a logical sector read and return the data */
      ret = smart_readsector(dev, arg);
      goto ok_out;

#ifdef CONFIG_FS_WRITABLE
    case BIOC_LLFORMAT:

      /* Perform a low-level format on the flash */

      ret = smart_llformat(dev, arg);
      goto ok_out;

    case BIOC_ALLOCSECT:

      /* Allocate a logical sector for the upper layer file system */

      ret = smart_allocsector(dev, arg);
      goto ok_out;

    case BIOC_FREESECT:

      /* Free the specified logical sector */

      ret = smart_freesector(dev, arg);
      goto ok_out;

    case BIOC_WRITESECT:

      /* Write to the sector */

      ret = smart_writesector(dev, arg);
      goto ok_out;
#endif /* CONFIG_FS_WRITABLE */

    }

  /* No other block driver ioctl commmands 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)
    {
      fdbg("ERROR: MTD ioctl(%04x) failed: %d\n", cmd, ret);
    }

ok_out:
  return ret;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: smart_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/smartN where N is the minor number.
 *   mtd - The MTD device that supports the FLASH interface.
 *
 ****************************************************************************/

int smart_initialize(int minor, FAR struct mtd_dev_s *mtd, const char *partname)
{
  struct smart_struct_s *dev;
  int ret = -ENOMEM;
  uint32_t  totalsectors;
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
  struct smart_multiroot_device_s *rootdirdev;
#endif

  /* Sanity check */

#ifdef CONFIG_DEBUG
  if (minor < 0 || minor > 255 || !mtd)
    {
      return -EINVAL;
    }
#endif

  /* Allocate a SMART device structure */

  dev = (struct smart_struct_s *)kmalloc(sizeof(struct smart_struct_s));
  if (dev)
    {
      /* Initialize the SMART device structure */

      dev->mtd = mtd;

      /* 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).
       */

      /* Set these to zero in case the device doesn't support them */

      ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&dev->geo));
      if (ret < 0)
        {
          fdbg("MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret);
          kfree(dev);
          goto errout;
        }

      /* Set the sector size to the default for now */

      dev->sMap = NULL;
      dev->rwbuffer = NULL;
      ret = smart_setsectorsize(dev, CONFIG_MTD_SMART_SECTOR_SIZE);
      if (ret != OK)
        {
          kfree(dev);
          goto errout;
        }

      /* Calculate the totalsectors on this device and validate */

      totalsectors = dev->neraseblocks * dev->sectorsPerBlk;
      if (totalsectors > 65534)
        {
          fdbg("SMART Sector size too small for device\n");
          kfree(dev);
          ret = -EINVAL;
          goto errout;
        }

      dev->freesectors = (uint16_t) totalsectors;

      /* Mark the device format status an unknown */

      dev->formatstatus = SMART_FMT_STAT_UNKNOWN;
      dev->namesize = CONFIG_SMARTFS_MAXNAMLEN;
      if (partname)
        {
          strncpy(dev->partname, partname, SMART_PARTNAME_SIZE);
        }
      else
        {
          dev->partname[0] = '\0';
        }

#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
      dev->minor = minor;
#endif

      /* Create a MTD block device name */

#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
      if (partname != NULL)
        {
          snprintf(dev->rwbuffer, 18, "/dev/smart%d%sd1", minor, partname);
        }
      else
        {
          snprintf(dev->rwbuffer, 18, "/dev/smart%dd1", minor);
        }

      /* Inode private data is a reference to a struct containing
       * the SMART device structure and the root directory number.
       */

      rootdirdev = (struct smart_multiroot_device_s*) kmalloc(sizeof(*rootdirdev));
      if (rootdirdev == NULL)
        {
          fdbg("register_blockdriver failed: %d\n", -ret);
          kfree(dev->sMap);
          kfree(dev->rwbuffer);
          kfree(dev);
          ret = -ENOMEM;
          goto errout;
        }

      /* Populate the rootdirdev */

      rootdirdev->dev = dev;
      rootdirdev->rootdirnum = 0;
      ret = register_blockdriver(dev->rwbuffer, &g_bops, 0, rootdirdev);

#else
      if (partname != NULL)
        {
          snprintf(dev->rwbuffer, 18, "/dev/smart%d%s", minor, partname);
        }
      else
        {
          snprintf(dev->rwbuffer, 18, "/dev/smart%d", minor);
        }

      /* Inode private data is a reference to the SMART device structure */

      ret = register_blockdriver(dev->rwbuffer, &g_bops, 0, dev);
#endif

      if (ret < 0)
        {
          fdbg("register_blockdriver failed: %d\n", -ret);
          kfree(dev->sMap);
          kfree(dev->rwbuffer);
          kfree(dev);
          goto errout;
        }

      /* Do a scan of the device */

      smart_scan(dev);
    }

errout:
  return ret;
}
