Add an MTD driver to wrap another MTD driver and makes is sector size look like 512 bytes

This commit is contained in:
Gregory Nutt 2014-03-24 17:45:45 -06:00
parent bd52d9af21
commit 30c34c07cf
4 changed files with 691 additions and 0 deletions

View File

@ -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

View File

@ -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)

648
drivers/mtd/sector512.c Normal file
View File

@ -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 <gnutt@nuttx.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
************************************************************************************/
/************************************************************************************
* Included Files
************************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/kmalloc.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/mtd/mtd.h>
/************************************************************************************
* 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;
}

View File

@ -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
*