/**************************************************************************** * drivers/mtd/smart.c * * Sector Mapped Allocation for Really Tiny (SMART) Flash block driver. * * Copyright (C) 2013-2016 Ken Pettit. All rights reserved. * Author: Ken Pettit * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ //#define CONFIG_SMART_LOCAL_CHECKFREE #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 #ifdef CONFIG_MTD_SMART_WEAR_LEVEL /* 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 gWearLevelToBitMap4[] = { 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 gWearBitToLevelMap4[] = { 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]; /* Optional partition name */ uint8_t formatversion; /* Format version on the device */ uint8_t formatstatus; /* Indicates the status of the device format */ uint8_t namesize; /* Length of filenames on this device */ 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 }; #define SMART_WEARFLAGS_FORCE_REORG 0x01 #define SMART_WEARFLAGS_WRITE_NEEDED 0x02 #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 commited * 0 = commited * 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 commited * 0 = commited * 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 commited * 0 = commited * 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 /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int smart_open(FAR struct inode *inode); static int smart_close(FAR struct inode *inode); static ssize_t smart_reload(struct smart_struct_s *dev, FAR uint8_t *buffer, off_t startblock, size_t nblocks); static ssize_t smart_read(FAR struct inode *inode, unsigned char *buffer, size_t start_sector, unsigned int nsectors); #ifdef CONFIG_FS_WRITABLE static ssize_t smart_write(FAR struct inode *inode, const unsigned char *buffer, size_t start_sector, unsigned int nsectors); #endif static int smart_geometry(FAR struct inode *inode, struct geometry *geometry); static int smart_ioctl(FAR struct inode *inode, int cmd, unsigned long arg); static int smart_findfreephyssector(FAR struct smart_struct_s *dev, uint8_t canrelocate); #ifdef CONFIG_FS_WRITABLE 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); #endif static int smart_readsector(FAR struct smart_struct_s *dev, unsigned long arg); #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_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 */ #ifdef CONFIG_FS_WRITABLE smart_write, /* write */ #else NULL, /* write */ #endif 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 */ NULL /* poll */ }; #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, freecount; #ifdef CONFIG_DEBUG_FS uint16_t blockfree, 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); } } } /* Modifiy 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 numer of sectors * ****************************************************************************/ static ssize_t smart_reload(struct smart_struct_s *dev, FAR uint8_t *buffer, off_t startblock, size_t nblocks) { ssize_t nread; ssize_t mtdBlocks, mtdStartBlock; /* Calculate the number of MTD blocks to read */ mtdBlocks = nblocks * dev->mtdBlksPerSector; /* Calculate the first MTD block number */ mtdStartBlock = startblock * dev->mtdBlksPerSector; /* Read the full erase block into the buffer */ finfo("Read %d blocks starting at block %d\n", mtdBlocks, mtdStartBlock); nread = MTD_BREAD(dev->mtd, mtdStartBlock, mtdBlocks, buffer); if (nread != mtdBlocks) { ferr("ERROR: Read %d blocks starting at block %d failed: %d\n", nblocks, startblock, nread); } return nread; } /**************************************************************************** * Name: smart_read * * Description: Read the specified numer of sectors * ****************************************************************************/ static ssize_t smart_read(FAR struct inode *inode, unsigned char *buffer, size_t start_sector, unsigned int nsectors) { FAR struct smart_struct_s *dev; finfo("SMART: sector: %d nsectors: %d\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 * ****************************************************************************/ #ifdef CONFIG_FS_WRITABLE static ssize_t smart_write(FAR struct inode *inode, FAR const unsigned char *buffer, size_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, mtdblockcount; finfo("sector: %d nsectors: %d\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: %d mtdnsectors: %d\n", mtdstartblock, mtdblockcount); /* Start at first block to be written */ remaining = mtdblockcount; nextblock = mtdstartblock; offset = 0; /* Loop for all blocks to be written */ while (remaining > 0) { /* If this is an aligned block, then erase the block */ if (alignedblock == nextblock) { /* Erase the erase block */ eraseblock = alignedblock / mtdBlksPerErase; ret = MTD_ERASE(dev->mtd, eraseblock, 1); if (ret < 0) { ferr("ERROR: Erase block=%d failed: %d\n", eraseblock, ret); /* Unlock the mutex if we add one */ return ret; } } /* Calculate the number of blocks to write. */ blkstowrite = mtdBlksPerErase; if (nextblock != alignedblock) { blkstowrite = alignedblock - nextblock; } if (blkstowrite > remaining) { blkstowrite = remaining; } /* Try to write to the sector. */ finfo("Write MTD block %d from offset %d\n", nextblock, offset); nxfrd = MTD_BWRITE(dev->mtd, nextblock, blkstowrite, &buffer[offset]); if (nxfrd != blkstowrite) { /* The block is not empty!! What to do? */ ferr("ERROR: Write block %d failed: %d.\n", nextblock, nxfrd); /* Unlock the mutex if we add one */ return -EIO; } /* Then update for amount written */ nextblock += blkstowrite; remaining -= blkstowrite; offset += blkstowrite * dev->geo.blocksize; alignedblock += mtdBlksPerErase; } return nsectors; } #endif /* CONFIG_FS_WRITABLE */ /**************************************************************************** * Name: smart_geometry * * Description: Return device geometry * ****************************************************************************/ static int smart_geometry(FAR struct inode *inode, struct geometry *geometry) { 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; #ifdef CONFIG_FS_WRITABLE geometry->geo_writeenabled = true; #else geometry->geo_writeenabled = false; #endif 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: %d sectorsize: %d\n", geometry->geo_nsectors, geometry->geo_sectorsize); return OK; } return -EINVAL; } /**************************************************************************** * Name: smart_setsectorsize * * Description: Sets the device's sector size and recalculates sector size * dependant variables. * ****************************************************************************/ static int smart_setsectorsize(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 becasue 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 %ld\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 %d reading from device\n", -ret); goto errout; } /* Modify the data */ memcpy(&dev->rwbuffer[offset - startblock * dev->geo.blocksize], buffer, nbytes); /* Write the data back to the device */ ret = MTD_BWRITE(dev->mtd, startblock, nblocks, (FAR uint8_t *) dev->rwbuffer); if (ret < 0) { ferr("ERROR: Error %d 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, 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, sector; uint16_t x, physical, 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 gWearBitToLevelMap4[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, 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 = gWearLevelToBitMap4[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, prerelease; uint16_t logicalsector; uint16_t loser; uint32_t readaddress; uint32_t offset; uint16_t seq1; uint16_t seq2; 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[22]; FAR struct smart_multiroot_device_s *rootdirdev; #endif static const short 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 seach 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 */ for (sector = 0; sector < totalsectors; sector++) { finfo("Scan sector %d\n", sector); /* Calculate the read address for this sector */ readaddress = sector * dev->mtdBlksPerSector * dev->geo.blocksize; /* Read the header for this sector */ ret = MTD_READ(dev->mtd, readaddress, sizeof(struct smart_sect_header_s), (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 commited, 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(dev->rwbuffer, sizeof(devname), "/dev/smart%d%sd%d", dev->minor, dev->partname, x+1); } else { snprintf(devname, sizeof(devname), "/dev/smart%dd%d", dev->minor, x + 1); } /* Inode private data is a reference to a struct containing * the SMART device structure and the root directory number. */ rootdirdev = (struct smart_multiroot_device_s *) 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; ret = register_blockdriver(dev->rwbuffer, &g_bops, 0, rootdirdev); /* Inode private data is a reference to the SMART device structure */ ret = register_blockdriver(devname, &g_bops, 0, rootdirdev); } #endif } /* Test for duplicate logical sectors on the device */ #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) { 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) { seq1 = header.seq; } else { seq1 = *((FAR uint16_t *) &header.seq); } #else seq1 = header.seq; #endif /* Now determine who wins */ if ((seq1 > 0xfff0 && seq2 < 10) || seq2 > seq1) { /* Seq 2 is the winner ... bigger or it wrapped */ #ifndef CONFIG_MTD_SMART_MINIMIZE_RAM loser = dev->sMap[logicalsector]; dev->sMap[logicalsector] = sector; #else loser = dupsector; #endif } else { /* We keep the original mapping and seq2 is the loser */ loser = sector; } /* Now release the loser sector */ readaddress = loser * dev->mtdBlksPerSector * dev->geo.blocksize; ret = MTD_READ(dev->mtd, readaddress, sizeof(struct smart_sect_header_s), (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; } } #ifndef CONFIG_MTD_SMART_MINIMIZE_RAM /* Update the logical to physical sector map */ dev->sMap[logicalsector] = sector; #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, sector, __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_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, releasecount, 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 /* CONFIG_MTD_SMART_PACK_COUNTS */ /* 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, x, sector, minblock; uint16_t nextsector, newsector, 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 entirity. */ /* 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((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((uint8_t *) dev->rwbuffer, 3, crc); /* Add status to the CRC calculation */ crc = crc8part((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((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((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((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((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 * ****************************************************************************/ #ifdef CONFIG_FS_WRITABLE 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, 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 = %d\n", dev->erasesize); ferr(" Sector size = %d\n", dev->sectorsize); ferr(" Sectors/erase block = %d\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 *) §orheader->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: %d.\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-existant */ 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; } #endif /* CONFIG_FS_WRITABLE */ /**************************************************************************** * 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) { #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); #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); /* 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); } #ifndef CONFIG_MTD_SMART_ENABLE_CRC errout: #endif 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, 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, allocfreecount, allocblock; #ifdef CONFIG_MTD_SMART_WEAR_LEVEL uint16_t wornfreecount, wornblock; uint8_t wearlevel, 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; } } 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. * ****************************************************************************/ #ifdef CONFIG_FS_WRITABLE 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 } //releasemax = smart_get_count(dev, dev->releasecount, collectblock); 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; } #endif /* CONFIG_FS_WRITABLE */ /**************************************************************************** * 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, towrite; struct smart_read_write_s req; int ret; uint8_t buffer[8], 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, physsector; uint16_t remaining, 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)) { #ifdef CONFIG_FS_WRITABLE /* 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; } #endif } /* 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. * ****************************************************************************/ #ifdef CONFIG_FS_WRITABLE 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->crc8) = 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; } #endif /**************************************************************************** * 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. * ****************************************************************************/ #ifdef CONFIG_FS_WRITABLE static int smart_writesector(FAR struct smart_struct_s *dev, unsigned long arg) { int ret; bool needsrelocate = FALSE; uint32_t mtdblock; uint16_t physsector, oldphyssector, 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 adjustement 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 %d\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) { #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; } #endif /* CONFIG_FS_WRITABLE */ /**************************************************************************** * Name: smart_readsector * * Description: Reads data from the specified logical sector. The sector * should have already been allocated prior to the read. * ****************************************************************************/ static 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); ret = -EINVAL; goto errout; } #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; } #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); ret = -EIO; goto errout; } #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); ret = -EIO; goto errout; } } /* 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); ret = -EIO; goto errout; } /* 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); ret = -EIO; goto errout; } /* Read the sector data into the buffer */ readaddr = (uint32_t) physsector * dev->mtdBlksPerSector * dev->geo.blocksize + req->offset + sizeof(struct smart_sect_header_s); ret = MTD_READ(dev->mtd, readaddr, req->count, (FAR uint8_t *) req->buffer); if (ret != req->count) { ferr("ERROR: Error reading phys sector %d\n", physsector); ret = -EIO; goto errout; } #endif errout: return ret; } /**************************************************************************** * Name: smart_allocsector * * Description: Allocates a new logical sector. If an argument is given, * then it tries to allocate the specified sector number. * ****************************************************************************/ #ifdef CONFIG_FS_WRITABLE static inline int smart_allocsector(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->releasecount); 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; } #endif /* CONFIG_FS_WRITABLE */ /**************************************************************************** * Name: smart_freesector * * Description: Frees a logical sector from the device. Freeing (also * called releasing) is performed by programming the released * bit in the sector header's status byte. * ****************************************************************************/ #ifdef CONFIG_FS_WRITABLE static inline int smart_freesector(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 %d 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 %d logical sector in header doesn't match\n", logicalsector); ret = -EINVAL; goto errout; } /* Mark the sector as released */ #if CONFIG_SMARTFS_ERASEDSTATE == 0xff header.status &= ~SMART_STATUS_RELEASED; #else header.status |= SMART_STATUS_RELEASED; #endif /* Write the status back to the device */ offset = readaddr + offsetof(struct smart_sect_header_s, status); ret = smart_bytewrite(dev, offset, 1, &header.status); if (ret != 1) { 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; } #endif /* CONFIG_FS_WRITABLE */ /**************************************************************************** * 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_XIPBASE: /* The argument accompanying the BIOC_XIPBASE should be non-NULL. If * DEBUG is enabled, we will catch it here instead of in the MTD * driver. */ #ifdef CONFIG_DEBUG_FEATURES if (arg == 0) { ferr("ERROR: BIOC_XIPBASE argument is NULL\n"); return -EINVAL; } #endif /* Just change the BIOC_XIPBASE command to the MTDIOC_XIPBASE command. */ cmd = MTDIOC_XIPBASE; break; case BIOC_GETFORMAT: /* Return the format information for the device */ #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS ret = smart_getformat(dev, (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; #ifdef CONFIG_FS_WRITABLE case BIOC_LLFORMAT: /* Perform a low-level format on the flash */ ret = smart_llformat(dev, arg); goto ok_out; case BIOC_ALLOCSECT: /* 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; #endif /* CONFIG_FS_WRITABLE */ #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; } /**************************************************************************** * 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) { strncpy(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, 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 = stat(devpath, &sb); 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 vis 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 */