nuttx/drivers/mtd/smart.c
Xiang Xiao 779a610ca3 Remove the unnecessary NULL fields in global instance definition of file_operations
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
2023-01-04 00:32:13 +02:00

6535 lines
183 KiB
C

/****************************************************************************
* drivers/mtd/smart.c
*
* 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/types.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/crc8.h>
#include <nuttx/crc16.h>
#include <nuttx/crc32.h>
#include <nuttx/kmalloc.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/mtd/mtd.h>
#include <nuttx/mtd/smart.h>
#include <nuttx/fs/smart.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#if 0 /* Define to enable checking */
# define CONFIG_SMART_LOCAL_CHECKFREE
#endif
#define SMART_STATUS_COMMITTED 0x80
#define SMART_STATUS_RELEASED 0x40
#define SMART_STATUS_CRC 0x20
#define SMART_STATUS_SIZEBITS 0x1c
#define SMART_STATUS_VERBITS 0x03
#if defined(CONFIG_SMART_CRC_16)
#define SMART_STATUS_VERSION 0x02
#elif defined(CONFIG_SMART_CRC_32)
#define SMART_STATUS_VERSION 0x03
#else
#define SMART_STATUS_VERSION 0x01
#endif
#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_WEAR_POS 36
#define SMART_WEAR_LEVEL_FORMAT_SIG 32
#define SMART_PARTNAME_SIZE 4
#define SMART_FIRST_DIR_SECTOR 3 /* First root directory sector */
#define SMART_FIRST_ALLOC_SECTOR 12 /* First logical sector number
* we will use for assignment
* of requested alloc sectors.
* All entries 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
#define SMART_MAX_ALLOCS 10
#ifndef CONFIG_MTD_SMART_ALLOC_DEBUG
#define smart_malloc(d, b, n) kmm_malloc(b)
#define smart_zalloc(d, b, n) kmm_zalloc(b)
#define smart_free(d, p) kmm_free(p)
#endif
#define SMART_WEAR_FULL_RELOCATE_THRESHOLD 8
#define SMART_WEAR_REORG_THRESHOLD 14
#define SMART_WEAR_MIN_LEVEL 5
#define SMART_WEAR_FORCE_REORG_THRESHOLD 1
#define SMART_WEAR_BIT_DIVIDE 1
#define SMART_WEAR_ZERO_MASK 0x0f
#define SMART_WEAR_BLOCK_MASK 0x01
#define SMART_WEARFLAGS_FORCE_REORG 0x01
#define SMART_WEARFLAGS_WRITE_NEEDED 0x02
#define SET_BITMAP(m, n) do { (m)[(n) / 8] |= 1 << ((n) % 8); } while (0)
#define CLR_BITMAP(m, n) do { (m)[(n) / 8] &= ~(1 << ((n) % 8)); } while (0)
#define ISSET_BITMAP(m, n) ((m)[(n) / 8] & (1 << ((n) % 8)))
#ifdef CONFIG_SMARTFS_ALIGNED_ACCESS
# define SMARTFS_NEXTSECTOR(h) \
(uint16_t)((FAR const uint8_t *)(h)->nextsector)[1] << 8 | \
(uint16_t)((FAR const uint8_t *)(h)->nextsector)[0]
# define SMARTFS_SET_NEXTSECTOR(h, v) \
do \
{ \
((FAR uint8_t *)(h)->nextsector)[0] = (v) & 0xff; \
((FAR uint8_t *)(h)->nextsector)[1] = (v) >> 8; \
} while (0)
#else
# define SMARTFS_NEXTSECTOR(h) (*((FAR uint16_t *)(h)->nextsector))
# define SMARTFS_SET_NEXTSECTOR(h, v) \
do \
{ \
((*((FAR uint16_t *)(h)->nextsector)) = (uint16_t)(v)); \
} while (0)
#endif
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
/****************************************************************************
* Private Data
****************************************************************************/
/* Bit mapping for wear level bits */
/* These are defined to allow updating the wear leveling with the minimum
* number of sector relocations / maximum use of 1 --> 0 transitions when
* incrementing the wear level.
*
* 0: 1111 8: 1011
* 1: 1110 9: 1010
* 2: 1100 10: 0010
* 3: 1000 11: 1101
* 4: 0111 12: 1001
* 5: 0110 13: 0001
* 6: 0100 14: 0011
* 7: 0000 15: 0101
*/
static const uint8_t g_wearlevel_to_bitmap4[] =
{
0x0f, 0x0e, 0x0c, 0x08, /* Single bit erased (x3) */
0x07, 0x06, 0x04, 0x00, /* Single bit erased (x3) */
0x0b, 0x0a, 0x02, /* Single bit erased (x2) */
0x0d, 0x09, 0x01, /* Single bit erased (x2) */
0x03,
0x05
};
/* Map a Wear Level bit pattern back to the wear level */
static const uint8_t g_wearbit_to_levelmap4[] =
{
7, 13, 10, 14, 6, 15, 5, 4,
3, 12, 9, 8, 2, 11, 1, 0
};
#endif
/****************************************************************************
* Private Types
****************************************************************************/
#ifdef CONFIG_MTD_SMART_MINIMIZE_RAM
struct smart_cache_s
{
uint16_t logical; /* Logical sector number */
uint16_t physical; /* Associated physical sector */
uint16_t birth; /* The "birthday" of this entry */
};
#endif
/* When CRC is enabled, we allocate sectors in memory only and only write
* to the device when an actual writesector is performed. If during the
* alloc process we do a physical write, we would either have to hold off on
* writing the CRC value (which creates an invalid state on the device) or
* we would have to perform a write, release re-write every time which would
* increase the wear of the device 2x.
*/
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
struct smart_allocsector_s
{
struct smart_allocsector_s *next; /* Pointer to next alloc sector */
uint16_t logical; /* Logical sector number */
uint16_t physical; /* Associated physical sector */
};
#endif
struct smart_struct_s
{
FAR struct mtd_dev_s *mtd; /* Contained MTD interface */
struct mtd_geometry_s geo; /* Device geometry */
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_SMARTFS)
uint32_t unusedsectors; /* Count of unused sectors (i.e. free when erased) */
uint32_t blockerases; /* Count of unused sectors (i.e. free when erased) */
#endif
uint16_t neraseblocks; /* Number of erase blocks or sub-sectors */
uint16_t lastallocblock; /* Last block we allocated a sector from */
uint16_t freesectors; /* Total number of free sectors */
uint16_t releasesectors; /* Total number of released 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 */
uint32_t erasesize; /* Size of an erase block */
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];
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 */
uint8_t debuglevel; /* Debug reporting level */
uint8_t availsectperblk; /* Number of usable sectors per erase block */
#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_MTD_SMART_WEAR_LEVEL
uint8_t wearflags; /* Indicates force erase of static blocks needed */
uint8_t minwearlevel; /* Min level in the wear level bits */
uint8_t maxwearlevel; /* Max level in the wear level bits */
uint8_t *wearstatus; /* Array of wear leveling bits */
uint32_t uneven_wearcount; /* Number of times the wear level has gone over max */
#endif
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
FAR struct smart_allocsector_s *allocsector; /* Pointer to first alloc sector */
#endif
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
FAR uint16_t *smap; /* Virtual to physical sector map */
#else
FAR uint8_t *sbitmap; /* Virtual sector used bit-map */
FAR struct smart_cache_s *scache; /* Sector cache */
uint16_t cache_entries; /* Number of valid entries in the cache */
uint16_t cache_lastlog; /* Keep track of the last sector accessed */
uint16_t cache_lastphys; /* Keep the physical sector number also */
uint16_t cache_nextbirth; /* Sector cache aging value */
#endif
#ifdef CONFIG_MTD_SMART_SECTOR_ERASE_DEBUG
FAR uint8_t *erasecounts; /* Number of erases for each erase block */
#endif
#ifdef CONFIG_MTD_SMART_ALLOC_DEBUG
size_t bytesalloc;
struct smart_alloc_s alloc[SMART_MAX_ALLOCS]; /* Array of memory allocations */
#endif
};
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
struct smart_multiroot_device_s
{
FAR struct smart_struct_s *dev;
uint8_t rootdirnum;
};
#endif
/* Format 1 sector header definition */
#if SMART_STATUS_VERSION == 1
#define SMART_FMT_VERSION 1
struct smart_sect_header_s
{
uint8_t logicalsector[2]; /* The logical sector number */
uint8_t seq; /* Incrementing sequence number */
uint8_t crc8; /* CRC-8 or seq number MSB */
uint8_t status; /* Status of this sector:
* Bit 7: 1 = Not committed
* 0 = committed
* Bit 6: 1 = Not released
* 0 = released
* Bit 5: Sector CRC enable
* Bit 4-2: Sector size on volume
* Bit 1-0: Format version (0x1) */
};
typedef uint8_t crc_t;
/* Format 2 sector header definition. This is for a 16-bit CRC */
#elif SMART_STATUS_VERSION == 2
#define SMART_FMT_VERSION 2
struct smart_sect_header_s
{
uint8_t logicalsector[2]; /* The logical sector number */
uint8_t crc16[2]; /* CRC-16 for this sector */
uint8_t status; /* Status of this sector:
* Bit 7: 1 = Not committed
* 0 = committed
* Bit 6: 1 = Not released
* 0 = released
* Bit 5: Sector CRC enable
* Bit 4-2: Sector size on volume
* Bit 1-0: Format version (0x2) */
uint8_t seq; /* Incrementing sequence number */
};
typedef uint16_t crc_t;
/* Format 3 (32-bit) sector header definition. Actually, this format
* isn't used yet and will likely be changed to a format to support
* NAND devices (possibly with an 18-bit sector size, allowing up to
* 256K sectors on a larger NAND device, though this would take a fair
* amount of RAM for management).
*/
#elif SMART_STATUS_VERSION == 3
#error "32-Bit mode not supported yet"
#define SMART_FMT_VERSION 3
struct smart_sect_header_s
{
uint8_t logicalsector[4]; /* The logical sector number */
uint8_t crc32[4]; /* CRC-32 for this sector */
uint8_t status; /* Status of this sector:
* Bit 7: 1 = Not committed
* 0 = committed
* Bit 6: 1 = Not released
* 0 = released
* Bit 5: Sector CRC enable
* Bit 4-2: Sector size on volume
* Bit 1-0: Format version (0x3) */
uint8_t seq; /* Incrementing sequence number */
};
typedef uint32_t crc_t;
#endif
/* Following two definitions copied from internal definition of fs/smartfs.
* Because needed to search chain_header and entry_header.
*/
#if defined(CONFIG_MTD_SMART_ENABLE_CRC) && defined(CONFIG_SMART_CRC_32)
struct smart_chain_header_s
{
uint8_t nextsector[4]; /* Next logical sector in the chain */
uint8_t used[4]; /* Number of bytes used in this sector */
uint8_t type; /* Type of sector entry (file or dir) */
};
#else
struct smart_chain_header_s
{
uint8_t type; /* Type of sector entry (file or dir) */
uint8_t nextsector[2]; /* Next logical sector in the chain */
uint8_t used[2]; /* Number of bytes used in this sector */
};
#endif
struct smart_entry_header_s
{
uint16_t flags; /* Flags, including permissions:
* 15: Empty entry
* 14: Active entry
* 12-0: Permissions bits */
int16_t firstsector; /* Sector number of the name */
uint32_t utc; /* Time stamp */
};
/****************************************************************************
* 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,
blkcnt_t start_sector, unsigned int nsectors);
static ssize_t smart_write(FAR struct inode *inode,
FAR const unsigned char *buffer, blkcnt_t start_sector,
unsigned int nsectors);
static int smart_geometry(FAR struct inode *inode,
FAR struct geometry *geometry);
static int smart_ioctl(FAR struct inode *inode, int cmd,
unsigned long arg);
static int smart_findfreephyssector(FAR struct smart_struct_s *dev,
uint8_t canrelocate);
static int smart_writesector(FAR struct smart_struct_s *dev,
unsigned long arg);
static inline int smart_allocsector(FAR struct smart_struct_s *dev,
unsigned long requested);
static int smart_readsector(FAR struct smart_struct_s *dev,
unsigned long arg);
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
static int smart_validate_crc(FAR struct smart_struct_s *dev);
#endif
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
static int smart_read_wearstatus(FAR struct smart_struct_s *dev);
static int smart_relocate_static_data(FAR struct smart_struct_s *dev,
uint16_t block);
#endif
static int smart_relocate_sector(FAR struct smart_struct_s *dev,
uint16_t oldsector, uint16_t newsector);
#ifdef CONFIG_MTD_SMART_FSCK
static int smart_fsck(FAR struct smart_struct_s *dev);
#endif
#ifdef CONFIG_SMART_DEV_LOOP
static ssize_t smart_loop_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static ssize_t smart_loop_write(FAR struct file *filep,
FAR const char *buffer, size_t buflen);
static int smart_loop_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
#endif /* CONFIG_SMART_DEV_LOOP */
/****************************************************************************
* Private Data
****************************************************************************/
static const struct block_operations g_bops =
{
smart_open, /* open */
smart_close, /* close */
smart_read, /* read */
smart_write, /* write */
smart_geometry, /* geometry */
smart_ioctl /* ioctl */
};
#ifdef CONFIG_SMART_DEV_LOOP
static const struct file_operations g_fops =
{
NULL, /* open */
NULL, /* close */
smart_loop_read, /* read */
smart_loop_write, /* write */
NULL, /* seek */
smart_loop_ioctl, /* ioctl */
};
#endif /* CONFIG_SMART_DEV_LOOP */
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: smart_open
*
* Description: Open the block device
*
****************************************************************************/
static int smart_open(FAR struct inode *inode)
{
finfo("Entry\n");
return OK;
}
/****************************************************************************
* Name: smart_close
*
* Description: close the block device
*
****************************************************************************/
static int smart_close(FAR struct inode *inode)
{
finfo("Entry\n");
return OK;
}
/****************************************************************************
* Name: smart_malloc
*
* Description: Perform allocations and keep track of amount of allocated
* memory for this context.
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_ALLOC_DEBUG
FAR static void *smart_malloc(FAR struct smart_struct_s *dev,
size_t bytes, const char *name)
{
FAR void *ret = kmm_malloc(bytes);
uint8_t x;
/* Test if we are allocating the dev struct */
if (dev == NULL)
{
dev = ret;
dev->bytesalloc = 0;
for (x = 0; x < SMART_MAX_ALLOCS; x++)
{
dev->alloc[x].ptr = NULL;
}
}
/* Keep track of the total allocation */
if (ret != NULL)
{
dev->bytesalloc += bytes;
}
/* Keep track of individual allocs */
for (x = 0; x < SMART_MAX_ALLOCS; x++)
{
if (dev->alloc[x].ptr == NULL)
{
dev->alloc[x].ptr = ret;
dev->alloc[x].size = bytes;
dev->alloc[x].name = name;
break;
}
}
finfo("SMART alloc: %ld\n", dev->bytesalloc);
return ret;
}
#endif
/****************************************************************************
* Name: smart_zalloc
*
* Description: Perform allocations and keep track of amount of allocated
* memory for this context.
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_ALLOC_DEBUG
FAR static void *smart_zalloc(FAR struct smart_struct_s *dev,
size_t bytes, const char *name)
{
void *mem;
mem = smart_malloc(dev, bytes, name);
if (mem != NULL)
{
memset(mem, 0, bytes);
}
return mem;
}
#endif
/****************************************************************************
* Name: smart_free
*
* Description: Perform smart memory free operation.
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_ALLOC_DEBUG
static void smart_free(FAR struct smart_struct_s *dev, FAR void *ptr)
{
uint8_t x;
for (x = 0; x < SMART_MAX_ALLOCS; x++)
{
if (dev->alloc[x].ptr == ptr)
{
dev->alloc[x].ptr = NULL;
dev->bytesalloc -= dev->alloc[x].size;
kmm_free(ptr);
break;
}
}
}
#endif
/****************************************************************************
* Name: smart_set_count
*
* Description: Set either the freecount or releasecount value for the
* specified eraseblock (depending on which pointer is passed).
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
static void smart_set_count(FAR struct smart_struct_s *dev,
FAR uint8_t *pcount, uint16_t block,
uint8_t count)
{
if (dev->sectorsperblk > 16)
{
pcount[block] = count;
}
else
{
/* Save the lower 4 bits of the count in a shared byte */
if (block & 0x01)
{
pcount[block >> 1] = (pcount[block >> 1] & 0xf0) |
(count & 0x0f);
}
else
{
pcount[block >> 1] = (pcount[block >> 1] & 0x0f) |
((count & 0x0f) << 4);
}
/* If we have 16 sectors per block, then the upper bit (representing
* 16) all get packed into shared bytes.
*/
if (dev->sectorsperblk == 16)
{
if (count == 16)
{
pcount[(dev->geo.neraseblocks >> 1) + (block >> 3)] |=
1 << (block & 0x07);
}
else
{
pcount[(dev->geo.neraseblocks >> 1) + (block >> 3)] &=
~(1 << (block & 0x07));
}
}
}
}
#endif
/****************************************************************************
* Name: smart_get_count
*
* Description: Get either the freecount or releasecount value for the
* specified eraseblock (depending on which pointer is passed).
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
static uint8_t smart_get_count(FAR struct smart_struct_s *dev,
FAR uint8_t *pcount, uint16_t block)
{
uint8_t count;
if (dev->sectorsperblk > 16)
{
count = pcount[block];
}
else
{
/* Save the lower 4 bits of the count in a shared byte */
if (block & 0x01)
{
count = pcount[block >> 1] & 0x0f;
}
else
{
count = pcount[block >> 1] >> 4;
}
/* If we have 16 sectors per block, then the upper bit (representing
* 16) all get packed into shared bytes.
*/
if (dev->sectorsperblk == 16)
{
if (pcount[(dev->geo.neraseblocks >> 1) +
(block >> 3)] & (1 << (block & 0x07)))
{
count |= 0x10;
}
}
}
return count;
}
#endif
/****************************************************************************
* Name: smart_add_count
*
* Description: Add the specified value to and eraseblock count.
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
static void smart_add_count(struct smart_struct_s *dev, uint8_t *pcount,
uint16_t block, int adder)
{
int16_t value;
value = smart_get_count(dev, pcount, block) + adder;
smart_set_count(dev, pcount, block, value);
}
#endif
/****************************************************************************
* Name: smart_checkfree
*
* Description: A debug routine for validating the free sector count used
* during development of the wear leveling code.
*
****************************************************************************/
#ifdef CONFIG_SMART_LOCAL_CHECKFREE
int smart_checkfree(FAR struct smart_struct_s *dev, int lineno)
{
uint16_t x;
uint16_t freecount;
#ifdef CONFIG_DEBUG_FS
uint16_t blockfree;
uint16_t blockrelease;
static uint16_t prev_freesectors = 0;
static uint16_t prev_releasesectors = 0;
static uint8_t *prev_freecount = NULL;
static uint8_t *prev_releasecount = NULL;
#endif
freecount = 0;
for (x = 0; x < dev->neraseblocks; x++)
{
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
freecount += smart_get_count(dev, dev->freecount, x);
#else
freecount += dev->freecount[x];
#endif
}
/* Test if the calculated freesectors equals the reported value */
#ifdef CONFIG_DEBUG_FS
if (freecount != dev->freesectors)
{
fwarn("WARNING: Free count incorrect in line %d! Calculated=%d, "
"dev->freesectors=%d\n",
lineno, freecount, dev->freesectors);
/* Determine what changed from the last time which caused this error */
fwarn(" ... Prev freesectors=%d, prev releasesectors=%d\n",
prev_freesectors, prev_releasesectors);
if (prev_freecount)
{
for (x = 0; x < dev->neraseblocks; x++)
{
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
blockfree = smart_get_count(dev, dev->freecount, x);
blockrelease = smart_get_count(dev, dev->releasecount, x);
#else
blockfree = dev->freecount[x];
blockrelease = dev->releasecount[x];
#endif
if (prev_freecount[x] != blockfree ||
prev_releasecount[x] != blockrelease)
{
/* This block's values are different from the last time ...
* report it.
*/
fwarn(" ... Block %d: Old Free=%d, old release=%d, "
"New free=%d, new release = %d\n",
x, prev_freecount[x], prev_releasecount[x],
blockfree, blockrelease);
}
}
}
/* Modify the freesector count to reflect the actual calculated
* freecount to get us back in line.
*/
dev->freesectors = freecount;
return -EIO;
}
/* Make a copy of the freecount and releasecount arrays to compare the
* differences between successive calls so we can evaluate what changed
* in the event an error is detected.
*/
if (prev_freecount == NULL)
{
prev_freecount = (FAR uint8_t *)
smart_malloc(dev, dev->neraseblocks << 1, "Free backup");
prev_releasecount = prev_freecount + dev->neraseblocks;
}
if (prev_freecount != NULL)
{
for (x = 0; x < dev->neraseblocks; x++)
{
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
prev_freecount[x] = smart_get_count(dev, dev->freecount, x);
prev_releasecount[x] = smart_get_count(dev, dev->releasecount, x);
#else
prev_freecount[x] = dev->freecount[x];
prev_releasecount[x] = dev->releasecount[x];
#endif
}
}
/* Save the previous freesectors count */
prev_freesectors = dev->freesectors;
prev_releasesectors = dev->releasesectors;
#endif
return OK;
}
#endif
/****************************************************************************
* Name: smart_reload
*
* Description: Read the specified number 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;
ssize_t 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 */
finfo("Read %zu blocks starting at block %zu\n",
mtdblocks, mtdstartblock);
nread = MTD_BREAD(dev->mtd, mtdstartblock, mtdblocks, buffer);
if (nread != mtdblocks)
{
ferr("ERROR: Read %zd blocks starting at block %" PRIdOFF
" failed: %zd\n", nblocks, startblock, nread);
}
return nread;
}
/****************************************************************************
* Name: smart_read
*
* Description: Read the specified number of sectors
*
****************************************************************************/
static ssize_t smart_read(FAR struct inode *inode, unsigned char *buffer,
blkcnt_t start_sector, unsigned int nsectors)
{
FAR struct smart_struct_s *dev;
finfo("SMART: sector: %" PRIuOFF " nsectors: %u\n",
start_sector, nsectors);
DEBUGASSERT(inode && inode->i_private);
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
dev = ((FAR 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
*
****************************************************************************/
static ssize_t smart_write(FAR struct inode *inode,
FAR const unsigned char *buffer,
blkcnt_t start_sector, unsigned int nsectors)
{
FAR 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;
off_t mtdblockcount;
finfo("sector: %" PRIuOFF " nsectors: %u\n", start_sector, nsectors);
DEBUGASSERT(inode && inode->i_private);
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
dev = ((FAR struct smart_multiroot_device_s *)inode->i_private)->dev;
#else
dev = (FAR 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;
finfo("mtdsector: %" PRIdOFF " mtdnsectors: %" PRIdOFF "\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)
{
ferr("ERROR: Erase block=%" PRIdOFF " 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. */
finfo("Write MTD block %" PRIdOFF " from offset %" PRIdOFF "\n",
nextblock, offset);
nxfrd = MTD_BWRITE(dev->mtd, nextblock, blkstowrite, &buffer[offset]);
if (nxfrd != blkstowrite)
{
/* The block is not empty!! What to do? */
ferr("ERROR: Write block %" PRIdOFF " failed: %zd.\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;
}
/****************************************************************************
* Name: smart_geometry
*
* Description: Return device geometry
*
****************************************************************************/
static int smart_geometry(FAR struct inode *inode, struct geometry *geometry)
{
FAR struct smart_struct_s *dev;
uint32_t erasesize;
finfo("Entry\n");
DEBUGASSERT(inode);
if (geometry)
{
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
dev = ((FAR struct smart_multiroot_device_s *)inode->i_private)->dev;
#else
dev = (FAR struct smart_struct_s *)inode->i_private;
#endif
geometry->geo_available = true;
geometry->geo_mediachanged = false;
geometry->geo_writeenabled = true;
erasesize = dev->geo.erasesize;
geometry->geo_nsectors = dev->geo.neraseblocks * erasesize /
dev->sectorsize;
geometry->geo_sectorsize = dev->sectorsize;
finfo("available: true mediachanged: false writeenabled: %s\n",
geometry->geo_writeenabled ? "true" : "false");
finfo("nsectors: %" PRIuOFF " sectorsize: %" PRIi16 "\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
* dependent variables.
*
****************************************************************************/
static int smart_setsectorsize(FAR struct smart_struct_s *dev, uint16_t size)
{
uint32_t erasesize;
uint32_t totalsectors;
uint32_t allocsize;
/* Validate the size isn't zero so we don't divide by zero below */
if (size == 0)
{
size = CONFIG_MTD_SMART_SECTOR_SIZE;
}
if (size == dev->sectorsize)
{
return OK;
}
erasesize = dev->geo.erasesize;
dev->neraseblocks = dev->geo.neraseblocks;
dev->erasesize = erasesize;
dev->sectorsize = size;
dev->mtdblkspersector = dev->sectorsize / dev->geo.blocksize;
DEBUGASSERT(dev->sectorsize >= dev->geo.blocksize);
DEBUGASSERT(erasesize / dev->sectorsize <= 256);
if (erasesize / dev->sectorsize > 256)
{
/* We can't throw a error message here because it is too early.
* set the erasesize to zero and exit, then we will detect
* it during mksmartfs or mount.
*/
dev->erasesize = 0;
dev->sectorsperblk = 256;
dev->availsectperblk = 255;
}
else
{
/* Set the sectors per erase block and available sectors per erase
* block
*/
dev->sectorsperblk = erasesize / dev->sectorsize;
if (dev->sectorsperblk == 256)
{
dev->availsectperblk = 255;
}
else if (dev->sectorsperblk == 0)
{
return -EINVAL;
}
else
{
dev->availsectperblk = dev->sectorsperblk;
}
}
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_SMARTFS)
dev->unusedsectors = 0;
dev->blockerases = 0;
#endif
/* Release any existing rwbuffer and smap */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
if (dev->smap != NULL)
{
smart_free(dev, dev->smap);
dev->smap = NULL;
}
#else
if (dev->sbitmap != NULL)
{
smart_free(dev, dev->sbitmap);
dev->sbitmap = NULL;
}
dev->cache_entries = 0;
dev->cache_lastlog = 0xffff;
dev->cache_nextbirth = 0;
#endif
if (dev->rwbuffer != NULL)
{
smart_free(dev, dev->rwbuffer);
dev->rwbuffer = NULL;
}
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
if (dev->wearstatus != NULL)
{
smart_free(dev, dev->wearstatus);
dev->wearstatus = NULL;
}
#endif
/* Allocate a virtual to physical sector map buffer. Also allocate
* the storage space for releasecount and freecounts.
*/
totalsectors = dev->neraseblocks * dev->sectorsperblk;
/* Validate the number of total sectors is small enough for a uint16_t */
if (totalsectors > 65536)
{
ferr("ERROR: Invalid SMART sector count %" PRIu32 "\n", totalsectors);
return -EINVAL;
}
else if (totalsectors == 65536)
{
/* Special case. We allow 65536 sectors and simply waste 2 sectors
* to allow a smaller sector size with almost maximum flash usage.
*/
totalsectors -= 2;
}
dev->totalsectors = (uint16_t) totalsectors;
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
allocsize = dev->neraseblocks << 1;
dev->smap = (FAR uint16_t *)
smart_malloc(dev, totalsectors * sizeof(uint16_t) + allocsize,
"Sector map");
if (!dev->smap)
{
ferr("ERROR: Error allocating SMART virtual map buffer\n");
goto errexit;
}
dev->releasecount = (FAR uint8_t *) dev->smap +
(totalsectors * sizeof(uint16_t));
dev->freecount = dev->releasecount + dev->neraseblocks;
#else
dev->sbitmap = (FAR uint8_t *)
smart_malloc(dev, (totalsectors + 7) >> 3, "Sector Bitmap");
if (dev->sbitmap == NULL)
{
ferr("ERROR: Error allocating SMART sector cache\n");
goto errexit;
}
/* Calculate the alloc size of the freesector and release sector arrays */
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
if (dev->sectorsperblk > 16)
{
allocsize = dev->neraseblocks << 1;
}
else if (dev->sectorsperblk == 16)
{
allocsize = dev->neraseblocks + (dev->neraseblocks >> 2);
}
else
{
allocsize = dev->neraseblocks;
}
#else
allocsize = dev->neraseblocks << 1;
#endif
/* Allocate the sector cache */
if (dev->scache == NULL)
{
dev->scache = (FAR struct smart_cache_s *) smart_malloc(dev,
CONFIG_MTD_SMART_SECTOR_CACHE_SIZE * sizeof(struct smart_cache_s) +
allocsize, "Sector Cache");
}
if (!dev->scache)
{
ferr("ERROR: Error allocating SMART sector cache\n");
goto errexit;
}
dev->releasecount = (FAR uint8_t *)dev->scache +
(CONFIG_MTD_SMART_SECTOR_CACHE_SIZE * sizeof(struct smart_cache_s));
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
if (dev->sectorsperblk > 16)
{
dev->freecount = dev->releasecount + dev->neraseblocks;
}
else if (dev->sectorsperblk == 16)
{
dev->freecount = dev->releasecount + (dev->neraseblocks >> 1) +
(dev->neraseblocks >> 3);
}
else
{
dev->freecount = dev->releasecount + (dev->neraseblocks >> 1);
}
#else
dev->freecount = dev->releasecount + dev->neraseblocks;
#endif
#endif /* CONFIG_MTD_SMART_MINIMIZE_RAM */
#ifdef CONFIG_MTD_SMART_SECTOR_ERASE_DEBUG
/* Allocate a buffer to hold the erase counts */
if (dev->erasecounts == NULL)
{
dev->erasecounts = (FAR uint8_t *)
smart_malloc(dev, dev->neraseblocks, "Erase counts");
}
if (!dev->erasecounts)
{
ferr("ERROR: Error allocating erase count array\n");
goto errexit;
}
memset(dev->erasecounts, 0, dev->neraseblocks);
#endif
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
/* Allocate the wear leveling status array */
dev->wearstatus = (FAR uint8_t *) smart_malloc(dev, dev->neraseblocks >>
SMART_WEAR_BIT_DIVIDE, "Wear status");
if (!dev->wearstatus)
{
ferr("ERROR: Error allocating wear level status array\n");
goto errexit;
}
memset(dev->wearstatus, CONFIG_SMARTFS_ERASEDSTATE, dev->neraseblocks >>
SMART_WEAR_BIT_DIVIDE);
dev->wearflags = 0;
dev->uneven_wearcount = 0;
#endif
/* Allocate a read/write buffer */
dev->rwbuffer = (FAR char *) smart_malloc(dev, size, "RW Buffer");
if (!dev->rwbuffer)
{
ferr("ERROR: Error allocating SMART read/write buffer\n");
goto errexit;
}
return OK;
/* On error for any allocation, we jump here and free anything that had
* previously been allocated.
*/
errexit:
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
if (dev->smap)
{
smart_free(dev, dev->smap);
dev->smap = NULL;
}
#else
if (dev->sbitmap)
{
smart_free(dev, dev->sbitmap);
dev->sbitmap = NULL;
}
if (dev->scache)
{
smart_free(dev, dev->scache);
dev->scache = NULL;
}
#endif
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
if (dev->wearstatus)
{
smart_free(dev, dev->wearstatus);
dev->wearstatus = NULL;
}
#endif
#ifdef CONFIG_MTD_SMART_SECTOR_ERASE_DEBUG
if (dev->erasecounts)
{
smart_free(dev, dev->erasecounts);
dev->erasecounts = NULL;
}
#endif
return -ENOMEM;
}
/****************************************************************************
* 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 actually changed.
*
****************************************************************************/
static ssize_t smart_bytewrite(FAR struct smart_struct_s *dev, size_t offset,
int nbytes, FAR 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,
(FAR uint8_t *)dev->rwbuffer);
if (ret < 0)
{
ferr("ERROR: Error %zd 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,
(FAR uint8_t *) dev->rwbuffer);
if (ret < 0)
{
ferr("ERROR: Error %zd writing to device\n", -ret);
goto errout;
}
}
ret = nbytes;
errout:
return ret;
}
/****************************************************************************
* Name: smart_add_sector_to_cache
*
* Description: Adds a logical to physical sector mapping to the sector
* map cache. The cache is used to minimize RAM by eliminating
* a one-to-one mapping of all logical sectors and only keeping
* a fixed number of mappings per the
* CONFIG_MTD_SMART_SECTOR_CACHE_SIZE parameter. Sectors are
* automatically managed and removed based on the time since
* they were accessed last.
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_MINIMIZE_RAM
static int smart_add_sector_to_cache(FAR struct smart_struct_s *dev,
uint16_t logical, uint16_t physical, int line)
{
uint16_t index;
uint16_t x;
uint16_t oldest;
/* If we aren't full yet, just add the sector to the end of the list */
index = 1;
if (dev->cache_entries < CONFIG_MTD_SMART_SECTOR_CACHE_SIZE)
{
oldest = 0;
index = dev->cache_entries++;
}
else
{
/* Cache is full. We must find the least accessed entry and replace
* it
*/
oldest = 0xffff;
for (x = 0; x < CONFIG_MTD_SMART_SECTOR_CACHE_SIZE; x++)
{
/* Never replace cache entries for system sectors */
if (dev->scache[x].logical < SMART_FIRST_ALLOC_SECTOR)
continue;
/* If the hit count is zero, then choose this entry */
if (dev->scache[x].birth < oldest)
{
oldest = dev->scache[x].birth;
index = x;
}
}
}
/* Now add the sector at index */
dev->scache[index].logical = logical;
dev->scache[index].physical = physical;
dev->scache[index].birth = dev->cache_nextbirth++;
dev->cache_lastlog = logical;
dev->cache_lastphys = physical;
if (dev->debuglevel > 1)
{
_err("Add Cache sector: Log=%d, Phys=%d at index %d from line %d\n",
logical, physical, index, line);
}
/* Test if the birthdays need to be adjusted */
if (oldest >= CONFIG_MTD_SMART_SECTOR_CACHE_SIZE + 1024)
{
for (x = 0; x < dev->cache_entries; x++)
{
dev->scache[x].birth -= 1024;
}
dev->cache_nextbirth -= 1024;
}
return index;
}
#endif
/****************************************************************************
* Name: smart_cache_lookup
*
* Description: Perform a cache lookup for the requested logical sector.
* If the sector is in the cache, then update the hitcount and
* return the physical mapping. If a cache miss occurs, then
* the routine will scan the volume to find the logical sector
* and add / replace a cache entry with the newly located
* sector.
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_MINIMIZE_RAM
static uint16_t smart_cache_lookup(FAR struct smart_struct_s *dev,
uint16_t logical)
{
int ret;
uint16_t block;
uint16_t sector;
uint16_t x;
uint16_t physical;
uint16_t logicalsector;
struct smart_sect_header_s header;
size_t readaddress;
physical = 0xffff;
/* Test if searching for the last sector used */
if (logical == dev->cache_lastlog)
{
return dev->cache_lastphys;
}
/* First search for the entry in the cache */
for (x = 0; x < dev->cache_entries; x++)
{
if (dev->scache[x].logical == logical)
{
/* Entry found in the cache. Grab the physical mapping. */
physical = dev->scache[x].physical;
break;
}
}
/* If the entry wasn't found in the cache, then we must search the volume
* for it and add it to the cache.
*/
if (physical == 0xffff)
{
/* Now scan the MTD device. Instead of scanning start to end, we
* span the erase blocks and read one sector from each at a time.
* this helps speed up the search on volumes that aren't full
* because of sector allocation scheme will use the lower sector
* numbers in each erase block first.
*/
for (sector = 0;
sector < dev->availsectperblk && physical == 0xffff;
sector++)
{
/* Now scan across each erase block */
for (block = 0; block < dev->geo.neraseblocks; block++)
{
/* Calculate the read address for this sector */
readaddress = block * dev->erasesize +
sector * dev->sectorsize;
/* Read the header for this sector */
ret = MTD_READ(dev->mtd, readaddress,
sizeof(struct smart_sect_header_s),
(FAR uint8_t *) &header);
if (ret != sizeof(struct smart_sect_header_s))
{
goto err_out;
}
/* Get the logical sector number for this physical sector */
logicalsector = *((FAR uint16_t *) header.logicalsector);
#if CONFIG_SMARTFS_ERASEDSTATE == 0x00
if (logicalsector == 0)
{
continue;
}
#endif
/* Test if this sector has been committed */
if ((header.status & SMART_STATUS_COMMITTED) ==
(CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED))
{
continue;
}
/* Test if this sector has been release and skip it if it has */
if ((header.status & SMART_STATUS_RELEASED) !=
(CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_RELEASED))
{
continue;
}
if ((header.status & SMART_STATUS_VERBITS) !=
SMART_STATUS_VERSION)
{
continue;
}
/* Test if this is the sector we are looking for */
if (logicalsector == logical)
{
/* This is the sector we are looking for! Add it to the
* cache
*/
physical = block * dev->sectorsperblk + sector;
smart_add_sector_to_cache(dev, logical, physical,
__LINE__);
break;
}
}
}
}
/* Update the last logical sector found variable */
dev->cache_lastlog = logical;
dev->cache_lastphys = physical;
err_out:
return physical;
}
#endif
/****************************************************************************
* Name: smart_update_cache
*
* Description: Updates a cache entry (if present) replacing the logical
* sector's physical sector mapping with the new one provided.
* This does not affect the hit count.
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_MINIMIZE_RAM
static void smart_update_cache(FAR struct smart_struct_s *dev, uint16_t
logical, uint16_t physical)
{
uint16_t x;
/* Scan through all cache entries and find the logical sector entry */
for (x = 0; x < dev->cache_entries; x++)
{
if (dev->scache[x].logical == logical)
{
/* Entry found. Update it's physical mapping */
dev->scache[x].physical = physical;
/* If we are freeing a sector, then remove the logical entry from
* the cache.
*/
if (physical == 0xffff)
{
dev->scache[x].logical =
dev->scache[dev->cache_entries - 1].logical;
dev->scache[x].physical =
dev->scache[dev->cache_entries - 1].physical;
dev->cache_entries--;
}
if (dev->debuglevel > 1)
{
_err("Update Cache: Log=%d, Phys=%d at index %d\n",
logical, physical, x);
}
break;
}
}
if (dev->cache_lastlog == logical)
{
dev->cache_lastphys = physical;
}
}
#endif
/****************************************************************************
* Name: smart_get_wear_level
*
* Description: Gets the wear level of the specified block. Wear levels are
* encoded to minimize the number of zero to one transitions,
* possibly allowing updates to made on NOR devices that have
* no CRC enabled.
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
static uint8_t smart_get_wear_level(FAR struct smart_struct_s *dev,
uint16_t block)
{
uint8_t bits;
bits = dev->wearstatus[block >> SMART_WEAR_BIT_DIVIDE];
if (block & 0x01)
{
/* Use the upper nibble */
bits >>= 4;
}
else
{
/* Use the lower nibble */
bits &= 0x0f;
}
/* Lookup and return the level using the BitToLevel map */
return g_wearbit_to_levelmap4[bits];
}
#endif
/****************************************************************************
* Name: smart_find_wear_minmax
*
* Description: Find the minimum and maximum wear levels. This is used when
* we increment the wear level of a minimum value block so that
* we can detect if a new minimum exists and perform
* normalization of the wear-levels.
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
static void smart_find_wear_minmax(FAR struct smart_struct_s *dev)
{
uint16_t x;
unsigned char level;
dev->minwearlevel = 15;
dev->maxwearlevel = 0;
/* Loop through all erase blocks and find min / max level */
for (x = 0; x < dev->geo.neraseblocks; x++)
{
/* Find wear level of the minimum worn block */
level = smart_get_wear_level(dev, x);
if (level < dev->minwearlevel)
{
dev->minwearlevel = level;
}
/* Find wear level of the maximum worn block */
if (level > dev->maxwearlevel)
{
dev->maxwearlevel = level;
}
}
#ifdef CONFIG_MTD_SMART_SECTOR_ERASE_DEBUG
/* Also adjust the erase counts */
level = 255;
for (x = 0; x < dev->geo.neraseblocks; x++)
{
if (dev->erasecounts[x] < level)
{
level = dev->erasecounts[x];
}
}
if (level != 0)
{
for (x = 0; x < dev->geo.neraseblocks; x++)
{
dev->erasecounts[x] -= level;
}
}
#endif
}
#endif
/****************************************************************************
* Name: smart_set_wear_level
*
* Description: Sets the wear level of the specified block. The wear level
* is a 4-bit field packed 2 entries per byte and is mapped to
* a bit field which minimizes the number of 0 to 1 transitions
* such that entries can be updated on a NOR flash without the
* need to relocate the format sector (assuming CRC is not
* enabled, in which case a relocated is needed for ANY change).
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
static int smart_set_wear_level(FAR struct smart_struct_s *dev,
uint16_t block, uint8_t level)
{
uint8_t bits;
uint8_t oldlevel;
/* Get the old wear level to test if we need to update min / max */
oldlevel = smart_get_wear_level(dev, block);
/* Get the bit map for this wear level from the static map array */
if (level > 15)
{
_err("ERROR: Fatal Design Error! Wear level > 15, block=%d\n", block);
/* This is a design flaw, but we still allow processing, otherwise we
* will corrupt the volume. It's better to have a few blocks that are
* worn a bit more than to create an error condition on the volume.
*
* Set the level to the maximum value and add to the un-even wear count
* to keep track of the number of times this has happened.
*/
level = 15;
dev->uneven_wearcount++;
}
bits = g_wearlevel_to_bitmap4[level];
if (block & 0x01)
{
/* Use the upper nibble */
dev->wearstatus[block >> SMART_WEAR_BIT_DIVIDE] &= 0x0f;
dev->wearstatus[block >> SMART_WEAR_BIT_DIVIDE] |= bits << 4;
}
else
{
/* Use the lower nibble */
dev->wearstatus[block >> SMART_WEAR_BIT_DIVIDE] &= 0xf0;
dev->wearstatus[block >> SMART_WEAR_BIT_DIVIDE] |= bits;
}
/* Mark wear bits as dirty */
dev->wearflags |= SMART_WEARFLAGS_WRITE_NEEDED;
/* Test if min / max need to be updated */
if (oldlevel + 1 == level)
{
/* Test if max needs to be updated */
if (level > dev->maxwearlevel)
{
dev->maxwearlevel = level;
}
/* Test if this was the min level. If it was, then
* we need to rescan for min.
*/
if (oldlevel == dev->minwearlevel)
{
smart_find_wear_minmax(dev);
if (oldlevel != dev->minwearlevel)
finfo("##### New min wear level = %d\n", dev->minwearlevel);
}
}
return 0;
}
#endif
/****************************************************************************
* 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(FAR struct smart_struct_s *dev)
{
int sector;
int ret;
uint16_t totalsectors;
uint16_t sectorsize;
uint16_t prerelease;
uint16_t logicalsector;
uint16_t winner;
uint16_t loser;
uint32_t readaddress;
uint32_t offset;
uint16_t seq1;
uint16_t seq2;
uint16_t seqwrap;
struct smart_sect_header_s header;
#ifdef CONFIG_MTD_SMART_MINIMIZE_RAM
int dupsector;
uint16_t duplogsector;
#endif
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
int x;
char devname[32];
FAR struct smart_multiroot_device_s *rootdirdev;
#endif
static const uint16_t sizetbl[8] =
{
CONFIG_MTD_SMART_SECTOR_SIZE,
512, 1024, 4096, 2048, 8192, 16384, 32768
};
finfo("Entry\n");
/* Find the sector size on the volume by reading headers from
* sectors of decreasing size. On a formatted volume, the sector
* size is saved in the header status byte of search sector, so
* by starting with the largest supported sector size and
* decreasing from there, we will be sure to find data that is
* a header and not sector data.
*/
sectorsize = 0xffff;
offset = 16384;
while (sectorsize == 0xffff)
{
readaddress = 0;
while (readaddress < dev->erasesize * dev->geo.neraseblocks)
{
/* Read the next sector from the device */
ret = MTD_READ(dev->mtd, readaddress,
sizeof(struct smart_sect_header_s),
(FAR uint8_t *) &header);
if (ret != sizeof(struct smart_sect_header_s))
{
goto err_out;
}
if (header.status != CONFIG_SMARTFS_ERASEDSTATE)
{
sectorsize =
sizetbl[(header.status & SMART_STATUS_SIZEBITS) >> 2];
break;
}
readaddress += offset;
}
if (sectorsize == 0xffff)
{
sectorsize = CONFIG_MTD_SMART_SECTOR_SIZE;
}
offset >>= 1;
if (offset < 256 && sectorsize == 0xffff)
{
/* No valid sectors found on device. Default the
* sector size to the CONFIG value
*/
sectorsize = CONFIG_MTD_SMART_SECTOR_SIZE;
}
}
/* Now set the sectorsize and other sectorsize derived variables */
ret = smart_setsectorsize(dev, sectorsize);
if (ret != OK)
{
goto err_out;
}
/* Initialize the device variables */
totalsectors = dev->totalsectors;
dev->formatstatus = SMART_FMT_STAT_NOFMT;
dev->freesectors = dev->availsectperblk * dev->geo.neraseblocks;
dev->releasesectors = 0;
/* Initialize the freecount and releasecount arrays */
for (sector = 0; sector < dev->neraseblocks; sector++)
{
if (sector == dev->neraseblocks - 1 && dev->totalsectors == 65534)
{
prerelease = 2;
}
else
{
prerelease = 0;
}
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
smart_set_count(dev, dev->freecount, sector,
dev->availsectperblk - prerelease);
smart_set_count(dev, dev->releasecount, sector, prerelease);
#else
dev->freecount[sector] = dev->availsectperblk - prerelease;
dev->releasecount[sector] = prerelease;
#endif
}
/* Initialize the sector map */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
for (sector = 0; sector < totalsectors; sector++)
{
dev->smap[sector] = -1;
}
#else
/* Clear all logical sector used bits */
memset(dev->sbitmap, 0, (dev->totalsectors + 7) >> 3);
#endif
/* Now scan the MTD device */
/* At first, set the loser sector as the invalid value */
loser = totalsectors;
for (sector = 0; sector < totalsectors; sector++)
{
finfo("Scan sector %d\n", sector);
winner = 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),
(FAR uint8_t *)&header);
if (ret != sizeof(struct smart_sect_header_s))
{
goto err_out;
}
/* Get the logical sector number for this physical sector */
logicalsector = *((FAR 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 committed, therefore not free. Update the
* erase block's freecount.
*/
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
smart_add_count(dev, dev->freecount, sector / dev->sectorsperblk, -1);
#else
dev->freecount[sector / dev->sectorsperblk]--;
#endif
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))
{
/* Keep track of the total number of released sectors and
* released sectors per erase block.
*/
dev->releasesectors++;
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
smart_add_count(dev, dev->releasecount,
sector / dev->sectorsperblk, 1);
#else
dev->releasecount[sector / dev->sectorsperblk]++;
#endif
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 */
ferr("ERROR: 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,
(FAR uint8_t *)dev->rwbuffer);
if (ret != 32)
{
ferr("ERROR: 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;
}
/* 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(devname, 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 *)
smart_malloc(dev, sizeof(*rootdirdev), "Root Dir");
if (rootdirdev == NULL)
{
ferr("ERROR: Memory alloc failed\n");
ret = -ENOMEM;
goto err_out;
}
/* Populate the rootdirdev */
rootdirdev->dev = dev;
rootdirdev->rootdirnum = x;
/* 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 */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
if (dev->smap[logicalsector] != 0xffff)
#else
if (dev->sbitmap[logicalsector >> 3] & (1 << (logicalsector & 0x07)))
#endif
{
/* 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.
*/
#if SMART_STATUS_VERSION == 1
if ((header.status & SMART_STATUS_CRC) !=
(CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_CRC))
{
seq2 = header.seq;
}
else
{
seq2 = *((FAR uint16_t *) &header.seq);
}
#else
seq2 = header.seq;
#endif
/* We must re-read the 1st physical sector to get it's seq number */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
readaddress = dev->smap[logicalsector] * dev->mtdblkspersector *
dev->geo.blocksize;
#else
/* For minimize RAM, we have to rescan to find the 1st sector
* claiming to be this logical sector.
*/
for (dupsector = 0; dupsector < sector; dupsector++)
{
/* Calculate the read address for this sector */
readaddress = dupsector * dev->mtdblkspersector *
dev->geo.blocksize;
/* Read the header for this sector */
ret = MTD_READ(dev->mtd, readaddress,
sizeof(struct smart_sect_header_s),
(FAR uint8_t *) &header);
if (ret != sizeof(struct smart_sect_header_s))
{
goto err_out;
}
/* Get the logical sector number for this physical sector */
duplogsector = *((FAR uint16_t *) header.logicalsector);
#if CONFIG_SMARTFS_ERASEDSTATE == 0x00
if (duplogsector == 0)
{
duplogsector = -1;
}
#endif
/* Test if this sector has been committed */
if ((header.status & SMART_STATUS_COMMITTED) ==
(CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED))
{
continue;
}
/* Test if this sector has been release and skip it if it has */
if ((header.status & SMART_STATUS_RELEASED) !=
(CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_RELEASED))
{
continue;
}
if ((header.status & SMART_STATUS_VERBITS) !=
SMART_STATUS_VERSION)
{
continue;
}
/* Now compare if this logical sector matches the current
* sector
*/
if (duplogsector == logicalsector)
{
break;
}
}
#endif
ret = MTD_READ(dev->mtd, readaddress,
sizeof(struct smart_sect_header_s),
(FAR uint8_t *) &header);
if (ret != sizeof(struct smart_sect_header_s))
{
goto err_out;
}
#if SMART_STATUS_VERSION == 1
if ((header.status & SMART_STATUS_CRC) !=
(CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_CRC))
{
seq1 = header.seq;
seqwrap = 0xf0;
}
else
{
seq1 = *((FAR uint16_t *) &header.seq);
seqwrap = 0xfff0;
}
#else
seq1 = header.seq;
seqwrap = 0xf0;
#endif
/* Now determine who wins */
if ((seq1 > seqwrap && seq2 < 10) || seq2 > seq1)
{
/* Seq 2 is the winner ... bigger or it wrapped */
winner = sector;
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
loser = dev->smap[logicalsector];
#else
loser = dupsector;
#endif
}
else
{
/* We keep the original mapping and seq2 is the loser */
loser = sector;
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
winner = dev->smap[logicalsector];
#else
winner = smart_cache_lookup(dev, logicalsector);
#endif
}
finfo("Duplicate Sector winner=%d, loser=%d\n", winner, loser);
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
/* Check CRC of the winner sector just in case */
ret = MTD_BREAD(dev->mtd, winner * dev->mtdblkspersector,
dev->mtdblkspersector,
(FAR uint8_t *) dev->rwbuffer);
if (ret == dev->mtdblkspersector)
{
/* Validate the CRC of the read-back data */
ret = smart_validate_crc(dev);
}
if (ret != OK)
{
/* The winner sector has CRC error, so we select the loser
* sector. After swapping the winner and the loser sector, we
* will release the loser sector with CRC error.
*/
if (sector == winner)
{
/* winner: sector(CRC error) -> origin
* loser : origin -> sector(CRC error)
*/
winner = loser;
loser = sector;
}
else
{
/* winner: origin(CRC error) -> sector
* loser : sector -> origin(CRC error)
*/
loser = winner;
winner = sector;
}
finfo("Duplicate Sector winner=%d, loser=%d\n", winner, loser);
}
#endif /* CONFIG_MTD_SMART_ENABLE_CRC */
/* Now release the loser sector */
readaddress = loser * dev->mtdblkspersector * dev->geo.blocksize;
ret = MTD_READ(dev->mtd, readaddress,
sizeof(struct smart_sect_header_s),
(FAR 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)
{
ferr("ERROR: Error %d releasing duplicate sector\n", -ret);
goto err_out;
}
}
/* Test if this sector is loser of duplicate logical sector */
if (sector == loser)
{
continue;
}
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
/* Update the logical to physical sector map */
dev->smap[logicalsector] = winner;
#else
/* Mark the logical sector as used in the bitmap */
dev->sbitmap[logicalsector >> 3] |= 1 << (logicalsector & 0x07);
if (logicalsector < SMART_FIRST_ALLOC_SECTOR)
{
smart_add_sector_to_cache(dev, logicalsector, winner, __LINE__);
}
#endif
}
#if defined (CONFIG_MTD_SMART_WEAR_LEVEL) && (SMART_STATUS_VERSION == 1)
#ifdef CONFIG_MTD_SMART_CONVERT_WEAR_FORMAT
/* We need to check if we are converting an older format with incorrect
* wear leveling data in sector zero to the new format. The old format
* put all zeros in the wear level bit locations, but the new (better)
* way is to leave them 0xff.
*/
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
sector = dev->smap[0];
#else
sector = smart_cache_lookup(dev, 0);
#endif
/* Validate the sector is valid ... may be an unformatted device */
if (sector != 0xffff)
{
/* Read the sector data */
ret = MTD_BREAD(dev->mtd, sector * dev->mtdblkspersector,
dev->mtdblkspersector, (uint8_t *) dev->rwbuffer);
if (ret != dev->mtdblkspersector)
{
ferr("ERROR: Error reading physical sector %d.\n", sector);
goto err_out;
}
/* Check for old format wear leveling */
if (dev->rwbuffer[SMART_WEAR_LEVEL_FORMAT_SIG] == 0)
{
/* Old format detected. We must relocate sector zero and fill it
* in with 0xff.
*/
uint16_t newsector = smart_findfreephyssector(dev, FALSE);
if (newsector == 0xffff)
{
/* Unable to find a free sector!!! */
ferr("ERROR: Can't find a free sector for relocation\n");
ret = -ENOSPC;
goto err_out;
}
memset(&dev->rwbuffer[SMART_WEAR_LEVEL_FORMAT_SIG], 0xff,
dev->mtdblkspersector * dev->geo.blocksize -
SMART_WEAR_LEVEL_FORMAT_SIG);
smart_relocate_sector(dev, sector, newsector);
/* Update the free and release sector counts */
dev->freesectors--;
dev->releasesectors++;
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
dev->smap[0] = newsector;
dev->freecount[newsector / dev->sectorsperblk]--;
dev->releasecount[sector / dev->sectorsperblk]++;
#else
smart_update_cache(dev, 0, newsector);
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
smart_add_count(dev, dev->freecount,
newsector / dev->sectorsperblk, -1);
smart_add_count(dev, dev->releasecount,
sector / dev->sectorsperblk, 1);
#endif
#endif
}
}
#endif /* CONFIG_MTD_SMART_CONVERT_WEAR_FORMAT */
#endif /* CONFIG_MTD_SMART_WEAR_LEVEL && SMART_STATUS_VERSION == 1 */
#ifdef CONFIG_MTD_SMART_FSCK
smart_fsck(dev);
#endif
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
/* Read the wear leveling status bits */
smart_read_wearstatus(dev);
#endif
finfo("SMART Scan\n");
finfo(" Erase size: %10d\n", dev->sectorsperblk * dev->sectorsize);
finfo(" Erase count: %10d\n", dev->neraseblocks);
finfo(" Sect/block: %10d\n", dev->sectorsperblk);
finfo(" MTD Blk/Sect: %10d\n", dev->mtdblkspersector);
/* Validate the geometry */
if (dev->mtdblkspersector == 0 || dev->sectorsperblk == 0 ||
dev->sectorsperblk == 0 || dev->sectorsize == 0)
{
ferr("ERROR: Invalid Geometry!\n");
ret = -EINVAL;
goto err_out;
}
#ifdef CONFIG_MTD_SMART_ALLOC_DEBUG
finfo(" Allocations:\n");
for (sector = 0; sector < SMART_MAX_ALLOCS; sector++)
{
if (dev->alloc[sector].ptr != NULL)
{
finfo(" %s: %d\n",
dev->alloc[sector].name, dev->alloc[sector].size);
}
}
#endif
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(FAR struct smart_struct_s *dev,
FAR struct smart_format_s *fmt,
uint8_t rootdirnum)
#else
static inline int smart_getformat(FAR struct smart_struct_s *dev,
FAR struct smart_format_s *fmt)
#endif
{
int ret;
finfo("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->totalsectors;
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 */
fmt->nfreesectors += dev->releasesectors;
/* Subtract the reserved sector count */
fmt->nfreesectors -= dev->sectorsperblk + 4;
ret = OK;
err_out:
return ret;
}
/****************************************************************************
* Name: smart_erase_block_if_empty
*
* Description: Tests the specified erase block if it contains all free or
* released sectors and erases it.
*
****************************************************************************/
static void smart_erase_block_if_empty(FAR struct smart_struct_s *dev,
uint16_t block, uint8_t forceerase)
{
uint16_t freecount;
uint16_t releasecount;
uint16_t prerelease;
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
releasecount = smart_get_count(dev, dev->releasecount, block);
freecount = smart_get_count(dev, dev->freecount, block);
#else
releasecount = dev->releasecount[block];
freecount = dev->freecount[block];
#endif
if ((freecount + releasecount == dev->availsectperblk && freecount < 1) ||
forceerase)
{
/* Erase the block */
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_SMARTFS)
dev->unusedsectors += freecount;
dev->blockerases++;
#endif
MTD_ERASE(dev->mtd, block, 1);
#ifdef CONFIG_MTD_SMART_SECTOR_ERASE_DEBUG
if (dev->erasecounts)
{
dev->erasecounts[block]++;
}
#endif
/* If wear leveling enabled, then we must add one to the wear status */
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
smart_set_wear_level(dev, block, smart_get_wear_level(dev, block) + 1);
#endif
/* If we have a device with 65534 sectors, then disallow the last two
* physical sector if this is the last erase block on the device.
*/
if (block == dev->geo.neraseblocks - 1 && dev->totalsectors == 65534)
{
prerelease = 2;
}
else
{
prerelease = 0;
}
dev->freesectors += dev->availsectperblk - prerelease - freecount;
dev->releasesectors -= releasecount - prerelease;
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
smart_set_count(dev, dev->releasecount, block, prerelease);
smart_set_count(dev, dev->freecount, block,
dev->availsectperblk - prerelease);
#else
dev->releasecount[block] = prerelease;
dev->freecount[block] = dev->availsectperblk - prerelease;
#endif
/* Now that we have erased this block and updated the release / free
* counts, if we are in WEAR LEVELING enabled mode, we must check if
* this erase block's wear level has reached the threshold to warrant
* moving a minimum wear level block's data into it (i.e. relocating
* static data to this block so it will be worn less).
*/
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
if (!forceerase)
{
smart_relocate_static_data(dev, block);
}
#endif
#ifdef CONFIG_SMART_LOCAL_CHECKFREE
if (smart_checkfree(dev, __LINE__) != OK)
{
fwarn(" ...while eraseing block %d\n", block);
}
#endif
}
}
/****************************************************************************
* Name: smart_relocate_static_data
*
* Description: Tests if the specified block has reached the wear threshold
* for static data relocation and if it has, relocates a less
* worn block to it.
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
static int smart_relocate_static_data(FAR struct smart_struct_s *dev,
uint16_t block)
{
uint16_t freecount;
uint16_t x;
uint16_t sector;
uint16_t minblock;
uint16_t nextsector;
uint16_t newsector;
uint16_t mincount;
int ret;
FAR struct smart_sect_header_s *header;
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
FAR struct smart_allocsector_s *allocsector;
#endif
/* Now that we have erased this block and updated the release / free
* counts, if we are in WEAR LEVELING enabled mode, we must check if this
* erase block's wear level has reached the threshold to warrant moving a
* minimum wear level block's data into it (i.e. relocating static data to
* this block so it will be worn less).
*/
ret = OK;
header = (FAR struct smart_sect_header_s *) dev->rwbuffer;
#ifdef CONFIG_SMART_LOCAL_CHECKFREE
if (smart_checkfree(dev, __LINE__) != OK)
{
fwarn(" ...about to relocate static data %d\n", block);
}
#endif
if (smart_get_wear_level(dev, block) >= SMART_WEAR_FULL_RELOCATE_THRESHOLD)
{
/* Okay, this block is getting too worn. Move a minimum wear level
* block to it in it's entirety.
*/
/* Scan all erase blocks (or until we find a minimum wear level block
* with no free + released blocks.
*/
freecount = dev->sectorsperblk + 1;
minblock = dev->geo.neraseblocks;
mincount = 0;
for (x = 0; x < dev->geo.neraseblocks; x++)
{
if (smart_get_wear_level(dev, x) == dev->minwearlevel)
{
/* Don't allow the format sector or directory sector to
* be moved into a worn block. First get the format and
* dir sectors.
*/
mincount++;
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
if (smart_get_count(dev, dev->releasecount, x) +
smart_get_count(dev, dev->freecount, x) < freecount)
{
freecount = smart_get_count(dev, dev->releasecount, x) +
smart_get_count(dev, dev->freecount, x);
minblock = x;
}
#else
if (dev->freecount[x] + dev->releasecount[x] < freecount)
{
freecount = dev->freecount[x] + dev->releasecount[x];
minblock = x;
}
#endif
/* Break if freecount reaches zero */
if (freecount == 0)
{
/* We found a minimum wear-level block with no free
* sectors. relocate this block to the more highly worn
* block.
*/
break;
}
}
}
/* Okay, now move block 'x' to block 'block' and erase block 'x' */
x = minblock;
/* We are resuing nextsector and newsector variables here simply as
* variables for displaying debug data. I have learned through my
* years of programming that this is a really good way to create
* spaghetti code, but I didn't want to add stack variables just
* for debug data, and I *know* these variables aren't being used
* yet.
*/
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
nextsector = smart_get_count(dev, dev->freecount, x);
newsector = smart_get_count(dev, dev->releasecount, x);
#else
nextsector = dev->freecount[x];
newsector = dev->releasecount[x];
#endif
finfo("Moving block %d, wear %d, free %d, "
"released %d to block %d, wear %d\n",
x, smart_get_wear_level(dev, x),
nextsector, newsector,
block, smart_get_wear_level(dev, block));
nextsector = block * dev->sectorsperblk;
for (sector = x * dev->sectorsperblk; sector <
x * dev->sectorsperblk + dev->availsectperblk; sector++)
{
/* Read the next sector from this erase block */
ret = MTD_BREAD(dev->mtd, sector * dev->mtdblkspersector,
dev->mtdblkspersector, (FAR uint8_t *) dev->rwbuffer);
if (ret != dev->mtdblkspersector)
{
ferr("ERROR: Error reading sector %d\n", sector);
ret = -EIO;
goto errout;
}
/* Test if the block is in use */
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
/* Check if there is a temporary alloc for this physical sector */
allocsector = dev->allocsector;
while (allocsector)
{
if (allocsector->physical == sector)
{
break;
}
allocsector = allocsector->next;
}
/* If we found a temp allocation, just update the mapped physical
* location and move on to the next block ... there is no data to
* move yet.
*/
if (allocsector)
{
/* Get next sector from 'block' */
newsector = nextsector++;
if (newsector == 0xffff)
{
/* Unable to find a free sector!!! */
ferr("ERROR: Can't find a free sector for relocation\n");
ret = -ENOSPC;
goto errout;
}
/* Update the temporary allocation's physical sector */
allocsector->physical = newsector;
*((FAR uint16_t *) header->logicalsector) =
allocsector->logical;
}
else
#endif
{
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 = nextsector++;
/* Relocate the sector data */
if ((ret = smart_relocate_sector(dev, sector, newsector)) < 0)
{
goto errout;
}
}
dev->freesectors--;
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
dev->smap[*((FAR uint16_t *) header->logicalsector)] = newsector;
#else
smart_update_cache(dev, *((FAR uint16_t *)header->logicalsector),
newsector);
#endif
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
smart_add_count(dev, dev->freecount, block, -1);
#else
dev->freecount[block]--;
#endif /* CONFIG_MTD_SMART_PACK_COUNTS */
}
#ifdef CONFIG_SMART_LOCAL_CHECKFREE
if (smart_checkfree(dev, __LINE__) != OK)
{
fwarn(" ...about to erase static block %d\n", block);
}
#endif
/* Now erase the block we just relocated, force erasing it */
smart_erase_block_if_empty(dev, x, TRUE);
}
#ifdef CONFIG_SMART_LOCAL_CHECKFREE
if (smart_checkfree(dev, __LINE__) != OK)
{
fwarn(" ...done erasing static block %d\n", block);
}
#endif
errout:
return ret;
}
#endif
/****************************************************************************
* Name: smart_calc_sector_crc
*
* Description: Calculate the CRC value for the sector data in the RW buffer
* based on the configured CRC size.
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
static crc_t smart_calc_sector_crc(FAR struct smart_struct_s *dev)
{
crc_t crc = 0;
#ifdef CONFIG_SMART_CRC_8
/* Calculate CRC on data region of the sector */
crc = crc8((FAR uint8_t *)
&dev->rwbuffer[sizeof(struct smart_sect_header_s)],
dev->mtdblkspersector * dev->geo.blocksize -
sizeof(struct smart_sect_header_s));
/* Add logical sector number and seq to the CRC calculation */
crc = crc8part((FAR uint8_t *)dev->rwbuffer, 3, crc);
/* Add status to the CRC calculation */
crc = crc8part((FAR uint8_t *)
&dev->rwbuffer[offsetof(struct smart_sect_header_s, status)], 1, crc);
#elif defined(CONFIG_SMART_CRC_16)
/* Calculate CRC on data region of the sector */
crc = crc16((FAR uint8_t *)
&dev->rwbuffer[sizeof(struct smart_sect_header_s)],
dev->mtdblkspersector * dev->geo.blocksize -
sizeof(struct smart_sect_header_s));
/* Add logical sector number to the CRC calculation */
crc = crc16part((FAR uint8_t *) dev->rwbuffer, 2, crc);
/* Add status and seq to the CRC calculation */
crc = crc16part((uint8_t *)
&dev->rwbuffer[offsetof(struct smart_sect_header_s, status)], 2, crc);
#elif defined(CONFIG_SMART_CRC_32)
/* Calculate CRC on data region of the sector */
crc = crc32((FAR uint8_t *)
&dev->rwbuffer[sizeof(struct smart_sect_header_s)],
dev->mtdblkspersector * dev->geo.blocksize -
sizeof(struct smart_sect_header_s));
/* Add logical sector number, status and seq to the CRC calculation */
crc = crc32part((FAR uint8_t *) dev->rwbuffer, 6, crc);
#else
#error "Unknown CRC size!"
#endif
return crc;
}
#endif /* CONFIG_MTD_SMART_ENABLE_CRC */
/****************************************************************************
* 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.
*
* Input Parameters:
*
* arg: Upper 16 bits contains the sector size
* Lower 16 bits contains the number of root dir entries
*
****************************************************************************/
static inline int smart_llformat(FAR struct smart_struct_s *dev,
unsigned long arg)
{
FAR struct smart_sect_header_s *sectorheader;
size_t wrcount;
int x;
int ret;
uint8_t sectsize;
uint8_t prerelease;
uint16_t sectorsize;
finfo("Entry\n");
/* Get the sector size from the provided arg */
sectorsize = arg >> 16;
if (sectorsize == 0)
{
sectorsize = CONFIG_MTD_SMART_SECTOR_SIZE;
}
/* Set the sector size for the device */
ret = smart_setsectorsize(dev, sectorsize);
if (ret != OK)
{
return ret;
}
/* Check for invalid format */
if (dev->erasesize == 0 || dev->sectorsperblk == 0)
{
dev->erasesize = dev->geo.erasesize;
ferr("ERROR: Invalid geometery ... "
"Sectors per erase block must be 1-256\n");
ferr(" Erase block size = %" PRId32 "\n",
dev->erasesize);
ferr(" Sector size = %d\n",
dev->sectorsize);
ferr(" Sectors/erase block = %" PRId32 "\n",
dev->erasesize / dev->sectorsize);
return -EINVAL;
}
/* 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. */
sectorheader = (FAR struct smart_sect_header_s *) dev->rwbuffer;
memset(dev->rwbuffer, CONFIG_SMARTFS_ERASEDSTATE, dev->sectorsize);
#if SMART_STATUS_VERSION == 1
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
/* CRC enabled. Using an 8-bit sequence number */
sectorheader->seq = 0;
#else
/* CRC not enabled. Using a 16-bit sequence number */
*((FAR uint16_t *) &sectorheader->seq) = 0;
#endif
#else /* SMART_STATUS_VERSION == 1 */
sectorheader->seq = 0;
#endif /* SMART_STATUS_VERSION == 1 */
/* Set the sector size of this sector */
sectsize = dev->sectorsize < 4096 ? (dev->sectorsize >> 9) :
dev->sectorsize == 4096 ? 3 : 5 + (dev->sectorsize >> 14);
sectsize <<= 2;
/* Set the sector logical sector to zero and setup the header status */
#if ( CONFIG_SMARTFS_ERASEDSTATE == 0xff )
*((FAR uint16_t *) sectorheader->logicalsector) = 0;
sectorheader->status = (uint8_t)~(SMART_STATUS_COMMITTED |
SMART_STATUS_VERBITS |
SMART_STATUS_SIZEBITS) |
SMART_STATUS_VERSION |
sectsize;
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
sectorheader->status &= ~SMART_STATUS_CRC;
#endif /* CONFIG_MTD_SMART_ENABLE_CRC */
#else /* CONFIG_SMARTFS_ERASEDSTATE == 0xff */
*((FAR uint16_t *) sectorheader->logicalsector) = 0xffff;
sectorheader->status = (uint8_t)(SMART_STATUS_COMMITTED |
SMART_STATUS_VERSION |
sectsize);
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
sectorheader->status |= SMART_STATUS_CRC;
#endif /* CONFIG_MTD_SMART_ENABLE_CRC */
#endif /* CONFIG_SMARTFS_ERASEDSTATE == 0xff */
/* 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 & 0xff);
#ifdef CONFIG_SMART_CRC_8
sectorheader->crc8 = smart_calc_sector_crc(dev);
#elif defined(CONFIG_SMART_CRC_16)
*((uint16_t *) sectorheader->crc16) = smart_calc_sector_crc(dev);
#elif defined(CONFIG_SMART_CRC_32)
*((uint32_t *) sectorheader->crc32) = smart_calc_sector_crc(dev);
#endif
/* Write the sector to the flash */
wrcount = MTD_BWRITE(dev->mtd, 0, dev->mtdblkspersector,
(FAR uint8_t *) dev->rwbuffer);
if (wrcount != dev->mtdblkspersector)
{
/* The block is not empty!! What to do? */
ferr("ERROR: Write block 0 failed: %zu.\n", wrcount);
/* Unlock the mutex if we add one */
return -EIO;
}
/* Now initialize our internal control variables */
ret = smart_setsectorsize(dev, sectorsize);
if (ret != OK)
{
return ret;
}
dev->formatstatus = SMART_FMT_STAT_UNKNOWN;
dev->freesectors = dev->availsectperblk * dev->geo.neraseblocks - 1;
dev->releasesectors = 0;
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
dev->uneven_wearcount = 0;
#endif
/* Initialize the released and free counts */
for (x = 0; x < dev->neraseblocks; x++)
{
/* Test for a geometry with 65536 sectors. We allow this, though
* we never use the last two sectors in this mode.
*/
if (x == dev->neraseblocks && dev->totalsectors == 65534)
{
prerelease = 2;
}
else
{
prerelease = 0;
}
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
smart_set_count(dev, dev->releasecount, x, prerelease);
smart_set_count(dev, dev->freecount, x,
dev->availsectperblk - prerelease);
#else
dev->releasecount[x] = prerelease;
dev->freecount[x] = dev->availsectperblk - prerelease;
#endif
}
/* Account for the format sector */
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
smart_set_count(dev, dev->freecount, 0, dev->availsectperblk - 1);
#else
dev->freecount[0]--;
#endif
/* Now initialize the logical to physical sector map */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
dev->smap[0] = 0; /* Logical sector zero = physical sector 0 */
for (x = 1; x < dev->totalsectors; x++)
{
/* Mark all other logical sectors as non-existent */
dev->smap[x] = -1;
}
#endif
#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;
}
/****************************************************************************
* Name: smart_relocate_sector
*
* Description: Relocates the specified sector to the new sector location.
*
****************************************************************************/
static int smart_relocate_sector(FAR struct smart_struct_s *dev,
uint16_t oldsector, uint16_t newsector)
{
size_t offset;
FAR struct smart_sect_header_s *header;
uint8_t newstatus;
int ret;
header = (FAR struct smart_sect_header_s *) dev->rwbuffer;
/* Increment the sequence number and clear the "commit" flag */
#if SMART_STATUS_VERSION == 1
if ((header->status & SMART_STATUS_CRC) !=
(CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_CRC))
{
#endif
/* Using 8-bit sequence */
header->seq++;
if (header->seq == 0xff)
{
header->seq = 1;
}
#if SMART_STATUS_VERSION == 1
}
else
{
/* Using 16-bit sequence and no CRC */
(*((FAR uint16_t *) &header->seq))++;
if (*((FAR uint16_t *) &header->seq) == 0xffff)
{
*((FAR uint16_t *) &header->seq) = 1;
}
}
#endif
/* When CRC is enabled, we must pre-commit the sector and also
* calculate an updated CRC for the sector prior to writing
* since we changed the sequence number.
*/
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
/* First pre-commit the sector */
#if CONFIG_SMARTFS_ERASEDSTATE == 0xff
header->status &= ~(SMART_STATUS_COMMITTED | SMART_STATUS_CRC);
#else
header->status |= SMART_STATUS_COMMITTED | SMART_STATUS_CRC;
#endif
/* Now calculate the new CRC */
#ifdef CONFIG_SMART_CRC_8
header->crc8 = smart_calc_sector_crc(dev);
#elif defined(CONFIG_SMART_CRC_16)
*((uint16_t *) header->crc16) = smart_calc_sector_crc(dev);
#elif defined(CONFIG_SMART_CRC_32)
*((uint32_t *) header->crc32) = smart_calc_sector_crc(dev);
#endif
/* Write the data to the new physical sector location */
ret = MTD_BWRITE(dev->mtd, newsector * dev->mtdblkspersector,
dev->mtdblkspersector, (FAR uint8_t *) dev->rwbuffer);
if (ret != dev->mtdblkspersector)
{
ferr("Error writing to new sector %d\n", newsector);
goto errout;
}
#else /* CONFIG_MTD_SMART_ENABLE_CRC */
#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, (FAR uint8_t *) dev->rwbuffer);
if (ret != dev->mtdblkspersector)
{
ferr("Error writing to new sector %d\n", newsector);
goto errout;
}
/* 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)
{
ferr("ERROR: Error %d committing new sector %d\n" -ret, newsector);
goto errout;
}
#endif /* CONFIG_MTD_SMART_ENABLE_CRC */
/* Release the old physical sector */
#if CONFIG_SMARTFS_ERASEDSTATE == 0xff
newstatus = header->status & ~(SMART_STATUS_RELEASED |
SMART_STATUS_COMMITTED);
#else
newstatus = header->status | SMART_STATUS_RELEASED |
SMART_STATUS_COMMITTED;
#endif
offset = oldsector * dev->mtdblkspersector * dev->geo.blocksize +
offsetof(struct smart_sect_header_s, status);
ret = smart_bytewrite(dev, offset, 1, &newstatus);
if (ret < 0)
{
ferr("ERROR: Error %d releasing old sector %d\n" -ret, oldsector);
}
errout:
return ret;
}
/****************************************************************************
* Name: smart_relocate_block
*
* Description: Relocates the specified MTD erase block by moving any
* active sectors to a different erase block and then erases
* the selected block.
*
****************************************************************************/
static int smart_relocate_block(FAR struct smart_struct_s *dev,
uint16_t block)
{
uint16_t newsector;
uint16_t oldrelease;
int x;
int ret;
FAR struct smart_sect_header_s *header;
uint8_t prerelease;
uint16_t freecount;
#if defined(CONFIG_SMART_LOCAL_CHECKFREE) && defined(CONFIG_DEBUG_FS)
uint16_t releasecount;
#endif
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
FAR struct smart_allocsector_s *allocsector;
#endif
/* 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.
*/
header = (FAR struct smart_sect_header_s *) dev->rwbuffer;
#ifdef CONFIG_SMART_LOCAL_CHECKFREE
if (smart_checkfree(dev, __LINE__) != OK)
{
fwarn(" ...while relocating block %d, free=%d\n",
block, dev->freesectors);
}
#endif
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
freecount = smart_get_count(dev, dev->freecount, block);
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_SMARTFS)
#if defined(CONFIG_SMART_LOCAL_CHECKFREE) && defined(CONFIG_DEBUG_FS)
releasecount = smart_get_count(dev, dev->releasecount, block);
#endif
#endif
/* Ensure we aren't relocating a block containing the only free sectors */
if (freecount >= dev->freesectors)
{
ferr("ERROR: Program bug! "
"Relocating the only block (%d) with free sectors!\n",
block);
ret = -EIO;
goto errout;
}
smart_set_count(dev, dev->freecount, block, 0);
#else /* CONFIG_MTD_SMART_PACK_COUNTS */
freecount = dev->freecount[block];
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_SMARTFS)
#if defined(CONFIG_SMART_LOCAL_CHECKFREE) && defined(CONFIG_DEBUG_FS)
releasecount = dev->releasecount[block];
#endif
#endif
dev->freecount[block] = 0;
#endif
/* Next move all live data in the block to a new home. */
for (x = block * dev->sectorsperblk; x <
block * dev->sectorsperblk + dev->availsectperblk; x++)
{
/* Read the next sector from this erase block */
ret = MTD_BREAD(dev->mtd, x * dev->mtdblkspersector,
dev->mtdblkspersector, (FAR uint8_t *) dev->rwbuffer);
if (ret != dev->mtdblkspersector)
{
ferr("ERROR: Error reading sector %d\n", x);
ret = -EIO;
goto errout;
}
/* Test if the block is in use */
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
/* Check if there is a temporary alloc for this physical sector */
allocsector = dev->allocsector;
while (allocsector)
{
if (allocsector->physical == x)
break;
allocsector = allocsector->next;
}
/* If we found a temp allocation, just update the mapped physical
* location and move on to the next block ... there is no data to
* move yet.
*/
if (allocsector)
{
newsector = smart_findfreephyssector(dev, FALSE);
if (newsector == 0xffff)
{
/* Unable to find a free sector!!! */
ferr("ERROR: Can't find a free sector for relocation\n");
ret = -ENOSPC;
goto errout;
}
/* Update the temporary allocation's physical sector */
allocsector->physical = newsector;
*((FAR uint16_t *) header->logicalsector) = allocsector->logical;
}
else
#endif
{
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, FALSE);
if (newsector == 0xffff)
{
/* Unable to find a free sector!!! */
ferr("ERROR: Can't find a free sector for relocation\n");
ret = -ENOSPC;
goto errout;
}
/* Relocate the sector data */
if ((ret = smart_relocate_sector(dev, x, newsector)) < 0)
{
goto errout;
}
}
/* Update the variables */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
dev->smap[*((FAR uint16_t *) header->logicalsector)] = newsector;
#else
smart_update_cache(dev, *((FAR uint16_t *) header->logicalsector),
newsector);
#endif
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
smart_add_count(dev, dev->freecount, newsector / dev->sectorsperblk,
-1);
#else
dev->freecount[newsector / dev->sectorsperblk]--;
#endif
}
/* Now erase the erase block */
MTD_ERASE(dev->mtd, block, 1);
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_SMARTFS)
dev->unusedsectors += freecount;
dev->blockerases++;
#endif
#ifdef CONFIG_MTD_SMART_SECTOR_ERASE_DEBUG
if (dev->erasecounts)
{
dev->erasecounts[block]++;
}
#endif
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
/* Update the new wear level count */
smart_set_wear_level(dev, block, smart_get_wear_level(dev, block) + 1);
#endif
/* Update the free and release sectors for this erase block. */
if (x == dev->neraseblocks && dev->totalsectors == 65534)
{
/* We can't use the last two sectors on a 65536 sector device,
* so "pre-release" them so they never get allocated.
*/
prerelease = 2;
}
else
{
prerelease = 0;
}
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
oldrelease = smart_get_count(dev, dev->releasecount, block);
dev->freesectors += oldrelease - prerelease;
dev->releasesectors -= oldrelease - prerelease;
smart_set_count(dev, dev->freecount, block,
dev->availsectperblk - prerelease);
smart_set_count(dev, dev->releasecount, block, prerelease);
#else
oldrelease = dev->releasecount[block];
dev->freesectors += oldrelease - prerelease;
dev->releasesectors -= oldrelease - prerelease;
dev->freecount[block] = dev->availsectperblk - prerelease;
dev->releasecount[block] = prerelease;
#endif
#ifdef CONFIG_SMART_LOCAL_CHECKFREE
if (smart_checkfree(dev, __LINE__) != OK)
{
fwarn(" ...while relocating block %d, "
"free=%d, release=%d, oldrelease=%d\n",
block, freecount, releasecount, oldrelease);
}
#endif
/* Test if this erase causes the block to reach the full relocate
* threshold requiring static data relocation.
*/
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
smart_relocate_static_data(dev, block);
#endif
return OK;
errout:
/* Restore the block's freecount if error */
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
smart_set_count(dev, dev->freecount, block, freecount);
#else
dev->freecount[block] = freecount;
#endif
return ret;
}
/****************************************************************************
* 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(FAR struct smart_struct_s *dev,
uint8_t canrelocate)
{
uint16_t count;
uint16_t allocfreecount;
uint16_t allocblock;
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
uint16_t wornfreecount;
uint16_t wornblock;
uint8_t wearlevel;
uint8_t wornlevel;
uint8_t maxwearlevel;
#endif
uint16_t physicalsector;
uint16_t block;
uint32_t readaddr;
struct smart_sect_header_s header;
int ret;
uint16_t i;
/* 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.
*/
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
retry:
#endif
allocfreecount = 0;
allocblock = 0xffff;
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
wornfreecount = 0;
wornblock = 0xffff;
wornlevel = 15;
maxwearlevel = 0;
#endif
physicalsector = 0xffff;
if (++dev->lastallocblock >= dev->neraseblocks)
{
dev->lastallocblock = 0;
}
block = dev->lastallocblock;
for (i = 0; i < dev->neraseblocks; i++)
{
/* Test if this block has more free blocks than the
* currently selected block
*/
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
count = smart_get_count(dev, dev->freecount, block);
#else
count = dev->freecount[block];
#endif
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
/* Keep track of the block with the max free sectors that is worn */
wearlevel = smart_get_wear_level(dev, block);
if (wearlevel >= SMART_WEAR_FULL_RELOCATE_THRESHOLD)
{
if (wearlevel > maxwearlevel && count > 0)
{
maxwearlevel = wearlevel;
}
if (count > wornfreecount || (count > 0 && wearlevel < wornlevel))
{
/* Keep track of this block. If there are only worn blocks
* with free sectors left, then we will use it.
*/
if (i < dev->neraseblocks - 1 || !wornfreecount)
{
wornfreecount = count;
wornblock = block;
wornlevel = wearlevel;
}
}
}
else
#endif
if (count > allocfreecount)
{
/* Assign this block to alloc from */
if (i < dev->neraseblocks - 1 || !allocfreecount)
{
allocblock = block;
allocfreecount = count;
}
}
if (++block >= dev->neraseblocks)
{
block = 0;
}
}
/* Check if we found an allocblock. */
if (allocblock == 0xffff)
{
/* No un-worn blocks with free sectors */
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
/* If we are allowed to relocate unworn blocks then do so now */
if (canrelocate && wornfreecount < (dev->sectorsperblk >> 2) &&
wornlevel == maxwearlevel)
{
/* Relocate up to 8 unworn blocks */
block = 0;
for (i = 0; i < 8; )
{
if (smart_get_wear_level(dev, block) <
SMART_WEAR_FORCE_REORG_THRESHOLD)
{
if (smart_relocate_block(dev, block) < 0)
{
ferr("ERROR: Error relocating block while finding "
"free phys sector\n");
return -1;
}
i++;
}
block++;
}
if (i > 0)
{
/* Disable relocate for retry */
canrelocate = FALSE;
goto retry;
}
}
else
{
dev->wearflags |= SMART_WEARFLAGS_FORCE_REORG;
}
/* Test if we found a worn block with free sectors */
if (wornblock != 0xffff)
{
allocblock = wornblock;
}
else
#endif
{
char buffer[8 * 12 + 1];
long remaining;
int j;
int k;
ferr("ERROR: Program bug! Expected a free sector, free=%d\n",
dev->freesectors);
for (i = 0, remaining = dev->neraseblocks;
remaining > 0;
i += 8, remaining -= 8)
{
for (j = 0, k = 0; j < 8 && j < remaining ; j++)
{
k += sprintf(&buffer[k], "%12d", dev->freecount[i + j]);
}
ferr("%04x:%s\n", i, buffer);
}
/* No free sectors found! Bug? */
return -ENOSPC;
}
}
/* Now find a free physical sector within this selected erase block to
* allocate.
*/
for (i = allocblock * dev->sectorsperblk;
i < allocblock * dev->sectorsperblk + dev->availsectperblk; i++)
{
/* Check if this physical sector is available. */
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
/* First check if there is a temporary alloc in place */
FAR struct smart_allocsector_s *allocsect;
allocsect = dev->allocsector;
while (allocsect)
{
if (allocsect->physical == i)
{
break;
}
allocsect = allocsect->next;
}
/* If we found this physical sector above, then continue on
* to the next physical sector in this block ... this one has
* a temporary allocation assigned.
*/
if (allocsect)
{
continue;
}
#endif
/* Now check on the physical media */
readaddr = i * dev->mtdblkspersector * dev->geo.blocksize;
ret = MTD_READ(dev->mtd, readaddr, sizeof(struct smart_sect_header_s),
(FAR uint8_t *) &header);
if (ret != sizeof(struct smart_sect_header_s))
{
ferr("ERROR: Error reading phys sector %d\n", physicalsector);
return -1;
}
if ((*((FAR uint16_t *) header.logicalsector) == 0xffff) &&
#if SMART_STATUS_VERSION == 1
(*((FAR uint16_t *) &header.seq) == 0xffff) &&
#else
(header.seq == CONFIG_SMARTFS_ERASEDSTATE) &&
#endif
((header.status & SMART_STATUS_COMMITTED) ==
(CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED)))
{
physicalsector = i;
dev->lastallocblock = allocblock;
break;
}
else
{
/* The FLASH may be not erased in the initial delivery state.
* Just in case for the recovery of this fatal situation,
* after once erasing the sector, return the sector as a free
* sector.
*/
if (1 == dev->availsectperblk)
{
MTD_ERASE(dev->mtd, allocblock, 1);
physicalsector = i;
dev->lastallocblock = allocblock;
break;
}
}
}
if (physicalsector == 0xffff)
{
ferr("ERROR: Program bug! Expected a free sector\n");
}
if (physicalsector >= dev->totalsectors)
{
ferr("ERROR: Program bug! Selected sector too big!!!\n");
}
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.
*
****************************************************************************/
static int smart_garbagecollect(FAR struct smart_struct_s *dev)
{
uint16_t collectblock;
uint16_t releasemax;
bool collect = TRUE;
int x;
int ret;
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
uint8_t count;
#endif
while (collect)
{
collect = FALSE;
/* Test if the released sectors count is greater than the
* free sectors. If it is, then we will do garbage collection.
*/
if (dev->releasesectors > dev->freesectors && dev->freesectors <
(dev->totalsectors >> 5))
{
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)
{
/* Find the block with the most released sectors */
collectblock = 0xffff;
releasemax = 0;
for (x = 0; x < dev->neraseblocks; x++)
{
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
/* Don't collect blocks that have been worn completely */
if (smart_get_wear_level(dev, x) >= SMART_WEAR_REORG_THRESHOLD)
{
continue;
}
#endif
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
count = smart_get_count(dev, dev->releasecount, x);
if (count > releasemax)
{
releasemax = count;
collectblock = x;
}
#else
if (dev->releasecount[x] > releasemax)
{
releasemax = dev->releasecount[x];
collectblock = x;
}
#endif
}
#if 0
releasemax = smart_get_count(dev, dev->releasecount, collectblock);
#endif
if (collectblock == 0xffff)
{
/* Need to collect, but no sectors with released blocks! */
ret = -ENOSPC;
goto errout;
}
#ifdef CONFIG_SMART_LOCAL_CHECKFREE
if (smart_checkfree(dev, __LINE__) != OK)
{
fwarn(" ...before collecting block %d\n", collectblock);
}
#endif
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
finfo("Collecting block %d, free=%d released=%d, "
"totalfree=%d, totalrelease=%d\n",
collectblock,
smart_get_count(dev, dev->freecount, collectblock),
smart_get_count(dev, dev->releasecount, collectblock),
dev->freesectors, dev->releasesectors);
#else
finfo("Collecting block %d, free=%d released=%d\n",
collectblock, dev->freecount[collectblock],
dev->releasecount[collectblock]);
#endif
/* Relocate the active data in the collection block */
ret = smart_relocate_block(dev, collectblock);
#ifdef CONFIG_SMART_LOCAL_CHECKFREE
if (smart_checkfree(dev, __LINE__) != OK)
{
fwarn(" ...while collecting block %d\n", collectblock);
}
#endif
if (ret != OK)
{
goto errout;
}
}
}
return OK;
errout:
return ret;
}
/****************************************************************************
* Name: smart_write_wearstatus
*
* Description: Writes the wear leveling status bits to sector zero (and
* possibly others if it doesn't fit) such that is is persisted
* across OS reboots.
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
static int smart_write_wearstatus(struct smart_struct_s *dev)
{
uint16_t sector;
uint16_t remaining;
uint16_t towrite;
struct smart_read_write_s req;
int ret;
uint8_t buffer[8];
uint8_t write_buffer = 0;
sector = 0;
remaining = dev->geo.neraseblocks >> 1;
memset(buffer, 0xff, sizeof(buffer));
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_SMARTFS)
if (dev->blockerases > 0)
{
*((uint32_t *) buffer) = dev->blockerases;
write_buffer = 1;
}
#endif
/* Write the uneven wear count just prior to the wear bits */
if (dev->uneven_wearcount != 0)
{
*((uint32_t *) &buffer[4]) = dev->uneven_wearcount;
write_buffer = 1;
}
/* Test if we need to write either total block erase count or
* uneven wearcount (or both)
*/
if (write_buffer)
{
req.logsector = sector;
req.offset = SMARTFS_FMT_WEAR_POS - 8;
req.count = sizeof(buffer);
req.buffer = buffer;
ret = smart_writesector(dev, (unsigned long) &req);
if (ret != OK)
{
goto errout;
}
}
/* Write all wear level bits to logical sector zero, one, two */
while (remaining)
{
/* Calculate the number of bytes to write to this sector */
towrite = remaining;
if (towrite >
dev->sectorsize - (SMARTFS_FMT_WEAR_POS +
sizeof(struct smart_sect_header_s)))
{
towrite = dev->sectorsize -
(SMARTFS_FMT_WEAR_POS +
sizeof(struct smart_sect_header_s));
}
/* Setup the sector write request (we are our own client) */
req.logsector = sector;
req.offset = SMARTFS_FMT_WEAR_POS;
req.count = towrite;
req.buffer =
&dev->wearstatus[(dev->geo.neraseblocks >> SMART_WEAR_BIT_DIVIDE) -
remaining];
/* Write the sector */
ret = smart_writesector(dev, (unsigned long) &req);
if (ret != OK)
{
goto errout;
}
/* Decrement the remaining count */
remaining -= towrite;
if (remaining)
{
/* Data doesn't fit in a single sector. Use the reserved sectors */
sector++;
if (sector >= SMART_FIRST_DIR_SECTOR)
{
/* Error, wear status bit too large! */
ferr("ERROR: Invalid geometry "
"- wear level status too large\n");
ret = -EINVAL;
goto errout;
}
}
}
/* Now clear the NEEDS_WRITE wear status bit */
dev->wearflags &= ~SMART_WEARFLAGS_WRITE_NEEDED;
ret = OK;
errout:
return ret;
}
#endif
/****************************************************************************
* Name: smart_read_wearstatus
*
* Description: Reads the wear leveling status bits from sector zero (and
* possibly others if it doesn't fit) such that is is persisted
* across OS reboots.
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
static inline int smart_read_wearstatus(FAR struct smart_struct_s *dev)
{
struct smart_read_write_s req;
uint16_t sector;
uint16_t physsector;
uint16_t remaining;
uint16_t toread;
uint8_t buffer[8];
int ret;
/* Prepare to read the total block erases and uneven wearcount values */
sector = 0;
req.logsector = sector;
req.offset = SMARTFS_FMT_WEAR_POS - 8;
req.count = sizeof(buffer);
req.buffer = buffer;
ret = smart_readsector(dev, (unsigned long) &req);
if (ret != sizeof(buffer))
{
goto errout;
}
/* Get the uneven wearcount value */
dev->uneven_wearcount = *((uint32_t *) &buffer[4]);
/* Check for erased state */
#if ( CONFIG_SMARTFS_ERASEDSTATE == 0xff )
if (dev->uneven_wearcount == 0xffffffff)
{
dev->uneven_wearcount = 0;
}
#endif
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_SMARTFS)
/* Get the block erases count */
dev->blockerases = *((uint32_t *) buffer);
#if ( CONFIG_SMARTFS_ERASEDSTATE == 0xff )
if (dev->blockerases == 0xffffffff)
{
dev->blockerases = 0;
}
#endif
#endif
/* Read all wear level bits from the flash */
remaining = dev->geo.neraseblocks >> 1;
while (remaining)
{
/* Calculate number of bytes to read from this sector */
toread = remaining;
if (toread > dev->sectorsize -
(SMARTFS_FMT_WEAR_POS + sizeof(struct smart_sect_header_s)))
{
toread = dev->sectorsize -
(SMARTFS_FMT_WEAR_POS +
sizeof(struct smart_sect_header_s));
}
/* Setup the sector read request (we are our own client) */
req.logsector = sector;
req.offset = SMARTFS_FMT_WEAR_POS;
req.count = toread;
req.buffer =
&dev->wearstatus[(dev->geo.neraseblocks >> SMART_WEAR_BIT_DIVIDE) -
remaining];
/* Validate wear status sector has been allocated */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
physsector = dev->smap[req.logsector];
#else
physsector = smart_cache_lookup(dev, req.logsector);
#endif
if ((sector != 0) && (physsector == 0xffff))
{
/* This logical sector does not exist yet. We must allocate it */
ret = smart_allocsector(dev, sector);
if (ret != sector)
{
ferr("ERROR: Unable to allocate wear level status sector %d\n",
sector);
ret = -EINVAL;
goto errout;
}
}
/* Read the sector */
ret = smart_readsector(dev, (unsigned long) &req);
if (ret != toread)
{
goto errout;
}
/* Decrement the remaining count */
remaining -= toread;
if (remaining)
{
/* Data doesn't fit in a single sector. Use the reserved sectors */
sector++;
if (sector >= SMART_FIRST_DIR_SECTOR)
{
/* Error, wear status bit too large! */
ferr("ERROR: Invalid geometry "
"- wear level status too large\n");
ret = -EINVAL;
goto errout;
}
}
}
/* Now interrogate the status bits */
smart_find_wear_minmax(dev);
#ifdef CONFIG_MTD_SMART_SECTOR_ERASE_DEBUG
/* Set the erase counts equal to the wear levels */
for (sector = 0; sector < dev->geo.neraseblocks; sector++)
{
dev->erasecounts[sector] = smart_get_wear_level(dev, sector);
}
#endif
ret = OK;
errout:
return ret;
}
#endif
/****************************************************************************
* Name: smart_write_alloc_sector
*
* Description: Writes a newly allocated sector's header to the RW buffer
* and updates sector mapping variables. If CRC isn't enabled
* it also writes the header to the device.
*
****************************************************************************/
static int smart_write_alloc_sector(FAR struct smart_struct_s *dev,
uint16_t logical, uint16_t physical)
{
int ret = 1;
uint8_t sectsize;
FAR struct smart_sect_header_s *header;
memset(dev->rwbuffer, CONFIG_SMARTFS_ERASEDSTATE, dev->sectorsize);
header = (FAR struct smart_sect_header_s *) dev->rwbuffer;
*((FAR uint16_t *) header->logicalsector) = logical;
#if SMART_STATUS_VERSION == 1
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
header->seq = 0;
#else
*((FAR uint16_t *) &header->seq) = 0;
#endif /* CONFIG_MTD_SMART_ENABLE_CRC */
#else
header->seq = 0;
#endif
/* Calculate the 3-bit logical sector size in bits 2-4:
* 000b - 256 bytes
* 001b - 512 bytes
* 010b - 1024 bytes
* 100b - 2048 bytes
* 011b - 4096 bytes
* 101b - 8192 bytes
* 110b - 16384 bytes
* 110b - 32768 bytes
*/
sectsize = dev->sectorsize < 4096 ? (dev->sectorsize >> 9) :
dev->sectorsize == 4096 ? 3 : 5 + (dev->sectorsize >> 14);
sectsize <<= 2;
#if CONFIG_SMARTFS_ERASEDSTATE == 0xff
header->status = ~(SMART_STATUS_COMMITTED | SMART_STATUS_SIZEBITS |
SMART_STATUS_VERBITS) | SMART_STATUS_VERSION | sectsize;
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
header->status &= ~SMART_STATUS_CRC;
#endif /* CONFIG_MTD_SMART_ENABLE_CRC */
#else
header->status = SMART_STATUS_COMMITTED | SMART_STATUS_VERSION | sectsize;
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
header->status |= SMART_STATUS_CRC;
#endif /* CONFIG_MTD_SMART_ENABLE_CRC */
#endif
/* Write the header to the physical sector location */
#ifndef CONFIG_MTD_SMART_ENABLE_CRC
finfo("Write MTD block %d\n", physical * dev->mtdblkspersector);
ret = MTD_BWRITE(dev->mtd, physical * dev->mtdblkspersector, 1,
(FAR uint8_t *) dev->rwbuffer);
if (ret != 1)
{
/* The block is not empty!! What to do? */
ferr("ERROR: Write block %d failed: %d.\n", physical *
dev->mtdblkspersector, ret);
/* Unlock the mutex if we add one */
return -EIO;
}
#endif /* CONFIG_MTD_SMART_ENABLE_CRC */
return ret;
}
/****************************************************************************
* Name: smart_validate_crc
*
* Description: Validates the CRC data in the sector's header against the
* data in the sector. Assumes the entire sector has been
* read into the RW buffer already.
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
static int smart_validate_crc(FAR struct smart_struct_s *dev)
{
crc_t crc;
FAR struct smart_sect_header_s *header;
/* Calculate CRC on data region of the sector */
crc = smart_calc_sector_crc(dev);
header = (FAR struct smart_sect_header_s *) dev->rwbuffer;
#ifdef CONFIG_SMART_CRC_8
/* Test 8-bit CRC */
if (crc != header->crc8)
{
return -EIO;
}
#elif defined(CONFIG_SMART_CRC_16)
/* Test 16-bit CRC */
if (crc != *((uint16_t *) header->crc16))
{
return -EIO;
}
#elif defined(CONFIG_SMART_CRC_32)
if (crc != *((uint32_t *) header->crc32))
{
return -EIO;
}
#endif
/* CRC checkout out okay */
return OK;
}
#endif
/****************************************************************************
* 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.
*
****************************************************************************/
static int smart_writesector(FAR struct smart_struct_s *dev,
unsigned long arg)
{
int ret;
bool needsrelocate = FALSE;
uint32_t mtdblock;
uint16_t physsector;
uint16_t oldphyssector;
uint16_t block;
FAR struct smart_read_write_s *req;
FAR struct smart_sect_header_s *header;
size_t offset;
uint8_t byte;
#if defined(CONFIG_MTD_SMART_WEAR_LEVEL) || !defined(CONFIG_MTD_SMART_ENABLE_CRC)
uint16_t x;
#endif
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
FAR struct smart_allocsector_s *allocsector;
#endif
finfo("Entry\n");
req = (FAR 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)
{
ferr("ERROR: Logical sector %d too large\n", req->logsector);
ret = -EINVAL;
goto errout;
}
header = (FAR struct smart_sect_header_s *)dev->rwbuffer;
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
/* Test if an adjustment to the wear levels is needed */
if (dev->minwearlevel >= SMART_WEAR_MIN_LEVEL ||
(dev->minwearlevel > 0 &&
dev->maxwearlevel >= SMART_WEAR_REORG_THRESHOLD))
{
/* Subtract dev->minwearlevel from all wear levels */
offset = dev->minwearlevel;
finfo("Reducing wear level bits by %zu\n", offset);
for (x = 0; x < dev->geo.neraseblocks; x++)
{
smart_set_wear_level(dev, x,
smart_get_wear_level(dev, x) - offset);
}
dev->minwearlevel -= offset;
dev->maxwearlevel -= offset;
/* Now write the new wear bits to the flash */
dev->wearflags &= ~SMART_WEARFLAGS_FORCE_REORG;
dev->wearflags |= SMART_WEARFLAGS_WRITE_NEEDED;
}
#endif
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
physsector = dev->smap[req->logsector];
#else
physsector = smart_cache_lookup(dev, req->logsector);
#endif
if (physsector == 0xffff)
{
ferr("ERROR: 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, (FAR uint8_t *)
dev->rwbuffer);
if (ret != dev->mtdblkspersector)
{
ferr("ERROR: Error reading phys sector %d\n", physsector);
ret = -EIO;
goto errout;
}
/* Test if we need to relocate the sector to perform the write */
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
allocsector = dev->allocsector;
while (allocsector)
{
/* Test if the requested logical sector is a temp alloc */
if (allocsector->logical == req->logsector)
{
break;
}
allocsector = allocsector->next;
}
/* When CRC is enabled, then we always have to relocate the sector if
* it is not a temporary alloc (i.e. initial alloc before the very first
* write operation).
*/
if (!allocsector)
{
needsrelocate = TRUE;
}
#else
/* When CRC is not enabled, we may be able to simply add the new data to
* the sector if it doesn't conflict with existing data on the device.
* Test if there is a conflict in the data.
*/
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
}
#endif /* CONFIG_MTD_SMART_ENABLE_CRC */
/* If we are not using CRC and on a device that supports re-writing
* bits from 1 to 0 without needing a block erase, such as NOR
* FLASH, then we can simply update the data in place and don't need
* to relocate the sector. Test if we need to relocate or not.
*/
if (needsrelocate)
{
/* Find a new physical sector to save data to */
oldphyssector = physsector;
physsector = smart_findfreephyssector(dev, FALSE);
if (physsector == 0xffff)
{
ferr("ERROR: Error relocating sector %d\n", req->logsector);
ret = -EIO;
goto errout;
}
/* Update the sequence number to indicate the sector was moved */
#if SMART_STATUS_VERSION == 1
if ((header->status & SMART_STATUS_CRC) !=
(CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_CRC))
{
#endif
header->seq++;
if (header->seq == 0xff)
{
header->seq = 0;
}
#if SMART_STATUS_VERSION == 1
}
else
{
(*((FAR uint16_t *) &header->seq))++;
if (*((FAR uint16_t *) &header->seq) == 0xffff)
*((FAR uint16_t *) &header->seq) = 1;
}
#else
header->seq++;
#endif
#if CONFIG_SMARTFS_ERASEDSTATE == 0xff
header->status |= SMART_STATUS_COMMITTED;
#else
header->status &= SMART_STATUS_COMMITTED;
#endif
}
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
/* When CRC is enabled and we have a temp alloc, then fill in the RW buffer
* with the header information prior to copying the write data to the buf.
*/
if (allocsector)
{
smart_write_alloc_sector(dev, allocsector->logical,
allocsector->physical);
/* Remove allocsector from the list and free the memory */
if (dev->allocsector == allocsector)
{
/* We are the head item. Remove ourselves as head */
dev->allocsector = allocsector->next;
}
else
{
FAR struct smart_allocsector_s *prev;
/* Start at head and find our entry */
prev = dev->allocsector;
while (prev && prev->next != allocsector)
{
/* Scan the list until we find this entry */
prev = prev->next;
}
if (prev)
{
/* Remove from the list */
prev->next = allocsector->next;
}
}
/* Now free the memory */
kmm_free(allocsector);
}
/* Now copy the data to the sector buffer. */
memcpy(&dev->rwbuffer[sizeof(struct smart_sect_header_s) + req->offset],
req->buffer, req->count);
/* Commit the sector ahead of time. The CRC will protect us */
#if CONFIG_SMARTFS_ERASEDSTATE == 0xff
header->status &= ~(SMART_STATUS_COMMITTED | SMART_STATUS_CRC);
#else
header->status |= SMART_STATUS_COMMITTED | SMART_STATUS_CRC;
#endif
/* Now calculate the CRC value for the sector */
#ifdef CONFIG_SMART_CRC_8
header->crc8 = smart_calc_sector_crc(dev);
#elif defined(CONFIG_SMART_CRC_16)
*((uint16_t *) header->crc16) = smart_calc_sector_crc(dev);
#elif defined(CONFIG_SMART_CRC_32)
*((uint32_t *) header->crc32) = smart_calc_sector_crc(dev);
#endif
#else /* CONFIG_MTD_SMART_ENABLE_CRC */
/* Now copy the data to the sector buffer. */
memcpy(&dev->rwbuffer[sizeof(struct smart_sect_header_s) + req->offset],
req->buffer, req->count);
#endif /* CONFIG_MTD_SMART_ENABLE_CRC */
/* 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, (FAR uint8_t *) dev->rwbuffer);
if (ret != dev->mtdblkspersector)
{
ferr("ERROR: Error writing to physical sector %d\n", physsector);
ret = -EIO;
goto errout;
}
/* Commit the new physical sector */
#ifndef CONFIG_MTD_SMART_ENABLE_CRC
#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)
{
finfo("Error committing physical sector %d\n", physsector);
ret = -EIO;
goto errout;
}
#endif
/* Release the old physical sector */
#if CONFIG_SMARTFS_ERASEDSTATE == 0xff
byte = header->status & ~(SMART_STATUS_RELEASED |
SMART_STATUS_COMMITTED);
#else
byte = header->status | SMART_STATUS_RELEASED |
SMART_STATUS_COMMITTED;
#endif
offset = mtdblock * dev->geo.blocksize +
offsetof(struct smart_sect_header_s, status);
ret = smart_bytewrite(dev, offset, 1, &byte);
/* Update releasecount for the released sector and freecount for the
* newly allocated physical sector.
*/
block = oldphyssector / dev->sectorsperblk;
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
smart_add_count(dev, dev->releasecount, block, 1);
smart_add_count(dev, dev->freecount, physsector / dev->sectorsperblk,
-1);
#else
dev->releasecount[block]++;
dev->freecount[physsector / dev->sectorsperblk]--;
#endif
dev->freesectors--;
dev->releasesectors++;
#ifdef CONFIG_SMART_LOCAL_CHECKFREE
/* Perform debug free count checking enabled */
smart_checkfree(dev, __LINE__);
#endif
/* Update the sector map */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
dev->smap[req->logsector] = physsector;
#else
smart_update_cache(dev, req->logsector, physsector);
#endif
/* Test if releasing the sector created an empty erase block */
smart_erase_block_if_empty(dev, block, FALSE);
/* Since we performed a relocation, do garbage collection to
* ensure we don't fill up our flash with released blocks.
*/
smart_garbagecollect(dev);
}
else /* needsrelocate */
{
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
/* Write the entire sector to FLASH when CRC enabled */
ret = MTD_BWRITE(dev->mtd, physsector * dev->mtdblkspersector,
dev->mtdblkspersector, (FAR uint8_t *) dev->rwbuffer);
if (ret != dev->mtdblkspersector)
{
ferr("ERROR: Error writing to physical sector %d\n", physsector);
ret = -EIO;
goto errout;
}
/* Read the sector back and validate the CRC. */
ret = MTD_BREAD(dev->mtd, physsector * dev->mtdblkspersector,
dev->mtdblkspersector, (FAR uint8_t *) dev->rwbuffer);
if (ret == dev->mtdblkspersector)
{
/* Validate the CRC of the read-back data */
ret = smart_validate_crc(dev);
}
if (ret != OK)
{
/* TODO: Mark this as a bad block! */
ferr("ERROR: Error validating physical sector %d\n", physsector);
ret = -EIO;
goto errout;
}
#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);
#endif
}
ret = OK;
errout:
return ret;
}
/****************************************************************************
* Name: smart_readsector
*
* Description: Reads data from the specified logical sector. The sector
* should have already been allocated prior to the read.
*
****************************************************************************/
static int smart_readsector(FAR struct smart_struct_s *dev,
unsigned long arg)
{
int ret;
uint16_t physsector;
FAR struct smart_read_write_s *req;
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
#if SMART_STATUS_VERSION == 1
FAR struct smart_sect_header_s *header;
#endif
#else
uint32_t readaddr;
struct smart_sect_header_s header;
#endif
finfo("Entry\n");
req = (FAR struct smart_read_write_s *) arg;
DEBUGASSERT(req->offset < dev->sectorsize);
DEBUGASSERT(req->offset + req->count + sizeof(struct smart_sect_header_s)
<= dev->sectorsize);
/* Ensure the logical sector has been allocated */
if (req->logsector >= dev->totalsectors)
{
ferr("ERROR: Logical sector %d too large\n", req->logsector);
return -EINVAL;
}
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
physsector = dev->smap[req->logsector];
#else
physsector = smart_cache_lookup(dev, req->logsector);
#endif
if (physsector == 0xffff)
{
ferr("ERROR: Logical sector %d not allocated\n", req->logsector);
return -EINVAL;
}
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
/* When CRC is enabled, we read the entire sector into RAM so we can
* validate the CRC.
*/
ret = MTD_BREAD(dev->mtd, physsector * dev->mtdblkspersector,
dev->mtdblkspersector, (FAR uint8_t *) dev->rwbuffer);
if (ret != dev->mtdblkspersector)
{
/* TODO: Mark the block bad */
ferr("ERROR: Error reading phys sector %d\n", physsector);
return -EIO;
}
#if SMART_STATUS_VERSION == 1
/* Test if this sector has CRC enabled or not */
header = (FAR struct smart_sect_header_s *) dev->rwbuffer;
if ((header->status & SMART_STATUS_CRC) ==
(CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_CRC))
{
/* Format VERSION 1 supports either no CRC or 8-bit CRC. Looks like
* CRC not enabled for this sector, so skip the CRC test.
*/
}
else
#endif
{
/* Validate the read CRC against the calculated sector CRC */
ret = smart_validate_crc(dev);
if (ret != OK)
{
/* TODO: Mark the block bad */
ferr("ERROR: Error validating sector %d CRC during read\n",
physsector);
return -EIO;
}
}
/* Copy data to the output buffer */
memmove((FAR char *) req->buffer, &dev->rwbuffer[req->offset +
sizeof(struct smart_sect_header_s)], req->count);
ret = req->count;
#else /* CONFIG_MTD_SMART_ENABLE_CRC */
/* 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),
(FAR uint8_t *)&header);
if (ret != sizeof(struct smart_sect_header_s))
{
ferr("ERROR: Error reading sector %d header\n", physsector);
return -EIO;
}
/* Do a sanity check on the header data */
if (((*(FAR 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? */
ferr("ERROR: Error in logical sector %d header, phys=%d\n",
req->logsector, physsector);
return -EIO;
}
/* 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, (FAR uint8_t *)
req->buffer);
if (ret != req->count)
{
ferr("ERROR: Error reading phys sector %d\n", physsector);
return -EIO;
}
#endif
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.
*
****************************************************************************/
static inline int smart_allocsector(FAR struct smart_struct_s *dev,
unsigned long requested)
{
uint16_t logsector = 0xffff; /* Logical sector number selected */
uint16_t physicalsector; /* The selected physical sector */
#ifndef CONFIG_MTD_SMART_ENABLE_CRC
int ret;
#endif
int x;
/* 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.
*/
if (dev->freesectors <= (dev->sectorsperblk << 0) + 4)
{
/* Do a garbage collect and then test freesectors again */
if (dev->releasesectors + dev->freesectors > dev->sectorsperblk + 4)
{
for (x = 0; x < dev->availsectperblk; x++)
{
smart_garbagecollect(dev);
if (dev->freesectors > dev->availsectperblk + 4)
{
break;
}
}
if (dev->freesectors <= (dev->availsectperblk << 0) + 4)
{
/* No space left!! */
return -ENOSPC;
}
}
else
{
/* 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 > 0) && (requested < dev->totalsectors))
{
/* Validate the sector is not already allocated */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
if (dev->smap[requested] == (uint16_t) -1)
#else
if (!(dev->sbitmap[requested >> 3] & (1 << (requested & 0x07))))
#endif
{
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
FAR struct smart_allocsector_s *allocsect;
/* Ensure this logical sector doesn't have a temporary alloc */
allocsect = dev->allocsector;
while (allocsect)
{
if (allocsect->logical == requested)
{
break;
}
allocsect = allocsect->next;
}
if (allocsect != NULL)
{
}
else
#endif
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++)
{
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
if (dev->smap[x] == (uint16_t) -1)
#else
if (!(dev->sbitmap[x >> 3] & (1 << (x & 0x07))))
#endif
{
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
FAR struct smart_allocsector_s *allocsect;
/* Ensure this logical sector doesn't have a temporary alloc
* when CRC is enabled. With CRC enabled, when a sector is
* allocated, we don't actually update the FLASH until the
* very end when we have all data so the CRC can be calculated.
* Instead, we keep an in-memory linked list of allocated
* sectors until the write sector occurs.
*/
allocsect = dev->allocsector;
while (allocsect)
{
if (allocsect->logical == x)
{
break;
}
allocsect = allocsect->next;
}
if (allocsect != NULL)
{
/* This logical sector has an in-memory temp alloc */
continue;
}
#endif
/* 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?
*/
ferr("ERROR: 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 perform 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, FALSE);
finfo("Alloc: log=%d, phys=%d, erase block=%d, free=%d, released=%d\n",
logsector, physicalsector, physicalsector /
dev->sectorsperblk, dev->freesectors, dev->releasesectors);
if (physicalsector == 0xffff)
{
return -ENOSPC;
}
#ifdef CONFIG_MTD_SMART_ENABLE_CRC
/* When CRC is enabled, we don't write the header to the device until
* the data is written via writesector. Just add the allocation to
* our temporary allocsector list and we'll pick it up later.
*/
{
FAR struct smart_allocsector_s *allocsect =
(FAR struct smart_allocsector_s *)
kmm_malloc(sizeof(struct smart_allocsector_s));
if (allocsect == NULL)
{
ferr("ERROR: Out of memory allocting sector\n");
return -ENOMEM;
}
/* Fill in the struct and add to the list. We are protected by the
* smartfs layer's mutex, so no locking is required.
*/
allocsect->logical = logsector;
allocsect->physical = physicalsector;
allocsect->next = dev->allocsector;
dev->allocsector = allocsect;
}
#else /* CONFIG_MTD_SMART_ENABLE_CRC */
/* Write the logical sector to the flash. We will fill it in with data
* later.
*/
ret = smart_write_alloc_sector(dev, logsector, physicalsector);
if (ret != 1)
{
/* Error writing sector, return error */
return ret;
}
#endif /* CONFIG_MTD_SMART_ENABLE_CRC */
/* Map the sector and update the free sector counts */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
dev->smap[logsector] = physicalsector;
#else
dev->sbitmap[logsector >> 3] |= (1 << (logsector & 0x07));
smart_add_sector_to_cache(dev, logsector, physicalsector, __LINE__);
#endif
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
smart_add_count(dev, dev->freecount,
physicalsector / dev->sectorsperblk, -1);
#else
dev->freecount[physicalsector / dev->sectorsperblk]--;
#endif
dev->freesectors--;
/* Return the logical sector number */
return logsector;
}
/****************************************************************************
* 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.
*
****************************************************************************/
static inline int smart_freesector(FAR 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 */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
if (dev->smap[logicalsector] == (uint16_t) -1)
#else
if (!(dev->sbitmap[logicalsector >> 3] &
(1 << (logicalsector & 0x07))))
#endif
{
ferr("ERROR: Invalid release - sector %ld not allocated\n",
logicalsector);
ret = -EINVAL;
goto errout;
}
}
/* Okay to release the sector. Read the sector header info */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
physsector = dev->smap[logicalsector];
#else
physsector = smart_cache_lookup(dev, logicalsector);
#endif
readaddr = physsector * dev->mtdblkspersector * dev->geo.blocksize;
ret = MTD_READ(dev->mtd, readaddr, sizeof(struct smart_sect_header_s),
(FAR uint8_t *) &header);
if (ret != sizeof(struct smart_sect_header_s))
{
goto errout;
}
/* Do a sanity check on the logical sector number */
if (*((FAR uint16_t *) header.logicalsector) != (uint16_t) logicalsector)
{
/* Hmmm... something is wrong. This should always match! Bug in our
* code?
*/
ferr("ERROR: Sector %ld 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)
{
ferr("ERROR: Error updating physical sector %d status\n", physsector);
goto errout;
}
/* Update the erase block's release count */
dev->releasesectors++;
block = physsector / dev->sectorsperblk;
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
smart_add_count(dev, dev->releasecount, block, 1);
#else
dev->releasecount[block]++;
#endif
/* Unmap this logical sector */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
dev->smap[logicalsector] = (uint16_t) -1;
#else
dev->sbitmap[logicalsector >> 3] &= ~(1 << (logicalsector & 0x07));
smart_update_cache(dev, logicalsector, 0xffff);
#endif
/* If this block has only released blocks, then erase it */
smart_erase_block_if_empty(dev, block, FALSE);
ret = OK;
errout:
return ret;
}
/****************************************************************************
* Name: smart_ioctl
*
* Description: Return device geometry
*
****************************************************************************/
static int smart_ioctl(FAR struct inode *inode, int cmd, unsigned long arg)
{
FAR struct smart_struct_s *dev;
int ret;
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_SMARTFS)
FAR struct mtd_smart_procfs_data_s *procfs_data;
FAR struct mtd_smart_debug_data_s *debug_data;
#endif
finfo("Entry\n");
DEBUGASSERT(inode && inode->i_private);
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
dev = ((FAR struct smart_multiroot_device_s *)inode->i_private)->dev;
#else
dev = (FAR 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_GETFORMAT:
/* Return the format information for the device */
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
ret = smart_getformat(dev, (FAR struct smart_format_s *) arg,
((FAR struct smart_multiroot_device_s *)
inode->i_private)->rootdirnum);
#else
ret = smart_getformat(dev, (FAR 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;
case BIOC_LLFORMAT:
/* Perform a low-level format on the flash */
ret = smart_llformat(dev, arg);
goto ok_out;
case BIOC_ALLOCSECT:
/* Ensure the FS is not trying to allocate a reserved sector */
if (arg < 3)
{
arg = (unsigned long) -1;
}
/* 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);
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
if (dev->wearflags & SMART_WEARFLAGS_WRITE_NEEDED)
{
/* Write new wear status bits to the device */
smart_write_wearstatus(dev);
}
#endif
goto ok_out;
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_SMARTFS)
case BIOC_GETPROCFSD:
/* Get ProcFS data */
procfs_data = (FAR struct mtd_smart_procfs_data_s *)arg;
procfs_data->totalsectors = dev->totalsectors;
procfs_data->sectorsize = dev->sectorsize;
procfs_data->freesectors = dev->freesectors;
procfs_data->releasesectors = dev->releasesectors;
procfs_data->namelen = dev->namesize;
procfs_data->formatversion = dev->formatversion;
procfs_data->unusedsectors = dev->unusedsectors;
procfs_data->blockerases = dev->blockerases;
procfs_data->sectorsperblk = dev->sectorsperblk;
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
procfs_data->formatsector = dev->smap[0];
procfs_data->dirsector = dev->smap[3];
#else
procfs_data->formatsector = smart_cache_lookup(dev, 0);
procfs_data->dirsector = smart_cache_lookup(dev, 3);
#endif
#ifdef CONFIG_MTD_SMART_SECTOR_ERASE_DEBUG
procfs_data->neraseblocks = dev->geo.neraseblocks;
procfs_data->erasecounts = dev->erasecounts;
#endif
#ifdef CONFIG_MTD_SMART_ALLOC_DEBUG
procfs_data->allocs = dev->alloc;
procfs_data->alloccount = SMART_MAX_ALLOCS;
#endif
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
procfs_data->uneven_wearcount = dev->uneven_wearcount;
#endif
ret = OK;
goto ok_out;
#endif
case BIOC_DEBUGCMD:
#if defined(CONFIG_FS_PROCFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_SMARTFS)
debug_data = (FAR struct mtd_smart_debug_data_s *) arg;
switch (debug_data->debugcmd)
{
case SMART_DEBUG_CMD_SET_DEBUG_LEVEL:
dev->debuglevel = debug_data->debugdata;
finfo("Debug level set to %d\n", dev->debuglevel);
ret = OK;
goto ok_out;
}
#endif
break;
}
/* 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)
{
ferr("ERROR: MTD ioctl(%04x) failed: %d\n", cmd, ret);
}
ok_out:
return ret;
}
#ifdef CONFIG_MTD_SMART_FSCK
/****************************************************************************
* Name: smart_fsck_crc
*
* Description: Validate CRC to check smartfs filesystem
*
****************************************************************************/
#ifdef CONFIG_MTD_SMART_FSCK_ENABLE_CRC
static int smart_fsck_crc(FAR struct smart_struct_s *dev,
uint16_t physsector)
{
int ret;
ret = MTD_BREAD(dev->mtd, physsector * dev->mtdblkspersector,
dev->mtdblkspersector, (FAR uint8_t *)dev->rwbuffer);
if (ret != dev->mtdblkspersector)
{
ferr("ERROR: Error reading phys sector %d\n", physsector);
return ret;
}
ret = smart_validate_crc(dev);
if (ret != OK)
{
ferr("ERROR: Error validating sector %d CRC\n", physsector);
return ret;
}
return ret;
}
#endif
/****************************************************************************
* Name: smart_fsck_file
*
* Description: fsck for file entry
*
****************************************************************************/
static int smart_fsck_file(FAR struct smart_struct_s *dev,
FAR uint8_t *checkmap, uint16_t logsector)
{
int ret = OK;
ssize_t size;
uint32_t readaddress;
FAR struct smart_sect_header_s *header;
FAR struct smart_chain_header_s *chain;
FAR uint8_t *usedmap;
size_t mapsize;
uint16_t physsector;
int i;
if (logsector >= dev->totalsectors)
{
ret = -EINVAL;
return ret;
}
/* Allocate a bitmap table for sectors this file is using */
mapsize = (dev->totalsectors + 7) / 8;
usedmap = (FAR uint8_t *)kmm_zalloc(mapsize);
if (!usedmap)
{
ferr("ERROR: Out of memory used map\n");
return OK;
}
do
{
/* Read the header for file sector */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
physsector = dev->smap[logsector];
#else
physsector = smart_cache_lookup(dev, logsector);
#endif
if (physsector >= dev->totalsectors)
{
ret = -ENXIO;
ferr("ERROR: Invalid phys sector %d\n", physsector);
break;
}
#ifdef CONFIG_MTD_SMART_FSCK_ENABLE_CRC
if (smart_fsck_crc(dev, physsector) != OK)
{
ret = -ENOENT;
ferr("ERROR: CRC phys sector %d\n", physsector);
break;
}
#endif
readaddress = physsector * dev->mtdblkspersector * dev->geo.blocksize;
size = MTD_READ(dev->mtd, readaddress,
sizeof(struct smart_sect_header_s) +
sizeof(struct smart_chain_header_s),
(uint8_t *)dev->rwbuffer);
if (size != (sizeof(struct smart_sect_header_s) +
sizeof(struct smart_chain_header_s)))
{
ret = -EIO;
ferr("Error reading phys sector %d\n", physsector);
break;
}
header = (struct smart_sect_header_s *) & dev->rwbuffer[0];
chain = (struct smart_chain_header_s *) &
dev->rwbuffer[sizeof(struct smart_sect_header_s)];
/* Test if the sector has live data (not free or not released) */
if (((header->status & SMART_STATUS_COMMITTED) ==
(CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED)) ||
((header->status & SMART_STATUS_RELEASED) !=
(CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_RELEASED)))
{
ret = -ENOENT;
ferr("ERROR: status(%02x) phys sector %d\n",
header->status, physsector);
break;
}
SET_BITMAP(usedmap, logsector);
/* next logical sector */
logsector = SMARTFS_NEXTSECTOR(chain);
}
while (logsector != 0xffff);
if (ret == OK)
{
/* These sectors in use are not removed */
for (i = 0; i < mapsize; i++)
{
checkmap[i] &= ~usedmap[i];
}
}
else
{
/* This file has any corruption, these sectors will be removed */
for (i = 0; i < mapsize; i++)
{
checkmap[i] |= usedmap[i];
}
}
kmm_free(usedmap);
return ret;
}
/****************************************************************************
* Name: smart_fsck_directory
*
* Description: fsck for directory entry
*
****************************************************************************/
static int smart_fsck_directory(FAR struct smart_struct_s *dev,
FAR uint8_t *checkmap, uint16_t logsector)
{
int ret = OK;
int relocate = 0;
ssize_t size;
FAR uint8_t *rwbuffer;
FAR struct smart_sect_header_s *header;
FAR struct smart_chain_header_s *chain;
FAR struct smart_entry_header_s *entry;
uint16_t entrysector;
uint16_t physsector;
uint16_t nextsector;
uint16_t newsector;
int entrysize;
FAR uint8_t *bottom;
FAR uint8_t *cur;
#ifdef CONFIG_DEBUG_FS_INFO
char entryname[dev->namesize + 1];
#endif
if ((logsector < SMART_FIRST_DIR_SECTOR) ||
(logsector >= dev->totalsectors))
{
ret = -EINVAL;
ferr("ERROR: Invalid log sector %d\n", logsector);
return ret;
}
/* Allocate sector buffer for Directory entry */
rwbuffer = (uint8_t *)kmm_malloc(dev->sectorsize);
if (!rwbuffer)
{
ferr("ERROR: Out of memory sector buffer\n");
return OK;
}
/* Read the Directory entry sector */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
physsector = dev->smap[logsector];
#else
physsector = smart_cache_lookup(dev, logsector);
#endif
if (physsector >= dev->totalsectors)
{
ret = -ENXIO;
ferr("ERROR: Invalid phys sector %d\n", physsector);
goto errout;
}
size = MTD_BREAD(dev->mtd, physsector * dev->mtdblkspersector,
dev->mtdblkspersector, rwbuffer);
if (size != dev->mtdblkspersector)
{
ret = -EIO;
ferr("ERROR: reading phys sector %d\n", physsector);
goto errout;
}
header = (struct smart_sect_header_s *) & rwbuffer[0];
chain = (struct smart_chain_header_s *) &
rwbuffer[sizeof(struct smart_sect_header_s)];
entry = (struct smart_entry_header_s *) &
rwbuffer[sizeof(struct smart_sect_header_s) +
sizeof(struct smart_chain_header_s)];
#ifdef CONFIG_MTD_SMART_FSCK_ENABLE_CRC
/* Check CRC */
if (smart_fsck_crc(dev, physsector) != OK)
{
ret = -ENOENT;
ferr("ERROR: CRC phys sector %d\n", physsector);
goto errout;
}
#endif
/* Test if the sector has live data (not free or not released) */
if (((header->status & SMART_STATUS_COMMITTED) ==
(CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED)) ||
((header->status & SMART_STATUS_RELEASED) !=
(CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_RELEASED)))
{
ret = -ENOENT;
ferr("ERROR: status(%02x) phys sector %d\n",
header->status, physsector);
goto errout;
}
/* Check next sector recursively */
nextsector = SMARTFS_NEXTSECTOR(chain);
if (nextsector != 0xffff)
{
finfo("Check next log sector %d\n", nextsector);
ret = smart_fsck_directory(dev, checkmap, nextsector);
if (ret != OK)
{
/* Invalidate the next sector */
ferr("Invalidate next log sector %d\n", nextsector);
SMARTFS_SET_NEXTSECTOR(chain, 0xffff);
/* Set flag to relocate later */
relocate = 1;
}
}
#define SMARTFS_DIRENT_EMPTY 0x8000 /* Set to non-erase state when entry used */
#define SMARTFS_DIRENT_ACTIVE 0x4000 /* Set to erase state when entry is active */
#define SMARTFS_DIRENT_TYPE 0x2000 /* Indicates the type of entry (file/dir) */
#define SMARTFS_DIRENT_DELETING 0x1000 /* Directory entry is being deleted */
#define SMARTFS_DIRENT_RESERVED 0x0E00 /* Reserved bits */
/* Check file or directory under this directory entry */
entrysize = sizeof(struct smart_entry_header_s) + dev->namesize;
bottom = rwbuffer + dev->sectorsize;
cur = &rwbuffer[sizeof(struct smart_sect_header_s) +
sizeof(struct smart_chain_header_s)];
while ((cur + entrysize) <= bottom)
{
ret = OK;
entry = (struct smart_entry_header_s *)cur;
if (entry->flags == 0xffff)
{
/* Test if the empty entry is exist or not? */
break;
}
#ifdef CONFIG_DEBUG_FS_INFO
strlcpy(entryname,
(const char *) (cur + sizeof(struct smart_entry_header_s)),
sizeof(entryname));
finfo("Check entry (name=%s flags=%02x logsector=%02x)\n",
entryname, entry->flags, entry->firstsector);
#endif
if (entry->flags & SMARTFS_DIRENT_ACTIVE)
{
entrysector = entry->firstsector;
if (entry->flags & SMARTFS_DIRENT_TYPE)
{
/* This entry is for directory */
ret = smart_fsck_directory(dev, checkmap, entrysector);
}
else
{
/* This entry is for file */
ret = smart_fsck_file(dev, checkmap, entrysector);
}
}
if (ret != OK)
{
#ifdef CONFIG_DEBUG_FS_INFO
finfo("Remove entry (name=%s flags=%02x)\n",
entryname, entry->flags);
#endif
if ((cur + (2 * entrysize)) <= bottom)
{
/* Truncate the current entry and overwrite with next entries */
memmove(cur, cur + entrysize, bottom - (cur + entrysize));
memset(bottom - entrysize, CONFIG_SMARTFS_ERASEDSTATE,
entrysize);
}
else
{
/* Only erase the current entry if next entry does not
* exist
*/
memset(cur, CONFIG_SMARTFS_ERASEDSTATE, entrysize);
cur += entrysize;
}
relocate = 1;
}
else
{
cur += entrysize;
}
}
/* Relocate sector */
if (relocate)
{
newsector = smart_findfreephyssector(dev, FALSE);
if (newsector == 0xffff)
{
ret = -ENOSPC;
ferr("Can't find a free sector for relocation\n");
goto errout;
}
memcpy(dev->rwbuffer, rwbuffer, dev->sectorsize);
ret = smart_relocate_sector(dev, physsector, newsector);
if (ret < 0)
{
ret = -EIO;
ferr("Can't relocate\n");
goto errout;
}
/* Update the variables */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
dev->smap[*((FAR uint16_t *)header->logicalsector)] = newsector;
#else
smart_update_cache(dev, *((FAR uint16_t *)header->logicalsector),
newsector);
#endif
#ifdef CONFIG_MTD_SMART_PACK_COUNTS
smart_add_count(dev, dev->freecount,
newsector / dev->sectorsperblk, -1);
#else
dev->freecount[newsector / dev->sectorsperblk]--;
#endif
}
kmm_free(rwbuffer);
CLR_BITMAP(checkmap, logsector);
return OK;
errout:
kmm_free(rwbuffer);
SET_BITMAP(checkmap, logsector);
return ret;
}
/****************************************************************************
* Name: smart_fsck
*
* Description: Check and repair the file system
*
****************************************************************************/
static int smart_fsck(FAR struct smart_struct_s *dev)
{
uint16_t logsector;
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
uint16_t physsector;
#endif
FAR uint8_t *checkmap;
size_t mapsize;
uint8_t rootdirentries;
int x;
finfo("Entry\n");
/* Allocate a bitmap table for filesystem check */
mapsize = (dev->totalsectors + 7) / 8;
checkmap = (FAR uint8_t *)kmm_zalloc(mapsize);
if (!checkmap)
{
ferr("ERROR: Out of memory fsck map\n");
return -ENOMEM;
}
/* Set all of the sectors have live data into the check bitmap */
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
for (logsector = 0; logsector < dev->totalsectors; logsector++)
{
physsector = dev->smap[logsector];
if (physsector < dev->totalsectors)
{
SET_BITMAP(checkmap, logsector);
}
}
#else
memcpy(checkmap, dev->sbitmap, mapsize);
#endif
/* Check if the sector can be available from root directories */
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
rootdirentries = dev->rootdirentries;
#else
rootdirentries = 1;
#endif
for (x = 0; x < rootdirentries; x++)
{
smart_fsck_directory(dev, checkmap, SMART_FIRST_DIR_SECTOR + x);
}
/* Release the invalid sector except for format or directory entry sector */
for (logsector = SMART_FIRST_ALLOC_SECTOR;
logsector < dev->totalsectors; logsector++)
{
if (ISSET_BITMAP(checkmap, logsector))
{
smart_freesector(dev, logsector);
}
}
/* Free the bitmap table for filesystem check */
kmm_free(checkmap);
return OK;
}
#endif /* CONFIG_MTD_SMART_FSCK */
/****************************************************************************
* 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,
FAR const char *partname)
{
FAR struct smart_struct_s *dev;
int ret = -ENOMEM;
uint32_t totalsectors;
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
FAR struct smart_multiroot_device_s *rootdirdev = NULL;
#endif
/* Sanity check */
#ifdef CONFIG_DEBUG_FEATURES
if (minor < 0 || minor > 255 || !mtd)
{
return -EINVAL;
}
#endif
/* Allocate a SMART device structure */
dev = (FAR struct smart_struct_s *)
smart_zalloc(NULL, sizeof(struct smart_struct_s), "Dev struct");
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)
{
ferr("ERROR: MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret);
goto errout;
}
/* Set the sector size to the default for now */
dev->sectorsize = 0;
ret = smart_setsectorsize(dev, CONFIG_MTD_SMART_SECTOR_SIZE);
if (ret != OK)
{
goto errout;
}
/* Calculate the totalsectors on this device and validate */
totalsectors = dev->neraseblocks * dev->sectorsperblk;
if (totalsectors > 65536)
{
ferr("ERROR: SMART Sector size too small for device\n");
ret = -EINVAL;
goto errout;
}
else if (totalsectors == 65536)
{
totalsectors -= 2;
}
dev->totalsectors = (uint16_t)totalsectors;
dev->freesectors = (uint16_t)dev->availsectperblk *
dev->geo.neraseblocks;
dev->lastallocblock = 0;
dev->debuglevel = 0;
/* Mark the device format status an unknown */
dev->formatstatus = SMART_FMT_STAT_UNKNOWN;
dev->namesize = CONFIG_SMARTFS_MAXNAMLEN;
if (partname)
{
strlcpy(dev->partname, partname, SMART_PARTNAME_SIZE);
}
else
{
dev->partname[0] = '\0';
}
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
dev->minor = minor;
#endif
/* Do a scan of the device */
ret = smart_scan(dev);
if (ret < 0)
{
ferr("ERROR: smart_scan failed: %d\n", -ret);
goto errout;
}
/* 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 = (FAR struct smart_multiroot_device_s *)
smart_malloc(dev, sizeof(*rootdirdev), "Root Dir");
if (rootdirdev == NULL)
{
ferr("ERROR: register_blockdriver failed: %d\n", -ret);
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)
{
ferr("ERROR: register_blockdriver failed: %d\n", -ret);
goto errout;
}
}
#ifdef CONFIG_SMART_DEV_LOOP
register_driver("/dev/smart", &g_fops, 0666, NULL);
#endif
return OK;
errout:
#ifndef CONFIG_MTD_SMART_MINIMIZE_RAM
smart_free(dev, dev->smap);
#else
smart_free(dev, dev->sbitmap);
smart_free(dev, dev->scache);
#endif
smart_free(dev, dev->rwbuffer);
#ifdef CONFIG_MTD_SMART_WEAR_LEVEL
smart_free(dev, dev->wearstatus);
#endif
#ifdef CONFIG_MTD_SMART_SECTOR_ERASE_DEBUG
smart_free(dev, dev->erasecounts);
#endif
#ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS
if (rootdirdev)
{
smart_free(dev, rootdirdev);
}
#endif
kmm_free(dev);
return ret;
}
/****************************************************************************
* Name: smart_losetup
*
* Description: Dynamically setups up a SMART enabled loop device that
* is backed by a file. The resulting loop device is a
* MTD type block device vs. a generic block device.
*
****************************************************************************/
#ifdef CONFIG_SMART_DEV_LOOP
static int smart_losetup(int minor, FAR const char *filename,
int sectsize, int erasesize, off_t offset, bool readonly)
{
FAR struct mtd_dev_s *mtd;
struct stat sb;
int x;
int ret;
char devpath[20];
/* Try to create a filemtd device using the filename provided */
mtd = filemtd_initialize(filename, offset, sectsize, erasesize);
if (mtd == NULL)
{
return -ENOENT;
}
/* Check if we need to dynamically assign a minor number */
if (minor == -1)
{
/* Start at zero and stat /dev/smartX until no entry found.
* Searching 0 to 256 should be sufficient.
*/
for (x = 0; x < 256; x++)
{
snprintf(devpath, sizeof(devpath), "/dev/smart%d", x);
ret = nx_stat(devpath, &sb, 1);
if (ret < 0)
{
/* We can use this minor number */
minor = x;
break;
}
}
}
/* Now create a smart MTD using the filemtd backing it */
ret = smart_initialize(minor, mtd, NULL);
if (ret != OK)
{
filemtd_teardown(mtd);
}
return ret;
}
#endif /* CONFIG_SMART_DEV_LOOP */
/****************************************************************************
* Name: loteardown
*
* Description:
* Undo the setup performed by losetup
*
****************************************************************************/
#ifdef CONFIG_SMART_DEV_LOOP
static int smart_loteardown(FAR const char *devname)
{
FAR struct smart_struct_s *dev;
FAR struct inode *inode;
int ret;
/* Sanity check */
#ifdef CONFIG_DEBUG_FEATURES
if (!devname)
{
return -EINVAL;
}
#endif
/* Open the block driver associated with devname so that we can get the
* inode reference.
*/
ret = open_blockdriver(devname, MS_RDONLY, &inode);
if (ret < 0)
{
ferr("ERROR: Failed to open %s: %d\n", devname, -ret);
return ret;
}
/* Inode private data is a reference to the loop device structure */
dev = (FAR struct smart_struct_s *)inode->i_private;
/* Validate this is a filemtd backended device */
if (!filemtd_isfilemtd(dev->mtd))
{
ferr("ERROR: Device is not a SMART loop: %s\n", devname);
return -EINVAL;
}
close_blockdriver(inode);
/* Now teardown the filemtd */
filemtd_teardown(dev->mtd);
unregister_blockdriver(devname);
kmm_free(dev);
return OK;
}
#endif /* CONFIG_SMART_DEV_LOOP */
/****************************************************************************
* Name: smart_loop_read
****************************************************************************/
#ifdef CONFIG_SMART_DEV_LOOP
static ssize_t smart_loop_read(FAR struct file *filep, FAR char *buffer,
size_t len)
{
return 0; /* Return EOF */
}
#endif /* CONFIG_SMART_DEV_LOOP */
/****************************************************************************
* Name: smart_loop_write
****************************************************************************/
#ifdef CONFIG_SMART_DEV_LOOP
static ssize_t smart_loop_write(FAR struct file *filep,
FAR const char *buffer, size_t len)
{
return len; /* Say that everything was written */
}
#endif /* CONFIG_SMART_DEV_LOOP */
/****************************************************************************
* Name: smart_loop_ioctl
****************************************************************************/
#ifdef CONFIG_SMART_DEV_LOOP
static int smart_loop_ioctl(FAR struct file *filep, int cmd,
unsigned long arg)
{
int ret;
switch (cmd)
{
/* Command: LOOPIOC_SETUP
* Description: Setup the loop device
* Argument: A pointer to a read-only instance of struct losetup_s.
* Dependencies: The loop device must be enabled (CONFIG_DEV_LOOP=y)
*/
case SMART_LOOPIOC_SETUP:
{
FAR struct smart_losetup_s *setup =
(FAR struct smart_losetup_s *)((uintptr_t)arg);
if (setup == NULL)
{
ret = -EINVAL;
}
else
{
ret = smart_losetup(setup->minor, setup->filename,
setup->sectsize, setup->erasesize,
setup->offset, setup->readonly);
}
}
break;
/* Command: LOOPIOC_TEARDOWN
* Description: Teardown a loop device previously setup via
* LOOPIOC_SETUP
* Argument: A read-able pointer to the path of the device to be
* torn down
* Dependencies: The loop device must be enabled (CONFIG_DEV_LOOP=y)
*/
case SMART_LOOPIOC_TEARDOWN:
{
FAR const char *devname = (FAR const char *)((uintptr_t)arg);
if (devname == NULL)
{
ret = -EINVAL;
}
else
{
ret = smart_loteardown(devname);
}
}
break;
default:
ret = -ENOTTY;
}
return ret;
}
#endif /* CONFIG_SMART_DEV_LOOP */