diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index a036d03559..1d56a89607 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -26,6 +26,27 @@ config MTD_PARTITION managing the sub-region of flash beginning at 'offset' (in blocks) and of size 'nblocks' on the device specified by 'mtd'. +config MTD_SECT512 + bool "512B sector conversion" + default n + ---help--- + If enabled, a MTD driver will be created that will convert the + sector size of any other MTD driver to a 512 byte "apparent" sector + size. The managed MTD driver in this case must have an erase block + size that is greater than 512B and an event multiple of 512B. + +if MTD_SECT512 + +config MTD_SECT512_ERASED_STATE + hex "Erased state of the FLASH" + default 0xff + +config MTD_SECT512_READONLY + bool "512B read-only" + default n + +endif # MTD_SECT512 + config MTD_PARTITION_NAMES bool "Support MTD partition naming" depends on FS_PROCFS diff --git a/drivers/mtd/Make.defs b/drivers/mtd/Make.defs index 7185081850..c7d8ea18d6 100644 --- a/drivers/mtd/Make.defs +++ b/drivers/mtd/Make.defs @@ -45,6 +45,10 @@ ifeq ($(CONFIG_MTD_PARTITION),y) CSRCS += mtd_partition.c endif +ifeq ($(CONFIG_MTD_SECT512),y) +CSRCS += sector512.c +endif + ifeq ($(CONFIG_MTD_NAND),y) CSRCS += mtd_nand.c mtd_onfi.c mtd_nandscheme.c mtd_nandmodel.c mtd_modeltab.c ifeq ($(CONFIG_MTD_NAND_SWECC),y) diff --git a/drivers/mtd/sector512.c b/drivers/mtd/sector512.c new file mode 100644 index 0000000000..80dfc91067 --- /dev/null +++ b/drivers/mtd/sector512.c @@ -0,0 +1,648 @@ +/************************************************************************************ + * drivers/mtd/sector512.c + * MTD driver that contains another MTD driver and converts a larger sector size + * to a standard 512 byte sector size. + * + * Copyright (C) 2014 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * 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 + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ +/* Configuration */ + +#ifndef CONFIG_MTD_SECT512_ERASED_STATE +# define CONFIG_MTD_SECT512_ERASED_STATE 0xff +#endif + +/* 512-byte sector constants */ + +#define SECTOR_512 512 +#define SHIFT_512 9 +#define MASK_512 511 + +/* Cache flags */ + +#define SST25_CACHE_VALID (1 << 0) /* 1=Cache has valid data */ +#define SST25_CACHE_DIRTY (1 << 1) /* 1=Cache is dirty */ +#define SST25_CACHE_ERASED (1 << 2) /* 1=Backing FLASH is erased */ + +#define IS_VALID(p) ((((p)->flags) & SST25_CACHE_VALID) != 0) +#define IS_DIRTY(p) ((((p)->flags) & SST25_CACHE_DIRTY) != 0) +#define IS_ERASED(p) ((((p)->flags) & SST25_CACHE_DIRTY) != 0) + +#define SET_VALID(p) do { (p)->flags |= SST25_CACHE_VALID; } while (0) +#define SET_DIRTY(p) do { (p)->flags |= SST25_CACHE_DIRTY; } while (0) +#define SET_ERASED(p) do { (p)->flags |= SST25_CACHE_DIRTY; } while (0) + +#define CLR_VALID(p) do { (p)->flags &= ~SST25_CACHE_VALID; } while (0) +#define CLR_DIRTY(p) do { (p)->flags &= ~SST25_CACHE_DIRTY; } while (0) +#define CLR_ERASED(p) do { (p)->flags &= ~SST25_CACHE_DIRTY; } while (0) + +/************************************************************************************ + * Private Types + ************************************************************************************/ + +/* This type represents the state of the MTD device. The struct mtd_dev_s must + * appear at the beginning of the definition so that you can freely cast between + * pointers to struct mtd_dev_s and struct s512_dev_s. + */ + +struct s512_dev_s +{ + struct mtd_dev_s mtd; /* MTD interface */ + FAR struct mtd_dev_s *dev; /* Saved lower level MTD interface instance */ + uint32_t eblocksize; /* Size of one erase block */ + size_t neblocks; /* Number of erase blocks */ + size_t sectperblock; /* Number of read/write sectors per erase block */ + uint16_t stdperblock; /* Number of 512 byte sectors in one erase block */ + uint8_t flags; /* Buffered sector flags */ + uint32_t eblockno; /* Erase sector number in the cache*/ + FAR uint8_t *eblock; /* Allocated erase block */ +}; + +/************************************************************************************ + * Private Function Prototypes + ************************************************************************************/ + +/* Helpers */ + +static FAR uint8_t *s512_cacheread(struct s512_dev_s *priv, off_t sector); +#ifndef CONFIG_MTD_SECT512_READONLY +static void s512_cacheflush(struct s512_dev_s *priv); +#endif + +/* MTD driver methods */ + +static int s512_erase(FAR struct mtd_dev_s *dev, off_t sector512, size_t nsectors); +static ssize_t s512_bread(FAR struct mtd_dev_s *dev, off_t sector512, + size_t nsectors, FAR uint8_t *buf); +static ssize_t s512_bwrite(FAR struct mtd_dev_s *dev, off_t sector512, + size_t nsectors, FAR const uint8_t *buf); +static ssize_t s512_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer); +static int s512_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); + +/************************************************************************************ + * Private Data + ************************************************************************************/ + +/************************************************************************************ + * Private Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: s512_cacheread + ************************************************************************************/ + +static FAR uint8_t *s512_cacheread(struct s512_dev_s *priv, off_t sector512) +{ + off_t eblockno; + off_t sector; + ssize_t result; + int index; + + /* Get the erase block containing this sector */ + + eblockno = sector512 / priv->stdperblock; + fvdbg("sector512: %lu eblockno: %lu\n", + (unsigned long)sector512, (unsigned long)eblockno); + + /* Check if the requested erase block is already in the cache */ + + if (!IS_VALID(priv) || eblockno != priv->eblockno) + { + /* No.. Flush any dirty erase block currently in the cache */ + + s512_cacheflush(priv); + + /* Read the erase block into the cache */ + + sector = eblockno * priv->sectperblock; + result = priv->dev->bread(priv->dev, sector, priv->sectperblock, + priv->eblock); + if (result < 0) + { + fdbg("ERROR: bread(%lu, %lu) returned %ld\n", + (unsigned long)sector, (unsigned long)priv->eblocksize, + (long)result); + + return NULL; + } + + /* Mark the sector as cached */ + + priv->eblockno = eblockno; + + SET_VALID(priv); /* The data in the cache is valid */ + CLR_DIRTY(priv); /* It should match the FLASH contents */ + CLR_ERASED(priv); /* The underlying FLASH has not been erased */ + } + + /* Get the index to the 512 sector in the erase block that holds the argument */ + + index = sector512 % priv->stdperblock; + + /* Return the address in the cache that holds this sector */ + + return &priv->eblock[index << SHIFT_512]; +} + +/************************************************************************************ + * Name: s512_cacheflush + ************************************************************************************/ + +#if !defined(CONFIG_MTD_SECT512_READONLY) +static void s512_cacheflush(struct s512_dev_s *priv) +{ + off_t sector; + ssize_t result; + + /* If the cached is dirty (meaning that it no longer matches the old FLASH contents) + * or was erased (with the cache containing the correct FLASH contents), then write + * the cached erase block to FLASH. + */ + + if (IS_DIRTY(priv) || IS_ERASED(priv)) + { + /* Write entire erase block to FLASH */ + + sector = priv->eblockno * priv->sectperblock; + result = priv->dev->bwrite(priv->dev, sector, priv->sectperblock, priv->eblock); + if (result < 0) + { + fdbg("ERROR: bwrite(%lu, %lu) returned %ld\n", + (unsigned long)sector, (unsigned long)priv->eblocksize, + (long)result); + + return; + } + + /* The cache is no long dirty and the FLASH is no longer erased */ + + CLR_DIRTY(priv); + CLR_ERASED(priv); + } +} +#endif + +/************************************************************************************ + * Name: s512_erase + ************************************************************************************/ + +static int s512_erase(FAR struct mtd_dev_s *dev, off_t sector512, size_t nsectors) +{ +#ifdef CONFIG_MTD_SECT512_READONLY + return -EACESS +#else + FAR struct s512_dev_s *priv = (FAR struct s512_dev_s *)dev; + FAR uint8_t *dest; + size_t sectorsleft = nsectors; + size_t eblockno; + int ret; + + fvdbg("sector512: %08lx nsectors: %lu\n", + (unsigned long)sector512, (unsigned int)nsectors); + + while (sectorsleft-- > 0) + { + /* Erase each sector. First, make sure that the erase block containing the + * 512 byte sector is in the cache. + */ + + dest = s512_cacheread(priv, sector512); + if (!dest) + { + fdbg("ERROR: s512_cacheread(%ul) failed\n", (unsigned long)sector512); + DEBUGPANIC(); + return -EIO; + } + + /* Erase the block containing this sector if it is not already erased. + * The erased indicator will be cleared when the data from the erase sector + * is read into the cache and set here when we erase the block. + */ + + if (!IS_ERASED(priv)) + { + eblockno = sector512 / priv->stdperblock; + fvdbg("sector512: %lu eblockno: %lu\n", + (unsigned long)sector512, (unsigned long)eblockno); + + ret = priv->dev->erase(priv->dev, eblockno, 1); + if (ret < 0) + { + fdbg("ERROR: Failed to erase block %lu: %d\n", + (unsigned long)eblockno, ret); + return ret; + } + + SET_ERASED(priv); + } + + /* Put the cached sector data into the erase state and mark the cache + * as dirty (but don't update the FLASH yet. The caller will do that + * at a more optimal time). + */ + + memset(dest, CONFIG_MTD_SECT512_ERASED_STATE, SECTOR_512); + SET_DIRTY(priv); + sector512++; + } + + /* Flush the last erase block left in the cache */ + + s512_cacheflush(priv); + + return (int)nsectors; +#endif +} + +/************************************************************************************ + * Name: s512_bread + ************************************************************************************/ + +static ssize_t s512_bread(FAR struct mtd_dev_s *dev, off_t sector512, + size_t nsectors, FAR uint8_t *buffer) +{ + FAR struct s512_dev_s *priv = (FAR struct s512_dev_s *)dev; + FAR uint8_t *src; + ssize_t remaining; + ssize_t result = nsectors; + + fvdbg("sector512: %08lx nsectors: %d\n", (long)sector512, (int)nsectors); + + /* Read each 512 byte sector from the block via the erase block cache */ + + for (remaining = nsectors; remaining; remaining--) + { + /* Make sure that the next sector is in the erase block cache */ + + src = s512_cacheread(priv, sector512); + if (!src) + { + fdbg("ERROR: s512_cacheread(%ul) failed\n", (unsigned long)sector512); + DEBUGPANIC(); + + result = (ssize_t)nsectors - remaining; + if (result <= 0) + { + result = -EIO; + } + + break; + } + + /* Copy the sector data from the erase block cache into the user buffer */ + + memcpy(buffer, src, SECTOR_512); + + buffer += SECTOR_512; + sector512++; + } + + return result; +} + +/************************************************************************************ + * Name: s512_bwrite + ************************************************************************************/ + +static ssize_t s512_bwrite(FAR struct mtd_dev_s *dev, off_t sector512, size_t nsectors, + FAR const uint8_t *buffer) +{ +#ifdef CONFIG_MTD_SECT512_READONLY + return -EACCESS; +#else + FAR struct s512_dev_s *priv = (FAR struct s512_dev_s *)dev; + ssize_t remaining; + ssize_t result; + off_t eblockno; + + fvdbg("sector512: %08lx nsectors: %d\n", (long)sector512, (int)nsectors); + + FAR uint8_t *dest; + + for (remaining = nsectors; remaining > 0; remaining--) + { + /* First, make sure that the erase block containing 512 byte sector is in + * memory. + */ + + dest = s512_cacheread(priv, sector512); + if (!dest) + { + result = (ssize_t)nsectors - remaining; + if (result <= 0) + { + result = -EIO; + } + + return result; + } + + /* Erase the block containing this sector if it is not already erased. + * The erased indicated will be cleared when the data from the erase sector + * is read into the cache and set here when we erase the sector. + */ + + if (!IS_ERASED(priv)) + { + eblockno = sector512 / priv->stdperblock; + fvdbg("sector512: %lu eblockno: %lu\n", + (unsigned long)sector512, (unsigned long)eblockno); + + result = priv->dev->erase(priv->dev, eblockno, 1); + if (result < 0) + { + fdbg("ERROR: Failed to erase block %lu: %ld\n", + (unsigned long)eblockno, (long)result); + return result; + } + + SET_ERASED(priv); + } + + /* Copy the new sector data into cached erase block */ + + memcpy(dest, buffer, SECTOR_512); + SET_DIRTY(priv); + + /* Set up for the next 512 byte sector */ + + buffer += SECTOR_512; + sector512++; + } + + /* Flush the last erase block left in the cache */ + + s512_cacheflush(priv); + return nsectors; +#endif +} + +/************************************************************************************ + * Name: s512_read + ************************************************************************************/ + +static ssize_t s512_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, + FAR uint8_t *buffer) +{ + FAR struct s512_dev_s *priv = (FAR struct s512_dev_s *)dev; + FAR uint8_t *src; + ssize_t remaining; + ssize_t xfrsize; + off_t sectoffset; + off_t sector; + + fvdbg("offset: %08lx nbytes: %lu\n", + (unsigned long)offset, (unsigned long)nbytes); + + /* Convert the offset into 512 byte sector address and a byte offset */ + + sectoffset = offset & MASK_512; + sector = offset >> SHIFT_512; + + for (remaining = nbytes; remaining > 0; remaining -= xfrsize) + { + /* Read the erase block into the cache and get the address of the + * beginning of the 512 byte block in the cached erase block. + */ + + src = s512_cacheread(priv, sector); + if (!src) + { + int result; + + fdbg("ERROR: s512_cacheread(%ul) failed\n", (unsigned long)sector); + DEBUGPANIC(); + + result = (ssize_t)nbytes - remaining; + if (result <= 0) + { + result = -EIO; + } + + return result; + } + + /* Then copy the requested bytes from the cached erase block */ + + xfrsize = remaining; + if (sectoffset + xfrsize > SECTOR_512) + { + xfrsize = SECTOR_512 - sectoffset; + } + + memcpy(buffer, src + sectoffset, xfrsize); + buffer += xfrsize; + } + + fvdbg("return nbytes: %d\n", (int)nbytes); + return nbytes; +} + +/************************************************************************************ + * Name: s512_ioctl + ************************************************************************************/ + +static int s512_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) +{ + FAR struct s512_dev_s *priv = (FAR struct s512_dev_s *)dev; + int ret = -EINVAL; /* Assume good command with bad parameters */ + + fvdbg("cmd: %d \n", cmd); + + switch (cmd) + { + case MTDIOC_GEOMETRY: + { + FAR struct mtd_geometry_s *geo = (FAR struct mtd_geometry_s *)((uintptr_t)arg); + if (geo) + { + /* Populate the geometry structure with information need to know + * the capacity and how to access the device. + * + * NOTE: that the device is treated as though it where just an array + * of fixed size blocks. That is most likely not true, but the client + * will expect the device logic to do whatever is necessary to make it + * appear so. + */ + + geo->blocksize = SECTOR_512; + geo->erasesize = SECTOR_512; + geo->neraseblocks = priv->neblocks * priv->stdperblock; + ret = OK; + + fvdbg("blocksize: %d erasesize: %d neraseblocks: %d\n", + geo->blocksize, geo->erasesize, geo->neraseblocks); + } + } + break; + + case MTDIOC_BULKERASE: + { + /* Erase the entire device */ + + ret = priv->dev->ioctl(priv->dev, MTDIOC_BULKERASE, 0); + if (ret >= 0) + { + priv->flags = 0; /* Buffered sector flags */ + priv->eblockno = 0; /* Erase sector number in the cache*/ + priv->eblock = NULL; /* Allocated erase block */ + } + } + break; + + case MTDIOC_XIPBASE: + default: + ret = -ENOTTY; /* Bad command */ + break; + } + + fvdbg("return %d\n", ret); + return ret; +} + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +/************************************************************************************ + * Name: s512_initialize + * + * Description: + * Create an initialized MTD device instance. This MTD driver contains another + * MTD driver and converts a larger sector size to a standard 512 byte sector + * size. + * + * MTD devices are not registered in the file system, but are created as instances + * that can be bound to other functions (such as a block or character driver front + * end). + * + ************************************************************************************/ + +FAR struct mtd_dev_s *s512_initialize(FAR struct mtd_dev_s *mtd) +{ + FAR struct s512_dev_s *priv; + FAR struct mtd_geometry_s geo; + int ret; + + fvdbg("mtd: %p\n", mtd); + + /* Get the device geometry */ + + DEBUGASSERT(mtd && mtd->ioctl); + ret = mtd->ioctl(mtd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&geo)); + + /* We expect that the block size will be >512 and an even multiple of 512 */ + + if (ret < 0 || geo.erasesize <= SECTOR_512 || + (geo.erasesize & ~MASK_512) != geo.erasesize ) + { + fdbg("ERROR: MTDIOC_GEOMETRY ioctl returned %d, eraseize=%d\n", + ret, geo.erasesize); + DEBUGPANIC(); + return NULL; + } + + /* Allocate a state structure (we allocate the structure instead of using + * a fixed, static allocation so that we can handle multiple FLASH devices. + * The current implementation would handle only one FLASH part per SPI + * device (only because of the SPIDEV_FLASH definition) and so would have + * to be extended to handle multiple FLASH parts on the same SPI bus. + */ + + priv = (FAR struct s512_dev_s *)kzalloc(sizeof(struct s512_dev_s)); + if (priv) + { + /* Initialize the allocated structure. (unsupported methods/fields + * were already nullified by kzalloc). + */ + + priv->mtd.erase = s512_erase; + priv->mtd.bread = s512_bread; + priv->mtd.bwrite = s512_bwrite; + priv->mtd.read = s512_read; + priv->mtd.ioctl = s512_ioctl; + + priv->dev = mtd; + priv->eblocksize = geo.erasesize; + priv->neblocks = geo.neraseblocks; + priv->sectperblock = geo.erasesize / geo.blocksize; + priv->stdperblock = geo.erasesize >> 9; + + /* Allocate a buffer for the erase block cache */ + + priv->eblock = (FAR uint8_t *)kmalloc(priv->eblocksize); + if (!priv->eblock) + { + /* Allocation failed! Discard all of that work we just did and return NULL */ + + fdbg("Allocation failed\n"); + kfree(priv); + priv = NULL; + } + } + + /* Register the MTD with the procfs system if enabled */ + +#ifdef CONFIG_MTD_REGISTRATION + mtd_register(&priv->mtd, "sector512"); +#endif + + /* Return the implementation-specific state structure as the MTD device */ + + fvdbg("Return %p\n", priv); + return &priv->mtd; +} diff --git a/include/nuttx/mtd/mtd.h b/include/nuttx/mtd/mtd.h index d59ac5fe73..5078d6590b 100644 --- a/include/nuttx/mtd/mtd.h +++ b/include/nuttx/mtd/mtd.h @@ -266,6 +266,24 @@ int smart_initialize(int minor, FAR struct mtd_dev_s *mtd, * functions (such as a block or character driver front end). */ +/************************************************************************************ + * Name: s512_initialize + * + * Description: + * Create an initialized MTD device instance. This MTD driver contains another + * MTD driver and converts a larger sector size to a standard 512 byte sector + * size. + * + * MTD devices are not registered in the file system, but are created as instances + * that can be bound to other functions (such as a block or character driver front + * end). + * + ************************************************************************************/ + +#ifdef CONFIG_MTD_SECT512 +FAR struct mtd_dev_s *s512_initialize(FAR struct mtd_dev_s *mtd); +#endif + /**************************************************************************** * Name: at45db_initialize *