/**************************************************************************** * drivers/mtd/smart.c * * Sector Mapped Allocation for Really Tiny (SMART) Flash block driver. * * Copyright (C) 2013 Ken Pettit. All rights reserved. * Author: Ken Pettit * * 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 /**************************************************************************** * Private Definitions ****************************************************************************/ #define SMART_STATUS_COMMITTED 0x80 #define SMART_STATUS_RELEASED 0x40 #define SMART_STATUS_SIZEBITS 0x1C #define SMART_STATUS_VERBITS 0x03 #define SMART_STATUS_VERSION 0x01 #define SMART_SECTSIZE_256 0x00 #define SMART_SECTSIZE_512 0x04 #define SMART_SECTSIZE_1024 0x08 #define SMART_SECTSIZE_2048 0x0C #define SMART_SECTSIZE_4096 0x10 #define SMART_SECTSIZE_8192 0x14 #define SMART_SECTSIZE_16384 0x18 #define SMART_FMT_STAT_UNKNOWN 0 #define SMART_FMT_STAT_FORMATTED 1 #define SMART_FMT_STAT_NOFMT 2 #define SMART_FMT_POS1 sizeof(struct smart_sect_header_s) #define SMART_FMT_POS2 (SMART_FMT_POS1 + 1) #define SMART_FMT_POS3 (SMART_FMT_POS1 + 2) #define SMART_FMT_POS4 (SMART_FMT_POS1 + 3) #define SMART_FMT_SIG1 'S' #define SMART_FMT_SIG2 'M' #define SMART_FMT_SIG3 'R' #define SMART_FMT_SIG4 'T' #define SMART_FMT_VERSION_POS (SMART_FMT_POS1 + 4) #define SMART_FMT_NAMESIZE_POS (SMART_FMT_POS1 + 5) #define SMART_FMT_ROOTDIRS_POS (SMART_FMT_POS1 + 6) #define SMARTFS_FMT_AGING_POS 32 #define SMART_FMT_VERSION 1 #define SMART_PARTNAME_SIZE 4 #define SMART_FIRST_ALLOC_SECTOR 12 /* First logical sector number we will * use for assignment of requested Alloc * sectors. All enries below this are * reserved (some for root dir entries, * other for our use, such as format * sector, etc. */ #if defined(CONFIG_FS_READAHEAD) || (defined(CONFIG_FS_WRITABLE) && defined(CONFIG_FS_WRITEBUFFER)) # define CONFIG_SMART_RWBUFFER 1 #endif #ifndef CONFIG_MTD_SMART_SECTOR_SIZE # define CONFIG_MTD_SMART_SECTOR_SIZE 1024 #endif #ifndef offsetof #define offsetof(type, member) ( (size_t) &( ( (type *) 0)->member)) #endif /**************************************************************************** * Private Types ****************************************************************************/ struct smart_struct_s { FAR struct mtd_dev_s *mtd; /* Contained MTD interface */ struct mtd_geometry_s geo; /* Device geometry */ uint16_t neraseblocks; /* Number of erase blocks or sub-sectors */ uint16_t freesectors; /* Total number of free sectors */ uint16_t mtdBlksPerSector; /* Number of MTD blocks per SMART Sector */ uint16_t sectorsPerBlk; /* Number of sectors per erase block */ uint16_t sectorsize; /* Sector size on device */ uint16_t totalsectors; /* Total number of sectors on device */ FAR uint16_t *sMap; /* Virtual to physical sector map */ FAR uint8_t *releasecount; /* Count of released sectors per erase block */ FAR uint8_t *freecount; /* Count of free sectors per erase block */ FAR char *rwbuffer; /* Our sector read/write buffer */ char partname[SMART_PARTNAME_SIZE]; /* Optional partition name */ uint8_t formatversion; /* Format version on the device */ uint8_t formatstatus; /* Indicates the status of the device format */ uint8_t namesize; /* Length of filenames on this device */ #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS uint8_t rootdirentries; /* Number of root directory entries */ uint8_t minor; /* Minor number of the block entry */ #endif }; #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS struct smart_multiroot_device_s { FAR struct smart_struct_s* dev; uint8_t rootdirnum; }; #endif struct smart_sect_header_s { uint8_t logicalsector[2]; /* The logical sector number */ uint8_t seq[2]; /* Incrementing sequence number */ uint8_t status; /* Status of this sector: * Bit 7: 1 = Not commited * 0 = commited * Bit 6: 1 = Not released * 0 = released * Bit 5: Reserved - 1 * Bit 4: Reserved - 1 * Bit 3: Reserved - 1 * Bit 2: Reserved - 1 * Bit 1-0: Format version */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int smart_open(FAR struct inode *inode); static int smart_close(FAR struct inode *inode); static ssize_t smart_reload(struct smart_struct_s *dev, FAR uint8_t *buffer, off_t startblock, size_t nblocks); static ssize_t smart_read(FAR struct inode *inode, unsigned char *buffer, size_t start_sector, unsigned int nsectors); #ifdef CONFIG_FS_WRITABLE static ssize_t smart_write(FAR struct inode *inode, const unsigned char *buffer, size_t start_sector, unsigned int nsectors); #endif static int smart_geometry(FAR struct inode *inode, struct geometry *geometry); static int smart_ioctl(FAR struct inode *inode, int cmd, unsigned long arg); /**************************************************************************** * Private Data ****************************************************************************/ static const struct block_operations g_bops = { smart_open, /* open */ smart_close, /* close */ smart_read, /* read */ #ifdef CONFIG_FS_WRITABLE smart_write, /* write */ #else NULL, /* write */ #endif smart_geometry, /* geometry */ smart_ioctl /* ioctl */ }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: smart_open * * Description: Open the block device * ****************************************************************************/ static int smart_open(FAR struct inode *inode) { fvdbg("Entry\n"); return OK; } /**************************************************************************** * Name: smart_close * * Description: close the block device * ****************************************************************************/ static int smart_close(FAR struct inode *inode) { fvdbg("Entry\n"); return OK; } /**************************************************************************** * Name: smart_reload * * Description: Read the specified numer of sectors * ****************************************************************************/ static ssize_t smart_reload(struct smart_struct_s *dev, FAR uint8_t *buffer, off_t startblock, size_t nblocks) { ssize_t nread; ssize_t mtdBlocks, mtdStartBlock; /* Calculate the number of MTD blocks to read */ mtdBlocks = nblocks * dev->mtdBlksPerSector; /* Calculate the first MTD block number */ mtdStartBlock = startblock * dev->mtdBlksPerSector; /* Read the full erase block into the buffer */ fdbg("Read %d blocks starting at block %d\n", mtdBlocks, mtdStartBlock); nread = MTD_BREAD(dev->mtd, mtdStartBlock, mtdBlocks, buffer); if (nread != mtdBlocks) { fdbg("Read %d blocks starting at block %d failed: %d\n", nblocks, startblock, nread); } return nread; } /**************************************************************************** * Name: smart_read * * Description: Read the specified numer of sectors * ****************************************************************************/ static ssize_t smart_read(FAR struct inode *inode, unsigned char *buffer, size_t start_sector, unsigned int nsectors) { struct smart_struct_s *dev; fvdbg("SMART: sector: %d nsectors: %d\n", start_sector, nsectors); DEBUGASSERT(inode && inode->i_private); #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS dev = ((struct smart_multiroot_device_s*) inode->i_private)->dev; #else dev = (struct smart_struct_s *)inode->i_private; #endif return smart_reload(dev, buffer, start_sector, nsectors); } /**************************************************************************** * Name: smart_write * * Description: Write (or buffer) the specified number of sectors * ****************************************************************************/ #ifdef CONFIG_FS_WRITABLE static ssize_t smart_write(FAR struct inode *inode, const unsigned char *buffer, size_t start_sector, unsigned int nsectors) { struct smart_struct_s *dev; off_t alignedblock; off_t mask; off_t blkstowrite; off_t offset; off_t nextblock; off_t mtdBlksPerErase; off_t eraseblock; size_t remaining; size_t nxfrd; int ret; off_t mtdstartblock, mtdblockcount; fvdbg("sector: %d nsectors: %d\n", start_sector, nsectors); DEBUGASSERT(inode && inode->i_private); #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS dev = ((struct smart_multiroot_device_s*) inode->i_private)->dev; #else dev = (struct smart_struct_s *)inode->i_private; #endif /* I think maybe we need to lock on a mutex here */ /* Get the aligned block. Here is is assumed: (1) The number of R/W blocks * per erase block is a power of 2, and (2) the erase begins with that same * alignment. */ mask = dev->sectorsPerBlk - 1; alignedblock = ((start_sector + mask) & ~mask) * dev->mtdBlksPerSector; /* Convert SMART blocks into MTD blocks */ mtdstartblock = start_sector * dev->mtdBlksPerSector; mtdblockcount = nsectors * dev->mtdBlksPerSector; mtdBlksPerErase = dev->mtdBlksPerSector * dev->sectorsPerBlk; fvdbg("mtdsector: %d mtdnsectors: %d\n", mtdstartblock, mtdblockcount); /* Start at first block to be written */ remaining = mtdblockcount; nextblock = mtdstartblock; offset = 0; /* Loop for all blocks to be written */ while (remaining > 0) { /* If this is an aligned block, then erase the block */ if (alignedblock == nextblock) { /* Erase the erase block */ eraseblock = alignedblock / mtdBlksPerErase; ret = MTD_ERASE(dev->mtd, eraseblock, 1); if (ret < 0) { fdbg("Erase block=%d failed: %d\n", eraseblock, ret); /* Unlock the mutex if we add one */ return ret; } } /* Calculate the number of blocks to write. */ blkstowrite = mtdBlksPerErase; if (nextblock != alignedblock) { blkstowrite = alignedblock - nextblock; } if (blkstowrite > remaining) { blkstowrite = remaining; } /* Try to write to the sector. */ fdbg("Write MTD block %d from offset %d\n", nextblock, offset); nxfrd = MTD_BWRITE(dev->mtd, nextblock, blkstowrite, &buffer[offset]); if (nxfrd != blkstowrite) { /* The block is not empty!! What to do? */ fdbg("Write block %d failed: %d.\n", nextblock, nxfrd); /* Unlock the mutex if we add one */ return -EIO; } /* Then update for amount written */ nextblock += blkstowrite; remaining -= blkstowrite; offset += blkstowrite * dev->geo.blocksize; alignedblock += mtdBlksPerErase; } return nsectors; } #endif /* CONFIG_FS_WRITABLE */ /**************************************************************************** * Name: smart_geometry * * Description: Return device geometry * ****************************************************************************/ static int smart_geometry(FAR struct inode *inode, struct geometry *geometry) { struct smart_struct_s *dev; uint32_t erasesize; fvdbg("Entry\n"); DEBUGASSERT(inode); if (geometry) { #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS dev = ((struct smart_multiroot_device_s*) inode->i_private)->dev; #else dev = (struct smart_struct_s *)inode->i_private; #endif geometry->geo_available = true; geometry->geo_mediachanged = false; #ifdef CONFIG_FS_WRITABLE geometry->geo_writeenabled = true; #else geometry->geo_writeenabled = false; #endif erasesize = dev->geo.erasesize; if (erasesize == 0) { erasesize = 65536; } geometry->geo_nsectors = dev->geo.neraseblocks * erasesize / dev->sectorsize; geometry->geo_sectorsize = dev->sectorsize; fvdbg("available: true mediachanged: false writeenabled: %s\n", geometry->geo_writeenabled ? "true" : "false"); fvdbg("nsectors: %d sectorsize: %d\n", geometry->geo_nsectors, geometry->geo_sectorsize); return OK; } return -EINVAL; } /**************************************************************************** * Name: smart_setsectorsize * * Description: Sets the device's sector size and recalculates sector size * dependant variables. * ****************************************************************************/ static int smart_setsectorsize(struct smart_struct_s *dev, uint16_t size) { uint32_t erasesize; uint32_t totalsectors; /* Validate the size isn't zero so we don't divide by zero below */ if (size == 0) { size = CONFIG_MTD_SMART_SECTOR_SIZE; } erasesize = dev->geo.erasesize; dev->neraseblocks = dev->geo.neraseblocks; /* Most FLASH devices have erase size of 64K, but geo.erasesize is only * 16 bits, so it will be zero */ if (erasesize == 0) { erasesize = 65536; } dev->sectorsize = size; dev->mtdBlksPerSector = dev->sectorsize / dev->geo.blocksize; dev->sectorsPerBlk = erasesize / dev->sectorsize; /* Release any existing rwbuffer and sMap */ if (dev->sMap != NULL) { kfree(dev->sMap); } if (dev->rwbuffer != NULL) { kfree(dev->rwbuffer); } /* Allocate a virtual to physical sector map buffer. Also allocate * the storage space for releasecount and freecounts. */ totalsectors = dev->neraseblocks * dev->sectorsPerBlk; dev->totalsectors = (uint16_t) totalsectors; dev->sMap = (uint16_t *) kmalloc(totalsectors * sizeof(uint16_t) + (dev->neraseblocks << 1)); if (!dev->sMap) { fdbg("Error allocating SMART virtual map buffer\n"); kfree(dev); return -EINVAL; } dev->releasecount = (uint8_t *) dev->sMap + (totalsectors * sizeof(uint16_t)); dev->freecount = dev->releasecount + dev->neraseblocks; /* Allocate a read/write buffer */ dev->rwbuffer = (char *) kmalloc(size); if (!dev->rwbuffer) { fdbg("Error allocating SMART read/write buffer\n"); kfree(dev->sMap); kfree(dev); return -EINVAL; } return OK; } /**************************************************************************** * Name: smart_bytewrite * * Description: Writes a non-page size count of bytes to the underlying * MTD device. If the MTD driver supports a direct impl of * write, then it uses it, otherwise it does a read-modify-write * and depends on the architecture of the flash to only program * bits that acutally changed. * ****************************************************************************/ static ssize_t smart_bytewrite(struct smart_struct_s *dev, size_t offset, int nbytes, const uint8_t *buffer) { ssize_t ret; #ifdef CONFIG_MTD_BYTE_WRITE /* Check if the underlying MTD device supports write */ if (dev->mtd->write != NULL) { /* Use the MTD's write method to write individual bytes */ ret = dev->mtd->write(dev->mtd, offset, nbytes, buffer); } else #endif { /* Perform block-based read-modify-write */ uint32_t startblock; uint16_t nblocks; /* First calculate the start block and number of blocks affected */ startblock = offset / dev->geo.blocksize; nblocks = (offset - startblock * dev->geo.blocksize + nbytes + dev->geo.blocksize-1) / dev->geo.blocksize; DEBUGASSERT(nblocks <= dev->mtdBlksPerSector); /* Do a block read */ ret = MTD_BREAD(dev->mtd, startblock, nblocks, (uint8_t *) dev->rwbuffer); if (ret < 0) { fdbg("Error %d reading from device\n", -ret); goto errout; } /* Modify the data */ memcpy(&dev->rwbuffer[offset - startblock * dev->geo.blocksize], buffer, nbytes); /* Write the data back to the device */ ret = MTD_BWRITE(dev->mtd, startblock, nblocks, (uint8_t *) dev->rwbuffer); if (ret < 0) { fdbg("Error %d writing to device\n", -ret); goto errout; } } ret = nbytes; errout: return ret; } /**************************************************************************** * Name: smart_scan * * Description: Performs a scan of the MTD device searching for format * information and fills in logical sector mapping, freesector * count, etc. * ****************************************************************************/ static int smart_scan(struct smart_struct_s *dev) { int sector; int ret; int offset; uint16_t totalsectors; uint16_t sectorsize; uint16_t logicalsector; uint16_t seq1; uint16_t seq2; size_t readaddress; struct smart_sect_header_s header; #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS int x; char devname[22]; struct smart_multiroot_device_s *rootdirdev; #endif fvdbg("Entry\n"); /* Read the 1st header from the device. We always keep the * 1st sector's header's sectorsize field accurate, even * after we erase an MTD block/sector */ ret = MTD_READ(dev->mtd, 0, sizeof(struct smart_sect_header_s), (uint8_t *) &header); if (ret != sizeof(struct smart_sect_header_s)) { goto err_out; } /* Now set the sectorsize and other sectorsize derived variables */ if (header.status == CONFIG_SMARTFS_ERASEDSTATE) { sectorsize = CONFIG_MTD_SMART_SECTOR_SIZE; } else { sectorsize = (header.status & SMART_STATUS_SIZEBITS) << 7; } ret = smart_setsectorsize(dev, sectorsize); if (ret != OK) { goto err_out; } /* Initialize the device variables */ totalsectors = dev->neraseblocks * dev->sectorsPerBlk; dev->formatstatus = SMART_FMT_STAT_NOFMT; dev->freesectors = totalsectors; /* Initialize the freecount and releasecount arrays */ for (sector = 0; sector < dev->neraseblocks; sector++) { dev->freecount[sector] = dev->sectorsPerBlk; dev->releasecount[sector] = 0; } /* Initialize the sector map */ for (sector = 0; sector < totalsectors; sector++) { dev->sMap[sector] = -1; } /* Now scan the MTD device */ for (sector = 0; sector < totalsectors; sector++) { fvdbg("Scan sector %d\n", sector); /* Calculate the read address for this sector */ readaddress = sector * dev->mtdBlksPerSector * dev->geo.blocksize; /* Read the header for this sector */ ret = MTD_READ(dev->mtd, readaddress, sizeof(struct smart_sect_header_s), (uint8_t *) &header); if (ret != sizeof(struct smart_sect_header_s)) { goto err_out; } /* Get the logical sector number for this physical sector */ logicalsector = *((uint16_t *) header.logicalsector); #if CONFIG_SMARTFS_ERASEDSTATE == 0x00 if (logicalsector == 0) { logicalsector = -1; } #endif /* Test if this sector has been committed */ if ((header.status & SMART_STATUS_COMMITTED) == (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED)) { continue; } /* This block is commited, therefore not free. Update the * erase block's freecount. */ dev->freecount[sector / dev->sectorsPerBlk]--; dev->freesectors--; /* Test if this sector has been release and if it has, * update the erase block's releasecount. */ if ((header.status & SMART_STATUS_RELEASED) != (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_RELEASED)) { dev->releasecount[sector / dev->sectorsPerBlk]++; continue; } if ((header.status & SMART_STATUS_VERBITS) != SMART_STATUS_VERSION) { continue; } /* Validate the logical sector number is in bounds */ if (logicalsector >= totalsectors) { /* Error in logical sector read from the MTD device */ fdbg("Invalid logical sector %d at physical %d.\n", logicalsector, sector); continue; } /* If this is logical sector zero, then read in the signature * information to validate the format signature. */ if (logicalsector == 0) { /* Read the sector data */ ret = MTD_READ(dev->mtd, readaddress, 32, (uint8_t*) dev->rwbuffer); if (ret != 32) { fdbg("Error reading physical sector %d.\n", sector); goto err_out; } /* Validate the format signature */ if (dev->rwbuffer[SMART_FMT_POS1] != SMART_FMT_SIG1 || dev->rwbuffer[SMART_FMT_POS2] != SMART_FMT_SIG2 || dev->rwbuffer[SMART_FMT_POS3] != SMART_FMT_SIG3 || dev->rwbuffer[SMART_FMT_POS4] != SMART_FMT_SIG4) { /* Invalid signature on a sector claiming to be sector 0! * What should we do? Release it?*/ continue; } /* TODO: May want to validate / save the erase block aging info */ /* Mark the volume as formatted and set the sector size */ dev->formatstatus = SMART_FMT_STAT_FORMATTED; dev->namesize = dev->rwbuffer[SMART_FMT_NAMESIZE_POS]; dev->formatversion = dev->rwbuffer[SMART_FMT_VERSION_POS]; #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS dev->rootdirentries = dev->rwbuffer[SMART_FMT_ROOTDIRS_POS]; /* If rootdirentries is greater than 1, then we need to register * additional block devices. */ for (x = 1; x < dev->rootdirentries; x++) { if (dev->partname[0] != '\0') { snprintf(dev->rwbuffer, sizeof(devname), "/dev/smart%d%sd%d", dev->minor, dev->partname, x+1); } else { snprintf(devname, sizeof(devname), "/dev/smart%dd%d", dev->minor, x + 1); } /* Inode private data is a reference to a struct containing * the SMART device structure and the root directory number. */ rootdirdev = (struct smart_multiroot_device_s*) kmalloc(sizeof(*rootdirdev)); if (rootdirdev == NULL) { fdbg("Memory alloc failed\n"); ret = -ENOMEM; goto err_out; } /* Populate the rootdirdev */ rootdirdev->dev = dev; rootdirdev->rootdirnum = x; ret = register_blockdriver(dev->rwbuffer, &g_bops, 0, rootdirdev); /* Inode private data is a reference to the SMART device structure */ ret = register_blockdriver(devname, &g_bops, 0, rootdirdev); } #endif } /* Test for duplicate logical sectors on the device */ if (dev->sMap[logicalsector] != 0xFFFF) { /* Uh-oh, we found more than 1 physical sector claiming to be * the * same logical sector. Use the sequence number information * to resolve who wins. */ uint16_t loser; seq2 = *((uint16_t *) header.seq); /* We must re-read the 1st physical sector to get it's seq number */ readaddress = dev->sMap[logicalsector] * dev->mtdBlksPerSector * dev->geo.blocksize; ret = MTD_READ(dev->mtd, readaddress, sizeof(struct smart_sect_header_s), (uint8_t *) &header); if (ret != sizeof(struct smart_sect_header_s)) { goto err_out; } seq1 = *((uint16_t *) header.seq); /* Now determine who wins */ if (seq1 > 0xFFF0 && seq2 < 10) { /* Seq 2 is the winner ... we assume it wrapped */ loser = dev->sMap[logicalsector]; dev->sMap[logicalsector] = sector; } else if (seq2 > seq1) { /* Seq 2 is bigger, so it's the winner */ loser = dev->sMap[logicalsector]; dev->sMap[logicalsector] = sector; } else { /* We keep the original mapping and seq2 is the loser */ loser = sector; } /* Now release the loser sector */ readaddress = loser * dev->mtdBlksPerSector * dev->geo.blocksize; ret = MTD_READ(dev->mtd, readaddress, sizeof(struct smart_sect_header_s), (uint8_t *) &header); if (ret != sizeof(struct smart_sect_header_s)) { goto err_out; } #if CONFIG_SMARTFS_ERASEDSTATE == 0xFF header.status &= ~SMART_STATUS_RELEASED; #else header.status |= SMART_STATUS_RELEASED; #endif offset = readaddress + offsetof(struct smart_sect_header_s, status); ret = smart_bytewrite(dev, offset, 1, &header.status); if (ret < 0) { fdbg("Error %d releasing duplicate sector\n", -ret); goto err_out; } } /* Update the logical to physical sector map */ dev->sMap[logicalsector] = sector; } fdbg("SMART Scan\n"); fdbg(" Erase size: %10d\n", dev->sectorsPerBlk * dev->sectorsize); fdbg(" Erase count: %10d\n", dev->neraseblocks); fdbg(" Sect/block: %10d\n", dev->sectorsPerBlk); fdbg(" MTD Blk/Sect: %10d\n", dev->mtdBlksPerSector); ret = OK; err_out: return ret; } /**************************************************************************** * Name: smart_getformat * * Description: Populates the SMART format structure based on the format * information for the inode. * ****************************************************************************/ #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS static inline int smart_getformat(struct smart_struct_s *dev, struct smart_format_s *fmt, uint8_t rootdirnum) #else static inline int smart_getformat(struct smart_struct_s *dev, struct smart_format_s *fmt) #endif { int ret; int x; fvdbg("Entry\n"); DEBUGASSERT(fmt); /* Test if we know the format status or not. If we don't know the * status, then we must perform a scan of the device to search * for the format marker */ if (dev->formatstatus != SMART_FMT_STAT_FORMATTED) { /* Perform the scan */ ret = smart_scan(dev); if (ret != OK) { goto err_out; } } /* Now fill in the structure */ if (dev->formatstatus == SMART_FMT_STAT_FORMATTED) { fmt->flags = SMART_FMT_ISFORMATTED; } else { fmt->flags = 0; } fmt->sectorsize = dev->sectorsize; fmt->availbytes = dev->sectorsize - sizeof(struct smart_sect_header_s); fmt->nsectors = dev->neraseblocks * dev->sectorsPerBlk; fmt->nfreesectors = dev->freesectors; fmt->namesize = dev->namesize; #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS fmt->nrootdirentries = dev->rootdirentries; fmt->rootdirnum = rootdirnum; #endif /* Add the released sectors to the reported free sector count */ for (x = 0; x < dev->neraseblocks; x++) { fmt->nfreesectors += dev->releasecount[x]; } /* Subtract the reserved sector count */ fmt->nfreesectors -= dev->sectorsPerBlk + 4; ret = OK; err_out: return ret; } /**************************************************************************** * Name: smart_llformat * * Description: Performs a low-level format of the flash device. This * involves erasing the device and writing a valid sector * zero (logical) with proper format signature. * ****************************************************************************/ #ifdef CONFIG_FS_WRITABLE static inline int smart_llformat(struct smart_struct_s *dev, unsigned long arg) { struct smart_sect_header_s *sectorheader; size_t wrcount; size_t totalsectors; int x; int ret; uint8_t sectsize; fvdbg("Entry\n"); /* Erase the MTD device */ ret = MTD_IOCTL(dev->mtd, MTDIOC_BULKERASE, 0); if (ret < 0) { return ret; } /* Now construct a logical sector zero header to write to the device. * We fill it with zero so when we add sector aging, all the sector * ages will already be initialized to zero without needing special * logic to deal with a 0xFF erased-state value. */ sectorheader = (struct smart_sect_header_s *) dev->rwbuffer; memset(dev->rwbuffer, 0, dev->sectorsize); memset(dev->rwbuffer, CONFIG_SMARTFS_ERASEDSTATE, SMARTFS_FMT_AGING_POS); *((uint16_t *) sectorheader->seq) = 0; sectsize = (CONFIG_MTD_SMART_SECTOR_SIZE >> 9) << 2; #if ( CONFIG_SMARTFS_ERASEDSTATE == 0xFF ) *((uint16_t *) sectorheader->logicalsector) = 0; sectorheader->status = (uint8_t) ~(SMART_STATUS_COMMITTED | SMART_STATUS_VERBITS | SMART_STATUS_SIZEBITS) | SMART_STATUS_VERSION | sectsize; #else *((uint16_t *) sectorheader->logicalsector) = 0xFFFF; sectorheader->status = (uint8_t) (SMART_STATUS_COMMITTED | SMART_STATUS_VERSION | sectsize); #endif /* Now add the format signature to the sector */ dev->rwbuffer[SMART_FMT_POS1] = SMART_FMT_SIG1; dev->rwbuffer[SMART_FMT_POS2] = SMART_FMT_SIG2; dev->rwbuffer[SMART_FMT_POS3] = SMART_FMT_SIG3; dev->rwbuffer[SMART_FMT_POS4] = SMART_FMT_SIG4; dev->rwbuffer[SMART_FMT_VERSION_POS] = SMART_FMT_VERSION; dev->rwbuffer[SMART_FMT_NAMESIZE_POS] = CONFIG_SMARTFS_MAXNAMLEN; /* Record the number of root directory entries we have */ dev->rwbuffer[SMART_FMT_ROOTDIRS_POS] = (uint8_t) arg; /* Write the sector to the flash */ wrcount = MTD_BWRITE(dev->mtd, 0, dev->mtdBlksPerSector, (uint8_t *) dev->rwbuffer); if (wrcount != dev->mtdBlksPerSector) { /* The block is not empty!! What to do? */ fdbg("Write block 0 failed: %d.\n", wrcount); /* Unlock the mutex if we add one */ return -EIO; } /* Now initialize our internal control variables */ ret = smart_setsectorsize(dev, CONFIG_MTD_SMART_SECTOR_SIZE); if (ret != OK) { return ret; } dev->formatstatus = SMART_FMT_STAT_UNKNOWN; dev->freesectors = dev->neraseblocks * dev->sectorsPerBlk - 1; for (x = 0; x < dev->neraseblocks; x++) { /* Initialize the released and free counts */ dev->releasecount[x] = 0; dev->freecount[x] = dev->sectorsPerBlk; } /* Account for the format sector */ dev->freecount[0]--; /* Now initialize the logical to physical sector map */ dev->sMap[0] = 0; /* Logical sector zero = physical sector 0 */ totalsectors = dev->neraseblocks * dev->sectorsPerBlk; for (x = 1; x < totalsectors; x++) { /* Mark all other logical sectors as non-existant */ dev->sMap[x] = -1; } #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS /* Un-register any extra directory device entries */ for (x = 2; x < 8; x++) { snprintf(dev->rwbuffer, 18, "/dev/smart%dd%d", dev->minor, x); unregister_blockdriver(dev->rwbuffer); } #endif return OK; } #endif /* CONFIG_FS_WRITABLE */ /**************************************************************************** * Name: smart_findfreephyssector * * Description: Finds a free physical sector based on free and released * count logic, taking into account reserved sectors. * ****************************************************************************/ static int smart_findfreephyssector(struct smart_struct_s *dev) { uint16_t allocfreecount; uint16_t allocblock; uint16_t physicalsector; uint16_t x; uint32_t readaddr; struct smart_sect_header_s header; int ret; /* Determine which erase block we should allocate the new * sector from. This is based on the number of free sectors * available in each erase block. */ allocfreecount = 0; allocblock = 0xFFFF; physicalsector = 0xFFFF; for (x = 0; x < dev->neraseblocks; x++) { /* Test if this block has more free blocks than the * currently selected block */ if (dev->freecount[x] > allocfreecount) { /* Assign this block to alloc from */ allocblock = x; allocfreecount = dev->freecount[x]; } } /* Check if we found an allocblock. */ if (allocblock == 0xFFFF) { /* No free sectors found! Bug? */ return -EIO; } /* Now find a free physical sector within this selected * erase block to allocate. */ for (x = allocblock * dev->sectorsPerBlk; x < (allocblock+1) * dev->sectorsPerBlk; x++) { /* Check if this physical sector is available */ readaddr = x * dev->mtdBlksPerSector * dev->geo.blocksize; ret = MTD_READ(dev->mtd, readaddr, sizeof(struct smart_sect_header_s), (uint8_t *) &header); if (ret != sizeof(struct smart_sect_header_s)) { fvdbg("Error reading phys sector %d\n", physicalsector); return -EIO; } if ((*((uint16_t *) header.logicalsector) == 0xFFFF) && (*((uint16_t *) header.seq) == 0xFFFF) && ((header.status & SMART_STATUS_COMMITTED) == (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED))) { physicalsector = x; break; } } return physicalsector; } /**************************************************************************** * Name: smart_garbagecollect * * Description: Performs garbage collection if needed. This is determined * by the count of released sectors relative to free and * total sectors. * ****************************************************************************/ #ifdef CONFIG_FS_WRITABLE static int smart_garbagecollect(struct smart_struct_s *dev) { uint16_t releasedsectors; uint16_t collectblock; uint16_t releasemax; uint16_t newsector; bool collect = TRUE; int x; int ret; size_t offset; struct smart_sect_header_s *header; uint8_t newstatus; while (collect) { collect = FALSE; /* Calculate the number of released sectors on the device */ releasedsectors = 0; collectblock = 0xFFFF; releasemax = 0; for (x = 0; x < dev->neraseblocks; x++) { releasedsectors += dev->releasecount[x]; if (dev->releasecount[x] > releasemax) { releasemax = dev->releasecount[x]; collectblock = x; } } /* Test if the released sectors count is greater than the * free sectors. If it is, then we will do garbage collection. */ if (releasedsectors > dev->freesectors) collect = TRUE; /* Test if we have more reached our reserved free sector limit */ if (dev->freesectors <= (dev->sectorsPerBlk << 0) + 4) collect = TRUE; /* Test if we need to garbage collect */ if (collect) { if (collectblock == 0xFFFF) { /* Need to collect, but no sectors with released blocks! */ ret = -ENOSPC; goto errout; } fdbg("Collecting block %d, free=%d released=%d\n", collectblock, dev->freecount[collectblock], dev->releasecount[collectblock]); if (dev->freecount[collectblock] == 6) { fdbg("here!\n"); } /* Perform collection on block with the most released sectors. * First mark the block as having no free sectors so we don't * try to move sectors into the block we are trying to erase. */ dev->freecount[collectblock] = 0; /* Next move all live data in the block to a new home. */ for (x = collectblock * dev->sectorsPerBlk; x < (collectblock + 1) * dev->sectorsPerBlk; x++) { /* Read the next sector from this erase block */ ret = MTD_BREAD(dev->mtd, x * dev->mtdBlksPerSector, dev->mtdBlksPerSector, (uint8_t *) dev->rwbuffer); if (ret != dev->mtdBlksPerSector) { fdbg("Error reading sector %d\n", x); ret = -EIO; goto errout; } /* Test if if the block is in use */ header = (struct smart_sect_header_s *) dev->rwbuffer; if (((header->status & SMART_STATUS_COMMITTED) == (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED)) || ((header->status & SMART_STATUS_RELEASED) != (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_RELEASED))) { /* This sector doesn't have live data (free or released). * just continue to the next sector and don't move it. */ continue; } /* Find a new sector where it can live, NOT in this erase block */ newsector = smart_findfreephyssector(dev); if (newsector == 0xFFFF) { /* Unable to find a free sector!!! */ fdbg("Can't find a free sector for relocation\n"); ret = -EIO; goto errout; } /* Increment the sequence number and clear the "commit" flag */ (*((uint16_t *) header->seq))++; if (*((uint16_t *) header->seq) == 0xFFFF) { *((uint16_t *) header->seq) = 1; } #if CONFIG_SMARTFS_ERASEDSTATE == 0xFF header->status |= SMART_STATUS_COMMITTED; #else header->status &= ~SMART_STATUS_COMMITTED; #endif /* Write the data to the new physical sector location */ ret = MTD_BWRITE(dev->mtd, newsector * dev->mtdBlksPerSector, dev->mtdBlksPerSector, (uint8_t *) dev->rwbuffer); /* Commit the sector */ offset = newsector * dev->mtdBlksPerSector * dev->geo.blocksize + offsetof(struct smart_sect_header_s, status); #if CONFIG_SMARTFS_ERASEDSTATE == 0xFF newstatus = header->status & ~SMART_STATUS_COMMITTED; #else newstatus = header->status | SMART_STATUS_COMMITTED; #endif ret = smart_bytewrite(dev, offset, 1, &newstatus); if (ret < 0) { fdbg("Error %d committing new sector %d\n" -ret, newsector); goto errout; } /* Release the old physical sector */ #if CONFIG_SMARTFS_ERASEDSTATE == 0xFF newstatus = header->status & ~SMART_STATUS_RELEASED; #else newstatus = header->status | SMART_STATUS_RELEASED; #endif offset = x * dev->mtdBlksPerSector * dev->geo.blocksize + offsetof(struct smart_sect_header_s, status); ret = smart_bytewrite(dev, offset, 1, &newstatus); if (ret < 0) { fdbg("Error %d releasing old sector %d\n" -ret, x); goto errout; } /* Update the variables */ dev->sMap[*((uint16_t *) header->logicalsector)] = newsector; dev->freecount[newsector / dev->sectorsPerBlk]--; } /* Now erase the erase block */ MTD_ERASE(dev->mtd, collectblock, 1); dev->freesectors += dev->releasecount[collectblock]; dev->freecount[collectblock] = dev->sectorsPerBlk; dev->releasecount[collectblock] = 0; /* If this is block zero, then be sure to write the sector size */ if (collectblock == 0) { /* Set the sector size in the 1st header */ uint8_t sectsize = dev->sectorsize >> 7; #if ( CONFIG_SMARTFS_ERASEDSTATE == 0xFF ) newstatus = (uint8_t) ~SMART_STATUS_SIZEBITS | sectsize; #else newstatus = (uint8_t) sectsize; #endif /* Write the sector size to the device */ offset = offsetof(struct smart_sect_header_s, status); ret = smart_bytewrite(dev, offset, 1, &newstatus); if (ret < 0) { fdbg("Error %d setting sector 0 size\n", -ret); } } /* Update the block aging information in the format signature sector */ } else { /* Test for aging sectors and push them to a new location * so we wear evenly. */ } } return OK; errout: return ret; } #endif /* CONFIG_FS_WRITABLE */ /**************************************************************************** * Name: smart_writesector * * Description: Writes data to the specified logical sector. The sector * should have already been allocated prior to the write. If * the logical sector already has data on the device, it will * be released and a new physical sector will be created and * mapped to the logical sector. * ****************************************************************************/ #ifdef CONFIG_FS_WRITABLE static inline int smart_writesector(struct smart_struct_s *dev, unsigned long arg) { int ret; uint16_t x; bool needsrelocate = FALSE; uint32_t mtdblock; uint16_t physsector; struct smart_read_write_s *req; struct smart_sect_header_s *header; size_t offset; uint8_t byte; fvdbg("Entry\n"); req = (struct smart_read_write_s *) arg; DEBUGASSERT(req->offset <= dev->sectorsize); DEBUGASSERT(req->offset+req->count <= dev->sectorsize); /* Ensure the logical sector has been allocated */ if (req->logsector >= dev->totalsectors) { fdbg("Logical sector %d too large\n", req->logsector); ret = -EINVAL; goto errout; } physsector = dev->sMap[req->logsector]; if (physsector == 0xFFFF) { fdbg("Logical sector %d not allocated\n", req->logsector); ret = -EINVAL; goto errout; } /* Read the sector data into our buffer */ mtdblock = physsector * dev->mtdBlksPerSector; ret = MTD_BREAD(dev->mtd, mtdblock, dev->mtdBlksPerSector, (uint8_t *) dev->rwbuffer); if (ret != dev->mtdBlksPerSector) { fdbg("Error reading phys sector %d\n", physsector); ret = -EIO; goto errout; } /* Test if we need to relocate the sector to perform the write */ for (x = 0; x < req->count; x++) { /* Test if the next byte can be written to the flash */ byte = dev->rwbuffer[sizeof(struct smart_sect_header_s) + req->offset + x]; #if CONFIG_SMARTFS_ERASEDSTATE == 0xFF if (((byte ^ req->buffer[x]) | byte) != byte) { needsrelocate = TRUE; break; } #else if (((byte ^ req->buffer[x]) | req->buffer[x]) != req->buffer[x]) { needsrelocate = TRUE; break; } #endif } if (needsrelocate) { /* Find a new physical sector to save data to */ physsector = smart_findfreephyssector(dev); if (physsector == 0xFFFF) { fdbg("Error relocating sector %d\n", req->logsector); ret = -EIO; goto errout; } /* Update the sequence number to indicate the sector was moved */ header = (struct smart_sect_header_s *) dev->rwbuffer; (*((uint16_t *) header->seq))++; #if CONFIG_SMARTFS_ERASEDSTATE == 0xFF header->status |= SMART_STATUS_COMMITTED; #else header->status &= SMART_STATUS_COMMITTED; #endif } /* Now copy the data to the sector buffer. */ memcpy(&dev->rwbuffer[sizeof(struct smart_sect_header_s) + req->offset], req->buffer, req->count); /* Now write the sector buffer to the device. */ if (needsrelocate) { /* Write the entire sector to the new physical location, uncommitted. */ ret = MTD_BWRITE(dev->mtd, physsector * dev->mtdBlksPerSector, dev->mtdBlksPerSector, (uint8_t *) dev->rwbuffer); if (ret != dev->mtdBlksPerSector) { fdbg("Error writing to physical sector %d\n", physsector); ret = -EIO; goto errout; } /* Commit the new physical sector */ #if CONFIG_SMARTFS_ERASEDSTATE == 0xFF byte = header->status & ~SMART_STATUS_COMMITTED; #else byte = header->status | SMART_STATUS_COMMITTED; #endif offset = physsector * dev->mtdBlksPerSector * dev->geo.blocksize + offsetof(struct smart_sect_header_s, status); ret = smart_bytewrite(dev, offset, 1, &byte); if (ret != 1) { fvdbg("Error committing physical sector %d\n", physsector); ret = -EIO; goto errout; } /* Release the old physical sector */ #if CONFIG_SMARTFS_ERASEDSTATE == 0xFF byte = header->status & ~SMART_STATUS_RELEASED; #else byte = header->status | SMART_STATUS_RELEASED; #endif offset = mtdblock * dev->geo.blocksize + offsetof(struct smart_sect_header_s, status); ret = smart_bytewrite(dev, offset, 1, &byte); /* Update releasecount for released sector and freecount for the * newly allocated physical sector. */ dev->releasecount[dev->sMap[req->logsector] / dev->sectorsPerBlk]++; dev->freecount[physsector / dev->sectorsPerBlk]--; dev->freesectors--; /* Update the sector map */ dev->sMap[req->logsector] = physsector; /* Since we performed a relocation, do garbage collection to * ensure we don't fill up our flash with released blocks. */ smart_garbagecollect(dev); } else { /* Not relocated. Just write the portion of the sector that needs * to be written. */ offset = mtdblock * dev->geo.blocksize + sizeof(struct smart_sect_header_s) + req->offset; ret = smart_bytewrite(dev, offset, req->count, req->buffer); } ret = OK; errout: return ret; } #endif /* CONFIG_FS_WRITABLE */ /**************************************************************************** * Name: smart_readsector * * Description: Reads data from the specified logical sector. The sector * should have already been allocated prior to the read. * ****************************************************************************/ static inline int smart_readsector(struct smart_struct_s *dev, unsigned long arg) { int ret; uint32_t readaddr; uint16_t physsector; struct smart_read_write_s *req; struct smart_sect_header_s header; fvdbg("Entry\n"); req = (struct smart_read_write_s *) arg; DEBUGASSERT(req->offset < dev->sectorsize); DEBUGASSERT(req->offset+req->count < dev->sectorsize); /* Ensure the logical sector has been allocated */ if (req->logsector >= dev->totalsectors) { fdbg("Logical sector %d too large\n", req->logsector); ret = -EINVAL; goto errout; } physsector = dev->sMap[req->logsector]; if (physsector == 0xFFFF) { fdbg("Logical sector %d not allocated\n", req->logsector); ret = -EINVAL; goto errout; } /* Read the sector header data to validate as a sanity check */ ret = MTD_READ(dev->mtd, physsector * dev->mtdBlksPerSector * dev->geo.blocksize, sizeof(struct smart_sect_header_s), (uint8_t *) &header); if (ret != sizeof(struct smart_sect_header_s)) { fvdbg("Error reading sector %d header\n", physsector); ret = -EIO; goto errout; } /* Do a sanity check on the header data */ if (((*(uint16_t *) header.logicalsector) != req->logsector) || ((header.status & SMART_STATUS_COMMITTED) == (CONFIG_SMARTFS_ERASEDSTATE & SMART_STATUS_COMMITTED))) { /* Error in sector header! How do we handle this? */ fdbg("Error in logical sector %d header, phys=%d\n", req->logsector, physsector); ret = -EIO; goto errout; } /* Read the sector data into the buffer */ readaddr = (uint32_t) physsector * dev->mtdBlksPerSector * dev->geo.blocksize + req->offset + sizeof(struct smart_sect_header_s);; ret = MTD_READ(dev->mtd, readaddr, req->count, (uint8_t *) req->buffer); if (ret != req->count) { fdbg("Error reading phys sector %d\n", physsector); ret = -EIO; goto errout; } errout: return ret; } /**************************************************************************** * Name: smart_allocsector * * Description: Allocates a new logical sector. If an argument is given, * then it tries to allocate the specified sector number. * ****************************************************************************/ #ifdef CONFIG_FS_WRITABLE static inline int smart_allocsector(struct smart_struct_s *dev, unsigned long requested) { int x; int ret; uint16_t logsector = 0xFFFF; /* Logical sector number selected */ uint16_t physicalsector; /* The selected physical sector */ uint16_t releasecount; struct smart_sect_header_s *header; uint8_t sectsize; /* Validate that we have enough sectors available to perform an * allocation. We have to ensure we keep enough reserved sectors * on hand to do released sector garbage collection. */ releasecount = 0; for (x = 0; x < dev->neraseblocks; x++) { releasecount += dev->releasecount[x]; } if (dev->freesectors <= (dev->sectorsPerBlk << 0) + 4) { /* We are at our free sector limit. Test if we have * sectors we can release */ if (releasecount == 0) { /* No space left!! */ return -ENOSPC; } } /* Check if a specific sector is being requested and allocate that * sector if it isn't already in use */ if ((requested > 2) && (requested < dev->totalsectors)) { /* Validate the sector is not already allocated */ if (dev->sMap[requested] == (uint16_t) -1) { logsector = requested; } } /* Check if we need to scan for an available logical sector */ if (logsector == 0xFFFF) { /* Loop through all sectors and find one to allocate */ for (x = SMART_FIRST_ALLOC_SECTOR; x < dev->totalsectors; x++) { if (dev->sMap[x] == (uint16_t) -1) { /* Unused logical sector found. Use this one */ logsector = x; break; } } } /* Test for an error allocating a sector */ if (logsector == 0xFFFF) { /* Hmmm. We think we had enough logical sectors, but * something happened and we didn't find any free * logical sectors. What do do? Report an error? * rescan and try again to "self heal" in case of a * bug in our code? */ fdbg("No free logical sector numbers! Free sectors = %d\n", dev->freesectors); return -EIO; } /* Check if we need to do garbage collection. We have to * ensure we keep enough reserved free sectors to per garbage * collection as it involves moving sectors from blocks with * released sectors into blocks with free sectors, then * erasing the vacated block. */ smart_garbagecollect(dev); /* Find a free physical sector */ physicalsector = smart_findfreephyssector(dev); fvdbg("Alloc: log=%d, phys=%d, erase block=%d, free=%d, released=%d\n", logsector, physicalsector, physicalsector / dev->sectorsPerBlk, dev->freesectors, releasecount); /* Create a header to assign the logical sector */ memset(dev->rwbuffer, CONFIG_SMARTFS_ERASEDSTATE, dev->sectorsize); header = (struct smart_sect_header_s *) dev->rwbuffer; *((uint16_t *) header->logicalsector) = logsector; *((uint16_t *) header->seq) = 0; sectsize = dev->sectorsize >> 7; #if CONFIG_SMARTFS_ERASEDSTATE == 0xFF header->status = ~(SMART_STATUS_COMMITTED | SMART_STATUS_SIZEBITS | SMART_STATUS_VERBITS) | SMART_STATUS_VERSION | sectsize; #else header->status = SMART_STATUS_COMMITTED | SMART_STATUS_VERSION | sectsize; #endif /* Write the header to the physical sector location */ x = physicalsector * dev->mtdBlksPerSector; fvdbg("Write MTD block %d\n", x); ret = MTD_BWRITE(dev->mtd, x, 1, (uint8_t *) dev->rwbuffer); if (ret != 1) { /* The block is not empty!! What to do? */ fdbg("Write block %d failed: %d.\n", x, ret); /* Unlock the mutex if we add one */ return -EIO; } /* Map the sector and update the free sector counts */ dev->sMap[logsector] = physicalsector; dev->freecount[physicalsector / dev->sectorsPerBlk]--; dev->freesectors--; /* Return the logical sector number */ return logsector; } #endif /* CONFIG_FS_WRITABLE */ /**************************************************************************** * Name: smart_freesector * * Description: Frees a logical sector from the device. Freeing (also * called releasing) is performed by programming the released * bit in the sector header's status byte. * ****************************************************************************/ #ifdef CONFIG_FS_WRITABLE static inline int smart_freesector(struct smart_struct_s *dev, unsigned long logicalsector) { int ret; int readaddr; uint16_t physsector; uint16_t block; struct smart_sect_header_s header; size_t offset; /* Check if the logical sector is within bounds */ if ((logicalsector > 2) && (logicalsector < dev->totalsectors)) { /* Validate the sector is actually allocated */ if (dev->sMap[logicalsector] == (uint16_t) -1) { fdbg("Invalid release - sector %d not allocated\n", logicalsector); ret = -EINVAL; goto errout; } } /* Okay to release the sector. Read the sector header info */ physsector = dev->sMap[logicalsector]; readaddr = physsector * dev->mtdBlksPerSector * dev->geo.blocksize; ret = MTD_READ(dev->mtd, readaddr, sizeof(struct smart_sect_header_s), (uint8_t *) &header); if (ret != sizeof(struct smart_sect_header_s)) { goto errout; } /* Do a sanity check on the logical sector number */ if (*((uint16_t *) header.logicalsector) != (uint16_t) logicalsector) { /* Hmmm... something is wrong. This should always match! Bug in our code? */ fdbg("Sector %d logical sector in header doesn't match\n", logicalsector); ret = -EINVAL; goto errout; } /* Mark the sector as released */ #if CONFIG_SMARTFS_ERASEDSTATE == 0xFF header.status &= ~SMART_STATUS_RELEASED; #else header.status |= SMART_STATUS_RELEASED; #endif /* Write the status back to the device */ offset = readaddr + offsetof(struct smart_sect_header_s, status); ret = smart_bytewrite(dev, offset, 1, &header.status); if (ret != 1) { fdbg("Error updating physicl sector %d status\n", physsector); goto errout; } /* Update the erase block's release count */ block = physsector / dev->sectorsPerBlk; dev->releasecount[block]++; /* Unmap this logical sector */ dev->sMap[logicalsector] = (uint16_t) -1; /* If this block has only released blocks, then erase it */ if (dev->releasecount[block] + dev->freecount[block] == dev->sectorsPerBlk) { /* Erase the block */ MTD_ERASE(dev->mtd, block, 1); dev->freesectors += dev->releasecount[block]; dev->releasecount[block] = 0; dev->freecount[block] = dev->sectorsPerBlk; } ret = OK; errout: return ret; } #endif /* CONFIG_FS_WRITABLE */ /**************************************************************************** * Name: smart_ioctl * * Description: Return device geometry * ****************************************************************************/ static int smart_ioctl(FAR struct inode *inode, int cmd, unsigned long arg) { struct smart_struct_s *dev ; int ret; fvdbg("Entry\n"); DEBUGASSERT(inode && inode->i_private); #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS dev = ((struct smart_multiroot_device_s*) inode->i_private)->dev; #else dev = (struct smart_struct_s *)inode->i_private; #endif /* Process the ioctl's we care about first, pass any we don't respond * to directly to the underlying MTD device. */ switch (cmd) { case BIOC_XIPBASE: /* The argument accompanying the BIOC_XIPBASE should be non-NULL. If * DEBUG is enabled, we will catch it here instead of in the MTD * driver. */ #ifdef CONFIG_DEBUG if (arg == 0) { fdbg("ERROR: BIOC_XIPBASE argument is NULL\n"); return -EINVAL; } #endif /* Just change the BIOC_XIPBASE command to the MTDIOC_XIPBASE command. */ cmd = MTDIOC_XIPBASE; break; case BIOC_GETFORMAT: /* Return the format information for the device */ #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS ret = smart_getformat(dev, (struct smart_format_s *) arg, ((struct smart_multiroot_device_s*) inode->i_private)->rootdirnum); #else ret = smart_getformat(dev, (struct smart_format_s *) arg); #endif goto ok_out; case BIOC_READSECT: /* Do a logical sector read and return the data */ ret = smart_readsector(dev, arg); goto ok_out; #ifdef CONFIG_FS_WRITABLE case BIOC_LLFORMAT: /* Perform a low-level format on the flash */ ret = smart_llformat(dev, arg); goto ok_out; case BIOC_ALLOCSECT: /* Allocate a logical sector for the upper layer file system */ ret = smart_allocsector(dev, arg); goto ok_out; case BIOC_FREESECT: /* Free the specified logical sector */ ret = smart_freesector(dev, arg); goto ok_out; case BIOC_WRITESECT: /* Write to the sector */ ret = smart_writesector(dev, arg); goto ok_out; #endif /* CONFIG_FS_WRITABLE */ } /* No other block driver ioctl commmands are not recognized by this * driver. Other possible MTD driver ioctl commands are passed through * to the MTD driver (unchanged). */ ret = MTD_IOCTL(dev->mtd, cmd, arg); if (ret < 0) { fdbg("ERROR: MTD ioctl(%04x) failed: %d\n", cmd, ret); } ok_out: return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: smart_initialize * * Description: * Initialize to provide a block driver wrapper around an MTD interface * * Input Parameters: * minor - The minor device number. The MTD block device will be * registered as as /dev/smartN where N is the minor number. * mtd - The MTD device that supports the FLASH interface. * ****************************************************************************/ int smart_initialize(int minor, FAR struct mtd_dev_s *mtd, const char *partname) { struct smart_struct_s *dev; int ret = -ENOMEM; uint32_t totalsectors; #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS struct smart_multiroot_device_s *rootdirdev; #endif /* Sanity check */ #ifdef CONFIG_DEBUG if (minor < 0 || minor > 255 || !mtd) { return -EINVAL; } #endif /* Allocate a SMART device structure */ dev = (struct smart_struct_s *)kmalloc(sizeof(struct smart_struct_s)); if (dev) { /* Initialize the SMART device structure */ dev->mtd = mtd; /* Get the device geometry. (casting to uintptr_t first eliminates * complaints on some architectures where the sizeof long is different * from the size of a pointer). */ /* Set these to zero in case the device doesn't support them */ ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&dev->geo)); if (ret < 0) { fdbg("MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret); kfree(dev); goto errout; } /* Set the sector size to the default for now */ dev->sMap = NULL; dev->rwbuffer = NULL; ret = smart_setsectorsize(dev, CONFIG_MTD_SMART_SECTOR_SIZE); if (ret != OK) { kfree(dev); goto errout; } /* Calculate the totalsectors on this device and validate */ totalsectors = dev->neraseblocks * dev->sectorsPerBlk; if (totalsectors > 65534) { fdbg("SMART Sector size too small for device\n"); kfree(dev); ret = -EINVAL; goto errout; } dev->freesectors = (uint16_t) totalsectors; /* Mark the device format status an unknown */ dev->formatstatus = SMART_FMT_STAT_UNKNOWN; dev->namesize = CONFIG_SMARTFS_MAXNAMLEN; if (partname) { strncpy(dev->partname, partname, SMART_PARTNAME_SIZE); } else { dev->partname[0] = '\0'; } #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS dev->minor = minor; #endif /* Create a MTD block device name */ #ifdef CONFIG_SMARTFS_MULTI_ROOT_DIRS if (partname != NULL) { snprintf(dev->rwbuffer, 18, "/dev/smart%d%sd1", minor, partname); } else { snprintf(dev->rwbuffer, 18, "/dev/smart%dd1", minor); } /* Inode private data is a reference to a struct containing * the SMART device structure and the root directory number. */ rootdirdev = (struct smart_multiroot_device_s*) kmalloc(sizeof(*rootdirdev)); if (rootdirdev == NULL) { fdbg("register_blockdriver failed: %d\n", -ret); kfree(dev->sMap); kfree(dev->rwbuffer); kfree(dev); ret = -ENOMEM; goto errout; } /* Populate the rootdirdev */ rootdirdev->dev = dev; rootdirdev->rootdirnum = 0; ret = register_blockdriver(dev->rwbuffer, &g_bops, 0, rootdirdev); #else if (partname != NULL) { snprintf(dev->rwbuffer, 18, "/dev/smart%d%s", minor, partname); } else { snprintf(dev->rwbuffer, 18, "/dev/smart%d", minor); } /* Inode private data is a reference to the SMART device structure */ ret = register_blockdriver(dev->rwbuffer, &g_bops, 0, dev); #endif if (ret < 0) { fdbg("register_blockdriver failed: %d\n", -ret); kfree(dev->sMap); kfree(dev->rwbuffer); kfree(dev); goto errout; } /* Do a scan of the device */ smart_scan(dev); } errout: return ret; }