From b01900d7e6d891ecfdad29eade03abdb9e4af62f Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Fri, 1 Nov 2013 07:39:20 -0600 Subject: [PATCH] Add an MTD device wrapper that can used to provide a /dev/config that can be used for retaining configuration data. From Ken Pettit --- ChangeLog | 5 +- drivers/mtd/Kconfig | 7 + drivers/mtd/Make.defs | 2 +- drivers/mtd/mtd_config.c | 995 +++++++++++++++++++++++++++++++++++++ include/nuttx/configdata.h | 143 ++++++ include/nuttx/fs/ioctl.h | 17 +- 6 files changed, 1162 insertions(+), 7 deletions(-) create mode 100644 drivers/mtd/mtd_config.c create mode 100644 include/nuttx/configdata.h diff --git a/ChangeLog b/ChangeLog index 4e1a5c574c..f88f6a9be1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5924,4 +5924,7 @@ caused an exception in usbdev_reset() later. The driver reference will be nullified later usbdev_unregister when the caller gets the error. From David Sidrane (2013-10-31). - + * drivers/mtd_config.c and include/nuttx/configdata.h: Add a container + for an MTD device that can be used to provide a simple, lightweight + interface to configation data storage that resides on some storage + media that is wrapped as an MTD device. From Ken Pettit (2013-11-1). diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig index ffe7ac06e1..f41eb8626a 100644 --- a/drivers/mtd/Kconfig +++ b/drivers/mtd/Kconfig @@ -35,6 +35,13 @@ config MTD_BYTE_WRITE support such writes. The SMART file system can take advantage of this option if it is enabled. +config MTD_CONFIG + bool "Enable Dev Config (MTD based) device" + default n + ---help--- + Provides a /dev/config device for saving / restoring application + configuration data to a standard MTD device or partition. + comment "MTD Device Drivers" config RAMMTD diff --git a/drivers/mtd/Make.defs b/drivers/mtd/Make.defs index 3f6c8b6fb5..f37f2c136c 100644 --- a/drivers/mtd/Make.defs +++ b/drivers/mtd/Make.defs @@ -39,7 +39,7 @@ ifeq ($(CONFIG_MTD),y) -CSRCS += at45db.c flash_eraseall.c ftl.c m25px.c ramtron.c +CSRCS += at45db.c flash_eraseall.c ftl.c m25px.c ramtron.c mtd_config.c ifeq ($(CONFIG_MTD_PARTITION),y) CSRCS += mtd_partition.c diff --git a/drivers/mtd/mtd_config.c b/drivers/mtd/mtd_config.c new file mode 100644 index 0000000000..0e1c57430d --- /dev/null +++ b/drivers/mtd/mtd_config.c @@ -0,0 +1,995 @@ +/**************************************************************************** + * drivers/mtd/mtd_config.c + * + * 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. + * + ****************************************************************************/ + +/**************************************************************************** + * 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 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct mtdconfig_struct_s +{ + FAR struct mtd_dev_s *mtd; /* Contained MTD interface */ + sem_t exclsem; /* Supports mutual exclusion */ + uint16_t blocksize; /* Size of blocks in contained MTD */ + uint16_t erasesize; /* 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 */ + uint8_t erasedvalue; /* Value of and erased byte on the MTD */ +}; + +struct mtdconfig_header_s +{ + uint8_t flags; /* Entry control flags */ + uint16_t id; /* ID of the config data item */ + int instance; /* Instance of the item */ + size_t len; /* Length of the data block */ +} 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 *, FAR char *, size_t); +static ssize_t mtdconfig_ioctl(FAR struct file *, int, unsigned long); +#ifndef CONFIG_DISABLE_POLL +static int mtdconfig_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup); +#endif + +/**************************************************************************** + * 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 */ +#ifndef CONFIG_DISABLE_POLL + , mtdconfig_poll /* poll */ +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: mtdconfig_readbytes + * + * Reads bytes from the contained MTD device. This will either usee + * 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, firstbyte; + off_t block, 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; + firstbyte = 0; + block++; + } + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: mtdconfig_writebytes + * + * Writes bytes to the contained MTD device. This will either usee + * 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, index; + off_t bytes_this_block; + + while (writelen) + { + /* Read existing data from the 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; + } + + bytes_this_block = offset - block * dev->blocksize; + index = bytes_this_block; + 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; + } + } + +errout: + return ret; +} + +/**************************************************************************** + * Name: mtdconfig_findfirstentry + * + * Locates the first config entry, even if it is empty. + * + * Returns: + * 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 = 2; + uint8_t sig[2]; + bool found = false; + int ret; + + mtdconfig_readbytes(dev, 0, sig, sizeof(sig)); /* Read the signature bytes */ + if (sig[0] != 'C' || sig[1] != 'D') + { + /* Config Data partition not formatted. */ + + return 0; + } + + /* 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, (uint8_t *) phdr, + sizeof(struct mtdconfig_header_s)); + if (ret != OK) + { + return 0; + } + + /* Test if this header has been released */ + + if (phdr->flags != dev->erasedvalue) + { + /* This entry has been released. Advance to next entry */ + + offset += sizeof(struct mtdconfig_header_s) + phdr->len; + } + 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. + * + * Returns: + * 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) +{ + bool found = false; + int ret; + + /* Loop until next entry found */ + + while (!found) + { + /* Calculate offset of the next entry */ + + offset += sizeof(struct mtdconfig_header_s) + phdr->len; + + /* Read next header */ + + ret = mtdconfig_readbytes(dev, offset, (uint8_t *) phdr, sizeof(*phdr)); + if (ret != OK) + { + return 0; + } + + /* Test if this header has is still active */ + + if (phdr->flags == dev->erasedvalue) + { + 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. + * + * Returns: + * offset to the next available entry (after consolidation).. + * + ****************************************************************************/ + +static off_t mtdconfig_ramconsolidate(FAR struct mtdconfig_struct_s *dev) +{ + 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. + * + * Returns: + * offset to the next available entry (after consolidation).. + * + ****************************************************************************/ + +static off_t mtdconfig_consolidate(FAR struct mtdconfig_struct_s *dev) +{ + off_t src_block, dst_block; + off_t src_offset, dst_offset; + uint16_t blkper, x, bytes, bytes_left_in_block; + struct mtdconfig_header_s hdr; + int ret; + uint8_t sig[2]; + + /* 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 */ + + /* 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! */ + + return 0; + } + + ret = MTD_BWRITE(dev->mtd, dst_block, 1, dev->buffer); + if (ret < 0) + { + /* I/O Error! */ + + return 0; + } + } + + /* Erase block zero and write a format signature. */ + + MTD_ERASE(dev->mtd, 0, 1); + sig[0] = 'C'; + sig[1] = 'D'; + mtdconfig_writebytes(dev, 0, sig, sizeof(sig)); + + /* Now consolidate entries */ + + src_block = 1; + dst_block = 0; + src_offset = src_block * dev->erasesize; + dst_offset = 2; + + while (src_block < dev->neraseblocks) + { + /* Scan all headers and move them to the src_offset */ + +retry_relocate: + MTD_READ(dev->mtd, src_offset, sizeof(hdr), (uint8_t *) &hdr); + if (hdr.flags == dev->erasedvalue) + { + /* Test if this entry will fit in the current destination block */ + + bytes_left_in_block = dst_offset - dst_block * dev->erasesize; + if (hdr.len + sizeof(hdr) > bytes_left_in_block) + { + /* We need to release the rest of the bytes in this block */ + + hdr.flags = ~dev->erasedvalue; + hdr.len = bytes_left_in_block - sizeof(hdr); + MTD_WRITE(dev->mtd, dst_offset, sizeof(hdr), (uint8_t *) &hdr); + + /* Update control variables */ + + dst_offset += bytes_left_in_block; + dst_block++; + DEBUGASSERT(dst_block != src_block); + DEBUGASSERT(dst_offset == dst_block * dev->erasesize); + + /* Retry the relocate */ + + goto retry_relocate; + } + + /* Copy this entry to the destination */ + + MTD_WRITE(dev->mtd, dst_offset, sizeof(hdr), (uint8_t *) &hdr); + 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. */ + + MTD_READ(dev->mtd, src_offset, bytes, dev->buffer); + MTD_WRITE(dev->mtd, dst_offset, bytes, dev->buffer); + + /* 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; + } + + /* 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++; + } + } + + return 0; +} + +/**************************************************************************** + * 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 = OK; + + /* Get exclusive access to the device */ + + ret = sem_wait(&dev->exclsem); + if (ret < 0) + { + ret = -errno; + 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 */ + + sem_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, (uint8_t *) buffer); + dev->readoff += bytes; + return bytes; +} + +/**************************************************************************** + * Name: mtdconfig_setconfig + ****************************************************************************/ + +static int mtdconfig_setconfig(FAR struct mtdconfig_struct_s *dev, + FAR struct config_data_s *pdata) +{ + uint8_t sig[2]; /* Format signature bytes ("CD") */ + char retrycount = 0; + int ret = -ENOSYS; + off_t offset, bytes_left_in_block, bytes; + uint16_t block; + struct mtdconfig_header_s hdr; + + /* Allocate a temp block buffer */ + + dev->buffer = (FAR uint8_t *) kmalloc(dev->blocksize); + + /* Read and vaidate 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'; + mtdconfig_writebytes(dev, 0, sig, sizeof(sig)); + + /* 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. + */ + + while (offset > 0 && (pdata->id != hdr.id || pdata->instance != hdr.instance)) + { + /* This header doesn't match. Test if it is the last hdr */ + + if (hdr.id == ((dev->erasedvalue << 8) | dev->erasedvalue)) + { + break; + } + + /* Nope, not the last header. Get the next one */ + + offset = mtdconfig_findnextentry(dev, offset, &hdr); + } + + /* Test if the header was found. */ + + if (pdata->id == hdr.id && pdata->instance == hdr.instance) + { + /* Mark this entry as released */ + + hdr.flags = ~dev->erasedvalue; + mtdconfig_writebytes(dev, offset, &hdr.flags, sizeof(hdr.flags)); + } + + /* Now find a new entry for this config data */ + + while (hdr.id != ((dev->erasedvalue << 8) | dev->erasedvalue)) + { + /* Read the next entry */ + + offset = mtdconfig_findnextentry(dev, offset, &hdr); + } + + /* Test if a new entry was found */ + + retrycount = 0; + if (offset > 0) + { + /* Try to write the data to this entry. We have to be + * sure it will fit in the erase block because we don't + * support data spanning erase blocks. + */ + +retry_fit: + block = offset / dev->erasesize; + bytes_left_in_block = block * dev->erasesize - offset; + if (bytes_left_in_block < pdata->len) + { + /* Data doesn't fit in the block! Our only recourse is + * to release the rest of the data in this erase block. + */ + + hdr.id = 0; + hdr.len = bytes_left_in_block - sizeof(hdr); + hdr.flags = ~dev->erasedvalue; + mtdconfig_writebytes(dev, offset, (uint8_t *) &hdr, sizeof(hdr)); + + /* Now we need to advance to the next erase block */ + + offset += bytes_left_in_block; + block++; + if (dev->neraseblocks == 1) + { + /* 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; + } + + offset = mtdconfig_ramconsolidate(dev); + retrycount++; + goto retry_fit; + } + + /* If we have 2 or more erase blocks, then we reserve one + * block to perform non-RAM assited consolidation. + */ + + else if (block + 1 == dev->neraseblocks) + { + if (retrycount) + { + /* Out of space! */ + + ret = -ENOMEM; + goto errout; + } + + offset = mtdconfig_consolidate(dev); + retrycount++; + goto retry_fit; + } + } + + /* Validate that we have a non-zero offset. We may have done a + * consolidation above that resulted + */ + + if (offset) + { + /* Save the data at this entry */ + + hdr.id = pdata->id; + hdr.instance = pdata->instance; + hdr.len = pdata->len; + hdr.flags = dev->erasedvalue; + mtdconfig_writebytes(dev, offset, (uint8_t *) &hdr, sizeof(hdr)); + bytes = mtdconfig_writebytes(dev, offset + sizeof(hdr), pdata->configdata, + pdata->len); + if (bytes != pdata->len) + { + /* Error writing data! */ + + hdr.flags = ~dev->erasedvalue; + mtdconfig_writebytes(dev, offset, (uint8_t *) &hdr, sizeof(hdr.flags)); + ret = -EIO; + goto errout; + } + + ret = OK; + } + } + +errout: + + /* Free the buffer */ + + kfree(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, bytes_to_read; + struct mtdconfig_header_s hdr; + + /* Allocate a temp block buffer */ + + dev->buffer = (FAR uint8_t *) kmalloc(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); + while (offset > 0 && (pdata->id != hdr.id || pdata->instance != hdr.instance)) + { + /* This header doesn't match. Test if it is the last hdr */ + + if (hdr.id == ((dev->erasedvalue << 8) | dev->erasedvalue)) + { + break; + } + + /* Nope, not the last header. Get the next one */ + + offset = mtdconfig_findnextentry(dev, offset, &hdr); + } + + /* Test if the header was found. */ + + if (pdata->id == hdr.id && pdata->instance == hdr.instance) + { + /* 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; + } + + ret = OK; + } + +errout: + /* Free the buffer */ + + kfree(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; + 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; + } + + return ret; +} + +/**************************************************************************** + * Name: mtdconfig_poll + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_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) + { + sem_post(fds->sem); + } + } + + return OK; +} +#endif + +/**************************************************************************** + * 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 = OK; + struct mtdconfig_struct_s *dev; + struct mtd_geometry_s geo; /* Device geometry */ + + dev = (struct mtdconfig_struct_s *)kmalloc(sizeof(struct mtdconfig_struct_s)); + if (dev) + { + /* Initialize the mtdconfig device structure */ + + dev->mtd = mtd; + sem_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) + { + fdbg("MTD ioctl(MTDIOC_GEOMETRY) failed: %d\n", ret); + kfree(dev); + goto errout; + } + + dev->blocksize = geo.blocksize; + dev->neraseblocks = geo.neraseblocks; + dev->erasesize = geo.erasesize; + dev->nblocks = geo.neraseblocks * geo.erasesize / geo.blocksize; + dev->erasedvalue = 0xFF; /* TODO: fix this */ + + (void)register_driver("/dev/config", &mtdconfig_fops, 0666, dev); + } + +errout: + return ret; +} +#endif /* CONFIG_MTD_CONFIG */ diff --git a/include/nuttx/configdata.h b/include/nuttx/configdata.h new file mode 100644 index 0000000000..8b540a49d3 --- /dev/null +++ b/include/nuttx/configdata.h @@ -0,0 +1,143 @@ +/**************************************************************************** + * include/nuttx/configdata.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_CONFIGDATA_H +#define __INCLUDE_NUTTX_CONFIGDATA_H + +/* The configdata device details kernel level services for providing + * application config data from kernel control objects, such as partitions + * on shared MTD devices, etc. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +#ifdef CONFIG_PLATFORM_CONFIGDATA + +/**************************************************************************** + * Pre-Processor Definitions + ****************************************************************************/ +/* Configuration ************************************************************/ +/* CONFIG_AUDIO - Enables Audio driver support + * CONFIG_DEBUG_AUDIO - If enabled (with CONFIG_DEBUG and, optionally, + * CONFIG_DEBUG_VERBOSE), this will generate output that can be used to + * debug Audio drivers. + */ + +/* IOCTL Commands ***********************************************************/ +/* The Audio module uses a standard character driver framework. However, a + * lot of the Audio driver functionality is configured via a device control + * interface, such as sampling rate, volume, data format, etc. + * The Audio ioctl commands are lised below: + * + * CFGDIOC_GETCONFIG - Get a specified Config Data item. + * + * ioctl argument: Pointer to a config_data_s structure to receive the + * config data. All fields of the strucure must be + * specified (i.e. id, instance, pointer and len). + * + * CFGDIOC_SETCONFIG - Set a specified Config Data Item + * + * ioctl argument: Pointer to a config_data_s structure to receive the + * config data. All fields of the strucure must be + * specified (i.e. id, instance, pointer and len). + */ + +#define CFGDIOC_GETCONFIG _CFGDIOC(1) +#define CFGDIOC_SETCONFIG _CFGDIOC(2) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* This structure is used to get and set config data items */ + +struct config_data_s +{ + uint16_t id; /* ID of the config data item */ + int instance; /* Instance of the item */ + FAR uint8_t *configdata; /* Pointer to the config data */ + size_t len; /* Length of the config data buffer */ +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: mtdconfig_register + * + * Description: + * This function binds an instance of an MTD device to the /dev/config + * device. + * + * When this function is called, the MTD device pass in should already + * be initialized appropriately to access the physical device or partition. + * + * Input parameters: + * mtd - Pointer to the MTD device to bind with the /dev/config device + * + * Returned Value: + * Zero on success; a negated errno value on failure. + * + ****************************************************************************/ + +int mtdconfig_register(FAR struct mtd_dev_s *mtd); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_PLATFORM_CONFIGDATA */ +#endif /* __INCLUDE_NUTTX_CONFIGDATA_H */ diff --git a/include/nuttx/fs/ioctl.h b/include/nuttx/fs/ioctl.h index 5c2fbb67b6..7baf2fd49b 100644 --- a/include/nuttx/fs/ioctl.h +++ b/include/nuttx/fs/ioctl.h @@ -1,7 +1,7 @@ /**************************************************************************** * include/nuttx/fs/ioctl.h * - * Copyright (C) 2008, 2009, 2011-2012 Gregory Nutt. All rights reserved. + * Copyright (C) 2008, 2009, 2011-2013 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without @@ -68,7 +68,8 @@ #define _QEIOCBASE (0x0f00) /* Quadrature encoder ioctl commands */ #define _AUDIOIOCBASE (0x1000) /* Audio ioctl commands */ #define _SLCDIOCBASE (0x1100) /* Segment LCD ioctl commands */ -#define _WLIOCBASE (0x1100) /* Wireless modules ioctl commands */ +#define _WLIOCBASE (0x1200) /* Wireless modules ioctl commands */ +#define _CFGDIOCBASE (0x1300) /* Config Data device (app config) ioctl commands */ /* Macros used to manage ioctl commands */ @@ -251,24 +252,30 @@ #define _QEIOCVALID(c) (_IOC_TYPE(c)==_QEIOCBASE) #define _QEIOC(nr) _IOC(_QEIOCBASE,nr) -/* NuttX Audio driver ioctl definitions ************************************/ +/* NuttX Audio driver ioctl definitions *************************************/ /* (see nuttx/audio/audio.h) */ #define _AUDIOIOCVALID(c) (_IOC_TYPE(c)==_AUDIOIOCBASE) #define _AUDIOIOC(nr) _IOC(_AUDIOIOCBASE,nr) -/* Segment LCD driver ioctl definitions ************************************/ +/* Segment LCD driver ioctl definitions *************************************/ /* (see nuttx/include/lcd/slcd_codec.h */ #define _SLCDIOCVALID(c) (_IOC_TYPE(c)==_SLCDIOCBASE) #define _SLCDIOC(nr) _IOC(_SLCDIOCBASE,nr) -/* Wireless driver ioctl definitions ************************************/ +/* Wireless driver ioctl definitions ****************************************/ /* (see nuttx/include/wireless/wireless.h */ #define _WLIOCVALID(c) (_IOC_TYPE(c)==_WLIOCBASE) #define _WLIOC(nr) _IOC(_WLIOCBASE,nr) +/* Application Config Data driver ioctl definitions *************************/ +/* (see nuttx/include/configdata.h */ + +#define _CFGDIOCVALID(c) (_IOC_TYPE(c)==_CFGDIOCBASE) +#define _CFGDIOC(nr) _IOC(_CFGDIOCBASE,nr) + /**************************************************************************** * Public Type Definitions ****************************************************************************/