/**************************************************************************** * drivers/child/skeleton.c * * Copyright (C) 2013 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 /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /**************************************************************************** * 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 mtd_partition_s. */ struct mtd_partition_s { /* This structure must reside at the beginning so that we can simply cast * from struct mtd_dev_s * to struct mtd_partition_s * */ struct mtd_dev_s child; /* The "child" MTD vtable that manages the * sub-region */ /* Other implementation specific data may follow here */ FAR struct mtd_dev_s *parent; /* The "parent" MTD driver that manages the * entire FLASH */ off_t offset; /* Offset to the first sector of the managed * sub-region */ off_t neraseblocks; /* The number of erase blocks in the managed * sub-region */ off_t blocksize; /* The size of one read/write block */ uint16_t blkpererase; /* Number of R/W blocks in one erase block */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* MTD driver methods */ static int part_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks); static ssize_t part_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, FAR uint8_t *buf); static ssize_t part_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, FAR const uint8_t *buf); static ssize_t part_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, FAR uint8_t *buffer); static int part_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg); /**************************************************************************** * Private Data ****************************************************************************/ /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: part_erase * * Description: * Erase several blocks, each of the size previously reported. * ****************************************************************************/ static int part_erase(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks) { FAR struct mtd_partition_s *priv = (FAR struct mtd_partition_s *)dev; off_t partsize; DEBUGASSERT(priv); /* Make sure that erase would not extend past the end of the partition */ partsize = priv->neraseblocks * priv->blkpererase; if ((startblock + nblocks) > partsize) { fdbg("ERROR: Read beyond the end of the partition\n"); return -ENXIO; } /* Just add the partition offset to the requested offset and let the * underlying MTD driver perform the erase. */ return priv->parent->erase(priv->parent, startblock + priv->offset, nblocks); } /**************************************************************************** * Name: part_bread * * Description: * Read the specified number of blocks into the user provided buffer. * ****************************************************************************/ static ssize_t part_bread(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, FAR uint8_t *buf) { FAR struct mtd_partition_s *priv = (FAR struct mtd_partition_s *)dev; off_t partsize; DEBUGASSERT(priv && (buf || nblocks == 0)); /* Make sure that read would not extend past the end of the partition */ partsize = priv->neraseblocks * priv->blkpererase; if ((startblock + nblocks) > partsize) { fdbg("ERROR: Read beyond the end of the partition\n"); return -ENXIO; } /* Just add the partition offset to the requested offset and let the * underlying MTD driver perform the read. */ return priv->parent->bread(priv->parent, startblock + priv->offset, nblocks, buf); } /**************************************************************************** * Name: part_bwrite * * Description: * Write the specified number of blocks from the user provided buffer. * ****************************************************************************/ static ssize_t part_bwrite(FAR struct mtd_dev_s *dev, off_t startblock, size_t nblocks, FAR const uint8_t *buf) { FAR struct mtd_partition_s *priv = (FAR struct mtd_partition_s *)dev; off_t partsize; DEBUGASSERT(priv && (buf || nblocks == 0)); /* Make sure that write would not extend past the end of the partition */ partsize = priv->neraseblocks * priv->blkpererase; if ((startblock + nblocks) > partsize) { fdbg("ERROR: Write beyond the end of the partition\n"); return -ENXIO; } /* Just add the partition offset to the requested offset and let the * underlying MTD driver perform the write. */ return priv->parent->bwrite(priv->parent, startblock + priv->offset, nblocks, buf); } /**************************************************************************** * Name: part_read * * Description: * Read the specified number of bytes to the user provided buffer. * ****************************************************************************/ static ssize_t part_read(FAR struct mtd_dev_s *dev, off_t offset, size_t nbytes, FAR uint8_t *buffer) { FAR struct mtd_partition_s *priv = (FAR struct mtd_partition_s *)dev; off_t erasesize; off_t readend; off_t newoffset; DEBUGASSERT(priv && (buffer || nbytes == 0)); /* Does the underlying MTD device support the read method? */ if (priv->parent->read) { /* Make sure that read would not extend past the end of the partition */ erasesize = priv->blocksize * priv->blkpererase; readend = (offset + nbytes + erasesize - 1) / erasesize; if (readend > priv->neraseblocks) { fdbg("ERROR: Read beyond the end of the partition\n"); return -ENXIO; } /* Just add the partition offset to the requested offset and let the * underlying MTD driver perform the read. */ newoffset = offset + priv->offset * priv->blocksize; return priv->parent->read(priv->parent, newoffset, nbytes, buffer); } /* The underlying MTD driver does not support the read() method */ return -ENOSYS; } /**************************************************************************** * Name: part_ioctl ****************************************************************************/ static int part_ioctl(FAR struct mtd_dev_s *dev, int cmd, unsigned long arg) { FAR struct mtd_partition_s *priv = (FAR struct mtd_partition_s *)dev; int ret = -EINVAL; /* Assume good command with bad parameters */ DEBUGASSERT(priv); switch (cmd) { case MTDIOC_GEOMETRY: { FAR struct mtd_geometry_s *geo = (FAR struct mtd_geometry_s *)arg; if (geo) { /* Populate the geometry structure with information needed to know * the capacity and how to access the device. */ geo->blocksize = priv->blocksize; geo->erasesize = priv->blocksize * priv->blkpererase; geo->neraseblocks = priv->neraseblocks; ret = OK; } } break; case MTDIOC_XIPBASE: { FAR void **ppv = (FAR void**)arg; unsigned long base; if (ppv) { /* Get hte XIP base of the entire FLASH */ ret = priv->parent->ioctl(priv->parent, MTDIOC_XIPBASE, (unsigned long)((uintptr_t)&base)); if (ret == OK) { /* Add the offset of this partion to the XIP base and * return the sum to the caller. */ *ppv = (FAR void *)(base + priv->offset * priv->blocksize); } } } break; case MTDIOC_BULKERASE: { /* Erase the entire partition */ ret = priv->parent->erase(priv->parent, priv->offset, priv->neraseblocks * priv->blkpererase); } break; default: ret = -ENOTTY; /* Bad command */ break; } return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: mtd_partition * * Description: * Give an instance of an MTD driver, create a flash partition, ie., * another MTD driver instance that only operates with a sub-region of * FLASH media. That sub-region is defined by a sector offsetset and a * sector count (where the size of a sector is provided the by parent MTD * driver). * ****************************************************************************/ FAR struct mtd_dev_s *mtd_partition(FAR struct mtd_dev_s *mtd, off_t offset, off_t nblocks) { FAR struct mtd_partition_s *part; FAR struct mtd_geometry_s geo; unsigned int blkpererase; off_t erasestart; off_t eraseend; off_t devblocks; int ret; DEBUGASSERT(mtd); /* Get the geometry of the FLASH device */ ret = mtd->ioctl(mtd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&geo)); if (ret < 0) { fdbg("ERROR: mtd->ioctl failed: %d\n", ret); return NULL; } /* Get the number of blocks per erase. There must be an even number of * blocks in one erase blocks. */ blkpererase = geo.erasesize / geo.blocksize; DEBUGASSERT(blkpererase * geo.blocksize == geo.erasesize); /* Adjust the offset and size if necessary so that they are multiples of * the erase block size (making sure that we do not go outside of the * requested sub-region). NOTE that eraseend is the first erase block * beyond the sub-region. */ erasestart = (offset + blkpererase - 1) / blkpererase; eraseend = (offset + nblocks) / blkpererase; if (erasestart >= eraseend) { fdbg("ERROR: sub-region too small\n"); return NULL; } /* Verify that the sub-region is valid for this geometry */ devblocks = blkpererase * geo.neraseblocks; if (eraseend > devblocks) { fdbg("ERROR: sub-region too big\n"); return NULL; } /* Allocate a partition device structure */ part = (FAR struct mtd_partition_s *)malloc(sizeof(struct mtd_partition_s)); if (!part) { fdbg("ERROR: Failed to allocate memory for the partition device\n"); return NULL; } /* Initialize the partition device structure */ part->child.erase = part_erase; part->child.bread = part_bread; part->child.bwrite = part_bwrite; part->child.read = mtd->read ? part_read : NULL; part->child.ioctl = part_ioctl; part->parent = mtd; part->offset = erasestart * blkpererase; part->neraseblocks = eraseend - erasestart; part->blocksize = geo.blocksize; part->blkpererase = blkpererase; /* Return the implementation-specific state structure as the MTD device */ return &part->child; }