6a3c2aded6
* Simplify EINTR/ECANCEL error handling 1. Add semaphore uninterruptible wait function 2 .Replace semaphore wait loop with a single uninterruptible wait 3. Replace all sem_xxx to nxsem_xxx * Unify the void cast usage 1. Remove void cast for function because many place ignore the returned value witout cast 2. Replace void cast for variable with UNUSED macro
1726 lines
46 KiB
C
1726 lines
46 KiB
C
/****************************************************************************
|
|
* 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 <pettitkd@gmail.com>
|
|
* With Updates from 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.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* 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 <nuttx/config.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <poll.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
#include <nuttx/fs/fs.h>
|
|
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/fs/ioctl.h>
|
|
#include <nuttx/mtd/mtd.h>
|
|
#include <nuttx/mtd/configdata.h>
|
|
|
|
#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 */
|