/**************************************************************************** * drivers/mtd/mtd_config.c * * Copyright (C) 2014, 2017-2018 Gregory Nutt. All rights reserved. * Copyright (C) 2013 Ken Pettit. All rights reserved. * Author: Ken Pettit * With Updates from 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. * ****************************************************************************/ /**************************************************************************** * dev_config provides an interface for saving and retrieving * application configuration data to a standard MTD partition. * It accepts an MTD pointer (presumable a partition) and * publishes a /dev/config device file for accessing via * defined ioctl calls to set and get config data. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_MTD_CONFIG /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /* Define the current format version */ #define CONFIGDATA_FORMAT_VERSION 1 #define CONFIGDATA_BLOCK_HDR_SIZE 3 /* Define the erased state of the MTD device, if not already defined */ #ifndef CONFIG_MTD_CONFIG_ERASEDVALUE # define CONFIG_MTD_CONFIG_ERASEDVALUE 0xff #endif #define MTD_ERASED_ID ((CONFIG_MTD_CONFIG_ERASEDVALUE << 8) | \ CONFIG_MTD_CONFIG_ERASEDVALUE) #define MTD_ERASED_FLAGS CONFIG_MTD_CONFIG_ERASEDVALUE /**************************************************************************** * Private Types ****************************************************************************/ struct mtdconfig_struct_s { FAR struct mtd_dev_s *mtd; /* Contained MTD interface */ sem_t exclsem; /* Supports mutual exclusion */ uint32_t blocksize :14; /* Size of blocks in contained MTD */ uint32_t erasesize :18; /* Size of erase block in contained MTD */ size_t nblocks; /* Number of blocks available */ size_t neraseblocks; /* Number of erase blocks available */ off_t readoff; /* Read offset (for hexdump) */ FAR uint8_t *buffer; /* Temp block read buffer */ }; begin_packed_struct struct mtdconfig_header_s { uint8_t flags; /* Entry control flags */ #ifdef CONFIG_MTD_CONFIG_NAMED char name[CONFIG_MTD_CONFIG_NAME_LEN]; #else uint8_t instance; /* Instance of the item */ uint16_t id; /* ID of the config data item */ #endif uint16_t len; /* Length of the data block */ } end_packed_struct; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int mtdconfig_open(FAR struct file *filep); static int mtdconfig_close(FAR struct file *filep); static ssize_t mtdconfig_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static int mtdconfig_ioctl(FAR struct file *filep, int cmd, unsigned long arg); static int mtdconfig_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup); /**************************************************************************** * Private Data ****************************************************************************/ static const struct file_operations mtdconfig_fops = { mtdconfig_open, /* open */ mtdconfig_close, /* close */ mtdconfig_read, /* read */ 0, /* write */ 0, /* seek */ mtdconfig_ioctl, /* ioctl */ mtdconfig_poll /* poll */ }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: mtdconfig_readbytes * * Reads bytes from the contained MTD device. This will either use * the read function or if that is not available, the bread with a copy. * ****************************************************************************/ static int mtdconfig_readbytes(FAR struct mtdconfig_struct_s *dev, int offset, FAR uint8_t *pdata, int readlen) { off_t bytestoread = readlen; off_t bytesthisblock; off_t firstbyte; off_t block; off_t index; int ret = OK; size_t bytes; /* Test if read interface supported. If it is, use it directly. */ if ((dev->mtd->read != NULL) && (readlen < dev->blocksize)) { /* Read interface available. Read directly to buffer */ bytes = MTD_READ(dev->mtd, offset, readlen, pdata); if (bytes != readlen) { /* Error reading data! */ ret = -EIO; } } else { /* Read interface not available, do a block read into our buffer */ block = offset / dev->blocksize; firstbyte = offset - (block * dev->blocksize); bytesthisblock = dev->blocksize - firstbyte; if (bytesthisblock > readlen) { bytesthisblock = readlen; } index = 0; while (bytestoread > 0) { if (bytesthisblock < dev->blocksize || bytestoread < dev->blocksize) { /* Copy to temp buffer first...don't need the whole block */ bytes = MTD_BREAD(dev->mtd, block, 1, dev->buffer); if (bytes != 1) { /* Error reading data! */ ret = -EIO; goto errout; } /* Copy data to the output buffer */ memcpy(&pdata[index], &dev->buffer[firstbyte], bytesthisblock); } else { /* We are reading a whole block. Read directly to buffer */ bytes = MTD_BREAD(dev->mtd, block, 1, &pdata[index]); if (bytes != 1) { /* Error reading data! */ ret = -EIO; goto errout; } } /* Update values for next block read */ bytestoread -= bytesthisblock; index += bytesthisblock; bytesthisblock = dev->blocksize; if (bytesthisblock > bytestoread) { bytesthisblock = bytestoread; } firstbyte = 0; block++; } } errout: return ret; } /**************************************************************************** * Name: mtdconfig_writebytes * * Writes bytes to the contained MTD device. This will either use * the byte write function or if that is not available, the bwrite. * ****************************************************************************/ static int mtdconfig_writebytes(FAR struct mtdconfig_struct_s *dev, int offset, FAR const uint8_t *pdata, int writelen) { int ret = OK; #ifdef CONFIG_MTD_BYTE_WRITE /* Test if this MTD device supports byte write */ if (dev->mtd->write != NULL) { ret = MTD_WRITE(dev->mtd, offset, writelen, pdata); } else #endif /* Perform the write using the block write method of the MTD */ { uint16_t block; uint16_t index; off_t bytes_this_block; off_t bytes_written = 0; while (writelen) { /* Read existing data from the block into the buffer */ block = offset / dev->blocksize; ret = MTD_BREAD(dev->mtd, block, 1, dev->buffer); if (ret != 1) { ret = -EIO; goto errout; } index = offset - block * dev->blocksize; bytes_this_block = dev->blocksize - index; if (bytes_this_block > writelen) { bytes_this_block = writelen; } /* Now write data to the block */ memcpy(&dev->buffer[index], pdata, bytes_this_block); ret = MTD_BWRITE(dev->mtd, block, 1, dev->buffer); if (ret != 1) { ret = -EIO; goto errout; } /* Update writelen, etc. */ writelen -= bytes_this_block; pdata += bytes_this_block; offset += bytes_this_block; bytes_written += bytes_this_block; } /* Return the number of bytes written */ ret = bytes_written; } errout: return ret; } /**************************************************************************** * Name: mtdconfig_findfirstentry * * Locates the first config entry, even if it is empty. * * Returned Value: * offset to the start of the entry. * ****************************************************************************/ static int mtdconfig_findfirstentry(FAR struct mtdconfig_struct_s *dev, FAR struct mtdconfig_header_s *phdr) { off_t offset = CONFIGDATA_BLOCK_HDR_SIZE; uint8_t sig[CONFIGDATA_BLOCK_HDR_SIZE]; bool found = false; int ret; uint16_t block; off_t bytes_left_in_block; uint16_t endblock; /* Read the signature bytes */ ret = mtdconfig_readbytes(dev, 0, sig, sizeof(sig)); if (ret != OK) { return 0; } if (sig[0] != 'C' || sig[1] != 'D' || sig[2] != CONFIGDATA_FORMAT_VERSION) { /* Config Data partition not formatted. */ return 0; } #ifdef CONFIG_MTD_CONFIG_RAM_CONSOLIDATE endblock = dev->neraseblocks; #else if (dev->neraseblocks == 1) { endblock = 1; } else { endblock = dev->neraseblocks - 1; } #endif /* Config is formatted. Now loop until we find the first entry */ while (!found) { /* Read headers until we find one that hasn't been released. */ ret = mtdconfig_readbytes(dev, offset, (FAR uint8_t *)phdr, sizeof(struct mtdconfig_header_s)); if (ret != OK) { return 0; } /* Test if this header has been released */ if (phdr->flags != MTD_ERASED_FLAGS) { /* This entry has been released. Advance to next entry */ offset += sizeof(struct mtdconfig_header_s) + phdr->len; block = offset / dev->erasesize; bytes_left_in_block = (block + 1) * dev->erasesize - offset; if (bytes_left_in_block <= sizeof(*phdr)) { offset = (block + 1) * dev->erasesize + CONFIGDATA_BLOCK_HDR_SIZE; if (block + 1 >= endblock) { return 0; } } if (bytes_left_in_block == dev->erasesize) { /* Skip block header */ offset += CONFIGDATA_BLOCK_HDR_SIZE; } } else { /* We found the first entry! */ found = true; } } /* Return the offset of the first entry */ return offset; } /**************************************************************************** * Name: mtdconfig_findnextentry * * Locates the next config entry starting at offset, even if it is empty. * * Returned Value: * offset to the start of the next entry. * ****************************************************************************/ static int mtdconfig_findnextentry(FAR struct mtdconfig_struct_s *dev, off_t offset, FAR struct mtdconfig_header_s *phdr, uint16_t size) { bool found = false; int ret; uint16_t block; uint16_t bytes_left_in_block; uint16_t endblock; #ifdef CONFIG_MTD_CONFIG_RAM_CONSOLIDATE endblock = dev->neraseblocks; #else if (dev->neraseblocks == 1) { endblock = 1; } else { endblock = dev->neraseblocks - 1; } #endif /* Loop until next entry found */ while (!found) { /* Calculate offset of the next entry */ offset += sizeof(struct mtdconfig_header_s) + phdr->len; if (offset % dev->erasesize == 0) { offset += CONFIGDATA_BLOCK_HDR_SIZE; } if (offset >= endblock * dev->erasesize) { return 0; } /* Test if too close to the end of the erase block */ block = offset / dev->erasesize; bytes_left_in_block = (block + 1) * dev->erasesize - offset; if (bytes_left_in_block <= sizeof(*phdr)) { offset = (block + 1) * dev->erasesize + CONFIGDATA_BLOCK_HDR_SIZE; if (block + 1 >= endblock) { return 0; } } /* Read next header */ read_next: ret = mtdconfig_readbytes(dev, offset, (FAR uint8_t *)phdr, sizeof(*phdr)); if (ret != OK) { return 0; } /* Test if this header has is still active */ if (phdr->flags == MTD_ERASED_FLAGS) { #ifdef CONFIG_MTD_CONFIG_NAMED if (phdr->name[0] == CONFIG_MTD_CONFIG_ERASEDVALUE) #else if (phdr->id == MTD_ERASED_ID) #endif { /* If we are searching for free space, then check * if this space is large enough. If it is, then * we are done. */ block = offset / dev->erasesize; bytes_left_in_block = (block + 1) * dev->erasesize - offset; if (size > 0 && bytes_left_in_block >= size + sizeof(*phdr)) { /* Free entry of large enough size found */ found = true; break; } /* Advance to next erase block and continue search */ offset = (block + 1) * dev->erasesize + CONFIGDATA_BLOCK_HDR_SIZE; if (block + 1 >= endblock) { return 0; } /* Don't return to top of loop since we manually adjusted * the offset, and don't want to go to the next entry. */ goto read_next; } else { /* If we are searching for existing, active entries * only, then we are done ... we found one. */ if (size == 0) { found = true; } } } } return offset; } /**************************************************************************** * Name: mtdconfig_ramconsolidate * * Attempts to perform a RAM based consolidation of released * items. This requires a large enough free RAM block. This * method of consolidation is used when only a single erase * block is available in the partition. * * Returned Value: * Offset to the next available entry (after consolidation). * ****************************************************************************/ static off_t mtdconfig_ramconsolidate(FAR struct mtdconfig_struct_s *dev) { FAR uint8_t *pbuf; FAR struct mtdconfig_header_s *phdr; struct mtdconfig_header_s hdr; uint16_t src_block = 0; uint16_t dst_block = 0; uint16_t blkper; off_t dst_offset = CONFIGDATA_BLOCK_HDR_SIZE; off_t src_offset = CONFIGDATA_BLOCK_HDR_SIZE; off_t bytes_left_in_block; uint8_t sig[CONFIGDATA_BLOCK_HDR_SIZE]; int ret; /* Allocate a consolidation buffer */ pbuf = (FAR uint8_t *)kmm_malloc(dev->erasesize); if (pbuf == NULL) { /* Unable to allocate buffer, can't consolidate! */ return 0; } /* Loop for all blocks and consolidate them */ blkper = dev->erasesize / dev->blocksize; while (src_block < dev->neraseblocks) { /* Point to beginning of pbuf and read the next erase block */ ret = MTD_BREAD(dev->mtd, src_block * blkper, blkper, pbuf); if (ret < 0) { /* Error doing block read */ goto errout; } /* Now erase the block */ ret = MTD_ERASE(dev->mtd, src_block, 1); if (ret < 0) { /* Error erasing the block */ goto errout; } /* If this is block zero, then write a format signature */ if (src_block == 0) { sig[0] = 'C'; sig[1] = 'D'; sig[2] = CONFIGDATA_FORMAT_VERSION; ret = mtdconfig_writebytes(dev, 0, sig, sizeof(sig)); if (ret != sizeof(sig)) { /* Cannot write even the signature. */ ret = -EIO; goto errout; } } /* Copy active items back to the MTD device. */ while (src_offset < dev->erasesize) { phdr = (FAR struct mtdconfig_header_s *) &pbuf[src_offset]; #ifdef CONFIG_MTD_CONFIG_NAMED if (phdr->name[0] == CONFIG_MTD_CONFIG_ERASEDVALUE) #else if (phdr->id == MTD_ERASED_ID) #endif { /* No more data in this erase block. */ src_offset = dev->erasesize; continue; } if (phdr->flags == MTD_ERASED_FLAGS) { /* This is an active entry. Copy it. Check if it * fits in the current destination block. */ bytes_left_in_block = (dst_block + 1) * dev->erasesize - dst_offset; if (bytes_left_in_block < sizeof(*phdr) + phdr->len) { /* Item won't fit in the destination block. Move to * the next block. */ dst_block++; dst_offset = dst_block * dev->erasesize + CONFIGDATA_BLOCK_HDR_SIZE; /* Test for program bug. We shouldn't ever overflow * even if no entries were inactive. */ DEBUGASSERT(dst_block != dev->neraseblocks); } /* Now write the item to the current dst_offset location. */ ret = mtdconfig_writebytes(dev, dst_offset, (FAR uint8_t *)phdr, sizeof(hdr)); if (ret != sizeof(hdr)) { /* I/O Error! */ ret = -EIO; goto errout; } dst_offset += sizeof(hdr); ret = mtdconfig_writebytes(dev, dst_offset, &pbuf[src_offset + sizeof(hdr)], phdr->len); if (ret != phdr->len) { /* I/O Error! */ ret = -EIO; goto errout; } dst_offset += phdr->len; /* Test if enough space in dst block for another header */ if (dst_offset + sizeof(hdr) >= (dst_block + 1) * dev->erasesize || dst_offset == (dst_block + 1) * dev->erasesize) { dst_block++; dst_offset = dst_block * dev->erasesize + CONFIGDATA_BLOCK_HDR_SIZE; } } /* Increment past the current source item */ src_offset += sizeof(hdr) + phdr->len; if (src_offset + sizeof(hdr) > dev->erasesize) { src_offset = dev->erasesize; } DEBUGASSERT(src_offset <= dev->erasesize); } /* Increment to next source block */ src_block++; src_offset = CONFIGDATA_BLOCK_HDR_SIZE; } kmm_free(pbuf); return dst_offset; errout: kmm_free(pbuf); ferr("ERROR: fail ram consolidate: %d\n", ret); return 0; } /**************************************************************************** * Name: mtdconfig_consolidate * * Performs consolidation by writing erase block zero to the * reserved block at the end, erasing block zero, then * walking through each item and copying them to the newly * erased block. It erases all blocks to the end of the * partition as it goes. * * Returned Value: * Offset to the next available entry (after consolidation). * ****************************************************************************/ #ifndef CONFIG_MTD_CONFIG_RAM_CONSOLIDATE static off_t mtdconfig_consolidate(FAR struct mtdconfig_struct_s *dev) { off_t src_block; off_t dst_block; off_t src_offset; off_t dst_offset; uint16_t blkper; uint16_t x; uint16_t bytes; uint16_t bytes_left_in_block; struct mtdconfig_header_s hdr; int ret; uint8_t sig[CONFIGDATA_BLOCK_HDR_SIZE]; FAR uint8_t *pbuf; /* Prepare to copy block 0 to the last block (erase blocks) */ src_block = 0; dst_block = dev->neraseblocks - 1; /* Ensure the last block is erased */ MTD_ERASE(dev->mtd, dst_block, 1); blkper = dev->erasesize / dev->blocksize; dst_block *= blkper; /* Convert to read/write blocks */ /* Allocate a small buffer for moving data */ pbuf = (FAR uint8_t *)kmm_malloc(dev->blocksize); if (pbuf == NULL) { return 0; } /* Now copy block zero to last block */ for (x = 0; x < blkper; x++) { ret = MTD_BREAD(dev->mtd, src_block++, 1, dev->buffer); if (ret < 0) { /* I/O Error! */ goto errout; } ret = MTD_BWRITE(dev->mtd, dst_block++, 1, dev->buffer); if (ret < 0) { /* I/O Error! */ goto errout; } } /* Erase block zero and write a format signature. */ MTD_ERASE(dev->mtd, 0, 1); sig[0] = 'C'; sig[1] = 'D'; sig[2] = CONFIGDATA_FORMAT_VERSION; ret = mtdconfig_writebytes(dev, 0, sig, sizeof(sig)); if (ret != sizeof(sig)) { /* Cannot write even the signature. */ ret = -EIO; goto errout; } /* Now consolidate entries. */ src_block = 1; dst_block = 0; src_offset = src_block * dev->erasesize + CONFIGDATA_BLOCK_HDR_SIZE; dst_offset = CONFIGDATA_BLOCK_HDR_SIZE; while (src_block < dev->neraseblocks) { /* Scan all headers and move them to the src_offset */ retry_relocate: bytes = MTD_READ(dev->mtd, src_offset, sizeof(hdr), (FAR uint8_t *)&hdr); if (bytes != sizeof(hdr)) { /* I/O Error! */ ret = -EIO; goto errout; } if (hdr.flags == MTD_ERASED_FLAGS) { /* Test if the source entry is active or if we are at the end * of data for this erase block. */ #ifdef CONFIG_MTD_CONFIG_NAMED if (hdr.name[0] == CONFIG_MTD_CONFIG_ERASEDVALUE) #else if (hdr.id == MTD_ERASED_ID) #endif { /* No more data in this erase block. Advance to the * next one. */ src_offset = (src_block + 1) * dev->erasesize + CONFIGDATA_BLOCK_HDR_SIZE; } else { /* Test if this entry will fit in the current destination block */ bytes_left_in_block = (dst_block + 1) * dev->erasesize - dst_offset; if (hdr.len + sizeof(hdr) > bytes_left_in_block) { /* Item doesn't fit in the block. Advance to the next one */ /* Update control variables */ dst_block++; dst_offset = dst_block * dev->erasesize + CONFIGDATA_BLOCK_HDR_SIZE; DEBUGASSERT(dst_block != src_block); /* Retry the relocate */ goto retry_relocate; } /* Copy this entry to the destination */ ret = mtdconfig_writebytes(dev, dst_offset, (FAR uint8_t *)&hdr, sizeof(hdr)); if (ret != sizeof(hdr)) { /* I/O Error! */ ret = -EIO; goto errout; } src_offset += sizeof(hdr); dst_offset += sizeof(hdr); /* Now copy the data */ while (hdr.len) { bytes = hdr.len; if (bytes > dev->blocksize) { bytes = dev->blocksize; } /* Move the data. */ ret = mtdconfig_readbytes(dev, src_offset, pbuf, bytes); if (ret != OK) { /* I/O Error! */ ret = -EIO; goto errout; } ret = mtdconfig_writebytes(dev, dst_offset, pbuf, bytes); if (ret != bytes) { /* I/O Error! */ ret = -EIO; goto errout; } /* Update control variables */ hdr.len -= bytes; src_offset += bytes; dst_offset += bytes; } } } else { /* This item has been released. Skip it! */ src_offset += sizeof(hdr) + hdr.len; if (src_offset + sizeof(hdr) >= (src_block + 1) * dev->erasesize || src_offset == (src_block + 1) * dev->erasesize) { /* No room left at end of source block */ src_offset = (src_block + 1) * dev->erasesize + CONFIGDATA_BLOCK_HDR_SIZE; } } /* Test if we are out of space in the src block */ if (src_offset + sizeof(hdr) >= (src_block + 1) * dev->erasesize) { /* No room at end of src block for another header. Go to next * source block. */ src_offset = (src_block + 1) * dev->erasesize + CONFIGDATA_BLOCK_HDR_SIZE; } /* Test if we advanced to the next block. If we did, then erase the * old block. */ if (src_block != src_offset / dev->erasesize) { /* Erase the block ... we have emptied it */ MTD_ERASE(dev->mtd, src_block, 1); src_block++; } /* Test if we are out of space in the dst block */ if (dst_offset + sizeof(hdr) >= (dst_block + 1) * dev->erasesize) { /* No room at end of dst block for another header. Go to next block. */ dst_block++; dst_offset = dst_block * dev->erasesize + CONFIGDATA_BLOCK_HDR_SIZE; DEBUGASSERT(dst_block != src_block); } } kmm_free(pbuf); return dst_offset; errout: kmm_free(pbuf); ferr("ERROR: fail consolidate: %d\n", ret); return 0; } #endif /* CONFIG_MTD_CONFIG_RAM_CONSOLIDATE */ /**************************************************************************** * Name: mtdconfig_open ****************************************************************************/ static int mtdconfig_open(FAR struct file *filep) { FAR struct inode *inode = filep->f_inode; FAR struct mtdconfig_struct_s *dev = inode->i_private; int ret; /* Get exclusive access to the device */ ret = nxsem_wait(&dev->exclsem); if (ret < 0) { ferr("ERROR: nxsem_wait failed: %d\n", ret); goto errout; } dev->readoff = 0; errout: return ret; } /**************************************************************************** * Name: mtdconfig_close ****************************************************************************/ static int mtdconfig_close(FAR struct file *filep) { FAR struct inode *inode = filep->f_inode; FAR struct mtdconfig_struct_s *dev = inode->i_private; /* Release exclusive access to the device */ nxsem_post(&dev->exclsem); return OK; } /**************************************************************************** * Name: mtdconfig_read ****************************************************************************/ static ssize_t mtdconfig_read(FAR struct file *filep, FAR char *buffer, size_t len) { FAR struct inode *inode = filep->f_inode; FAR struct mtdconfig_struct_s *dev = inode->i_private; size_t bytes; if (dev->readoff >= dev->neraseblocks * dev->erasesize) { return 0; } /* Read data from the file */ bytes = MTD_READ(dev->mtd, dev->readoff, len, (FAR uint8_t *)buffer); if (bytes != len) { return -EIO; } dev->readoff += bytes; return bytes; } /**************************************************************************** * Name: mtdconfig_findentry ****************************************************************************/ static int mtdconfig_findentry(FAR struct mtdconfig_struct_s *dev, off_t offset, FAR struct config_data_s *pdata, FAR struct mtdconfig_header_s *phdr) { uint16_t endblock; int ret; #ifdef CONFIG_MTD_CONFIG_RAM_CONSOLIDATE endblock = dev->neraseblocks; #else if (dev->neraseblocks == 1) { endblock = 1; } else { endblock = dev->neraseblocks - 1; } #endif #ifdef CONFIG_MTD_CONFIG_NAMED while (offset > 0 && strcmp(pdata->name, phdr->name) != 0) #else while (offset > 0 && (pdata->id != phdr->id || pdata->instance != phdr->instance)) #endif { #ifdef CONFIG_MTD_CONFIG_NAMED if (phdr->name[0] == CONFIG_MTD_CONFIG_ERASEDVALUE) #else if (phdr->id == MTD_ERASED_ID) #endif { /* Advance to the next block and continue the search */ offset = (offset + dev->erasesize) / dev->erasesize; offset = offset * dev->erasesize + CONFIGDATA_BLOCK_HDR_SIZE; if (offset >= endblock * dev->erasesize) { /* Entry doesn't exist on the device */ offset = 0; break; } /* Read the 1st header from the next block */ ret = mtdconfig_readbytes(dev, offset, (FAR uint8_t *)phdr, sizeof(*phdr)); if (ret != OK) { /* Error reading the data */ offset = 0; break; } if (phdr->flags == MTD_ERASED_FLAGS) { continue; } } /* Nope, not the last header. Get the next one */ offset = mtdconfig_findnextentry(dev, offset, phdr, 0); } return offset; } /**************************************************************************** * Name: mtdconfig_setconfig ****************************************************************************/ static int mtdconfig_setconfig(FAR struct mtdconfig_struct_s *dev, FAR struct config_data_s *pdata) { uint8_t sig[CONFIGDATA_BLOCK_HDR_SIZE]; /* Format signature bytes ("CD") */ char retrycount = 0; int ret = -ENOSYS; off_t offset; off_t bytes_left_in_block; off_t bytes; uint16_t block; struct mtdconfig_header_s hdr; uint8_t ram_consolidate; /* Allocate a temp block buffer */ dev->buffer = (FAR uint8_t *) kmm_malloc(dev->blocksize); if (dev->buffer == NULL) { return -ENOMEM; } /* Read and validate the signature bytes */ retry: offset = mtdconfig_findfirstentry(dev, &hdr); if (offset == 0) { /* Config Data partition not formatted. */ if (retrycount) { ret = -ENOSYS; goto errout; } /* Try to format the config partition */ ret = MTD_IOCTL(dev->mtd, MTDIOC_BULKERASE, 0); if (ret < 0) { goto errout; } /* Write a format signature */ sig[0] = 'C'; sig[1] = 'D'; sig[2] = CONFIGDATA_FORMAT_VERSION; ret = mtdconfig_writebytes(dev, 0, sig, sizeof(sig)); if (ret != sizeof(sig)) { /* Cannot write even the signature. */ ret = -EIO; goto errout; } /* Now go try to read the signature again (as verification) */ retrycount++; goto retry; } /* Okay, the Config Data partition is formatted. Check if the * config item being written is already in the database. If it * is, we must mark it as obsolete before creating a new entry. */ offset = mtdconfig_findentry(dev, offset, pdata, &hdr); /* Test if the header was found. */ #ifdef CONFIG_MTD_CONFIG_NAMED if (offset > 0 && strcmp(pdata->name, hdr.name) == 0) #else if (offset > 0 && pdata->id == hdr.id && pdata->instance == hdr.instance) #endif { /* Mark this entry as released */ hdr.flags = (uint8_t)~MTD_ERASED_FLAGS; mtdconfig_writebytes(dev, offset, &hdr.flags, sizeof(hdr.flags)); } /* Test if the new length is zero. If it is, then we are * deleting the entry. */ if (pdata->len == 0) { ret = OK; goto errout; } /* Now find a new entry for this config data */ retrycount = 0; retry_find: offset = mtdconfig_findfirstentry(dev, &hdr); #ifdef CONFIG_MTD_CONFIG_NAMED if (offset > 0 && hdr.name[0] == CONFIG_MTD_CONFIG_ERASEDVALUE) #else if (offset > 0 && hdr.id == MTD_ERASED_ID) #endif { block = offset / dev->erasesize; bytes_left_in_block = (block + 1) * dev->erasesize - offset; if (bytes_left_in_block < sizeof(hdr) + pdata->len) { /* Simulate an active block to search for the next one * in the code below. */ #ifdef CONFIG_MTD_CONFIG_NAMED hdr.name[0] = 1; #else hdr.id = 1; #endif } } #ifdef CONFIG_MTD_CONFIG_NAMED if (hdr.name[0] != CONFIG_MTD_CONFIG_ERASEDVALUE) #else if (hdr.id != MTD_ERASED_ID) #endif { /* Read the next entry */ offset = mtdconfig_findnextentry(dev, offset, &hdr, pdata->len); if (offset == 0) { /* No free entries left on device! */ #ifdef CONFIG_MTD_CONFIG_RAM_CONSOLIDATE ram_consolidate = 1; #else ram_consolidate = dev->neraseblocks == 1; #endif if (ram_consolidate) { /* If we only have 1 erase block, then we must do a RAM * assisted consolidation of released entries. */ if (retrycount) { /* Out of space! */ ret = -ENOMEM; goto errout; } mtdconfig_ramconsolidate(dev); retrycount++; goto retry_find; } #ifndef CONFIG_MTD_CONFIG_RAM_CONSOLIDATE else { if (retrycount) { /* Out of space! */ ret = -ENOMEM; goto errout; } mtdconfig_consolidate(dev); retrycount++; goto retry_find; } #endif } } /* Test if a new entry was found */ if (offset > 0) { /* Save the data at this entry */ #ifdef CONFIG_MTD_CONFIG_NAMED strcpy(hdr.name, pdata->name); #else hdr.id = pdata->id; hdr.instance = pdata->instance; #endif hdr.len = pdata->len; hdr.flags = MTD_ERASED_FLAGS; ret = mtdconfig_writebytes(dev, offset, (FAR uint8_t *)&hdr, sizeof(hdr)); if (ret != sizeof(hdr)) { /* Cannot write even header! */ ret = -EIO; goto errout; } bytes = mtdconfig_writebytes(dev, offset + sizeof(hdr), pdata->configdata, pdata->len); if (bytes != pdata->len) { /* Error writing data! */ hdr.flags = MTD_ERASED_FLAGS; mtdconfig_writebytes(dev, offset, (FAR uint8_t *)&hdr, sizeof(hdr.flags)); ret = -EIO; goto errout; } ret = OK; } errout: /* Free the buffer */ kmm_free(dev->buffer); return ret; } /**************************************************************************** * Name: mtdconfig_getconfig ****************************************************************************/ static int mtdconfig_getconfig(FAR struct mtdconfig_struct_s *dev, FAR struct config_data_s *pdata) { int ret = -ENOSYS; off_t offset; off_t bytes_to_read; struct mtdconfig_header_s hdr; /* Allocate a temp block buffer */ dev->buffer = (FAR uint8_t *)kmm_malloc(dev->blocksize); if (dev->buffer == NULL) { return -ENOMEM; } /* Get the offset of the first entry. This will also check * the format signature bytes. */ offset = mtdconfig_findfirstentry(dev, &hdr); offset = mtdconfig_findentry(dev, offset, pdata, &hdr); /* Test if the header was found. */ #ifdef CONFIG_MTD_CONFIG_NAMED if (offset > 0 && strcmp(pdata->name, hdr.name) == 0) #else if (offset > 0 && (pdata->id == hdr.id && pdata->instance == hdr.instance)) #endif { /* Entry found. Read the data */ bytes_to_read = hdr.len; if (bytes_to_read > pdata->len) { bytes_to_read = pdata->len; } /* Perform the read */ ret = mtdconfig_readbytes(dev, offset + sizeof(hdr), pdata->configdata, bytes_to_read); if (ret != OK) { /* Error reading the data */ ret = -EIO; goto errout; } /* Set return data length to match the config item length */ pdata->len = hdr.len; ret = OK; } errout: /* Free the buffer */ kmm_free(dev->buffer); return ret; } /**************************************************************************** * Name: mtdconfig_deleteconfig ****************************************************************************/ static int mtdconfig_deleteconfig(FAR struct mtdconfig_struct_s *dev, FAR struct config_data_s *pdata) { int ret = -ENOENT; off_t offset; struct mtdconfig_header_s hdr; /* Allocate a temp block buffer */ dev->buffer = (FAR uint8_t *)kmm_malloc(dev->blocksize); if (dev->buffer == NULL) { return -ENOMEM; } /* Get the offset of the first entry. This will also check * the format signature bytes. */ offset = mtdconfig_findfirstentry(dev, &hdr); offset = mtdconfig_findentry(dev, offset, pdata, &hdr); /* Test if the header was found. */ #ifdef CONFIG_MTD_CONFIG_NAMED if (offset > 0 && strcmp(pdata->name, hdr.name) == 0) #else if (offset > 0 && (pdata->id == hdr.id && pdata->instance == hdr.instance)) #endif { /* Entry found. Mark this entry as released */ hdr.flags = (uint8_t)~MTD_ERASED_FLAGS; mtdconfig_writebytes(dev, offset, &hdr.flags, sizeof(hdr.flags)); ret = OK; } /* Free the buffer */ kmm_free(dev->buffer); return ret; } /**************************************************************************** * Name: mtdconfig_ioctl ****************************************************************************/ static int mtdconfig_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { FAR struct inode *inode = filep->f_inode; FAR struct mtdconfig_struct_s *dev = inode->i_private; FAR struct config_data_s *pdata; struct mtdconfig_header_s hdr; off_t bytes_to_read; int ret = -ENOSYS; switch (cmd) { case CFGDIOC_SETCONFIG: /* Set the config item */ pdata = (FAR struct config_data_s *)arg; ret = mtdconfig_setconfig(dev, pdata); break; case CFGDIOC_GETCONFIG: /* Get the config item */ pdata = (FAR struct config_data_s *)arg; ret = mtdconfig_getconfig(dev, pdata); break; case CFGDIOC_DELCONFIG: /* Set the config item */ pdata = (FAR struct config_data_s *)arg; ret = mtdconfig_deleteconfig(dev, pdata); break; case CFGDIOC_FIRSTCONFIG: /* Get the the first config item */ pdata = (FAR struct config_data_s *)arg; dev->readoff = mtdconfig_findfirstentry(dev, &hdr); /* Test if the config item is valid */ #ifdef CONFIG_MTD_CONFIG_NAMED if (dev->readoff != 0 && hdr.name[0] != CONFIG_MTD_CONFIG_ERASEDVALUE) #else if (dev->readoff != 0 && hdr.id != MTD_ERASED_ID) #endif { /* Perform the read */ bytes_to_read = hdr.len; if (bytes_to_read > pdata->len) { bytes_to_read = pdata->len; } ret = mtdconfig_readbytes(dev, dev->readoff + sizeof(hdr), pdata->configdata, bytes_to_read); if (ret < 0) { break; } /* Set other return data items */ #ifdef CONFIG_MTD_CONFIG_NAMED strcpy(pdata->name, hdr.name); #else pdata->id = hdr.id; pdata->instance = hdr.instance; #endif pdata->len = bytes_to_read; } else { ret = -ENOENT; } break; case CFGDIOC_NEXTCONFIG: /* Get the next config item */ pdata = (FAR struct config_data_s *)arg; ret = mtdconfig_readbytes(dev, dev->readoff, (FAR uint8_t *)&hdr, sizeof(hdr)); if (ret < 0) { break; } dev->readoff = mtdconfig_findnextentry(dev, dev->readoff, &hdr, 0); /* Test if the config item is valid */ #ifdef CONFIG_MTD_CONFIG_NAMED if (dev->readoff != 0 && hdr.name[0] != CONFIG_MTD_CONFIG_ERASEDVALUE) #else if (dev->readoff != 0 && hdr.id != MTD_ERASED_ID) #endif { /* Test if this is an empty slot */ bytes_to_read = hdr.len; if (bytes_to_read > pdata->len) { bytes_to_read = pdata->len; } /* Read the config item data */ ret = mtdconfig_readbytes(dev, dev->readoff + sizeof(hdr), pdata->configdata, bytes_to_read); if (ret < 0) { break; } #ifdef CONFIG_MTD_CONFIG_NAMED strcpy(pdata->name, hdr.name); #else pdata->id = hdr.id; pdata->instance = hdr.instance; #endif pdata->len = bytes_to_read; } else { ret = -ENOENT; } break; case MTDIOC_BULKERASE: /* Call the MTD's ioctl for this */ if (dev->mtd->ioctl) { dev->mtd->ioctl(dev->mtd, cmd, arg); } break; } return ret; } /**************************************************************************** * Name: mtdconfig_poll ****************************************************************************/ static int mtdconfig_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup) { if (setup) { fds->revents |= (fds->events & (POLLIN | POLLOUT)); if (fds->revents != 0) { nxsem_post(fds->sem); } } return OK; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: mtdconfig_register * * Description: * Register a /dev/config device backed by an MTD * ****************************************************************************/ int mtdconfig_register(FAR struct mtd_dev_s *mtd) { int ret = -ENOMEM; struct mtdconfig_struct_s *dev; struct mtd_geometry_s geo; /* Device geometry */ dev = (struct mtdconfig_struct_s *) kmm_malloc(sizeof(struct mtdconfig_struct_s)); if (dev != NULL) { /* Initialize the mtdconfig device structure */ dev->mtd = mtd; nxsem_init(&dev->exclsem, 0, 1); /* 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). */ ret = MTD_IOCTL(mtd, MTDIOC_GEOMETRY, (unsigned long)((uintptr_t)&geo)); if (ret < 0) { ferr("ERROR: MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret); kmm_free(dev); goto errout; } dev->blocksize = geo.blocksize; dev->neraseblocks = geo.neraseblocks; dev->erasesize = geo.erasesize; dev->nblocks = geo.neraseblocks * geo.erasesize / geo.blocksize; register_driver("/dev/config", &mtdconfig_fops, 0666, dev); } errout: return ret; } #endif /* CONFIG_MTD_CONFIG */