1825 lines
48 KiB
C
1825 lines
48 KiB
C
/****************************************************************************
|
|
* drivers/mtd/mtd_config.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* 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 <fcntl.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
#include <nuttx/fs/fs.h>
|
|
|
|
#include <nuttx/mutex.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 */
|
|
|
|
#ifdef CONFIG_MTD_CONFIG_NAMED
|
|
# define CONFIGDATA_FORMAT_VERSION 1
|
|
# define MTD_ERASED_ID(dev) ((dev)->erasestate)
|
|
#else
|
|
# define CONFIGDATA_FORMAT_VERSION 2
|
|
# define MTD_ERASED_ID(dev) (((dev)->erasestate << 8) | (dev)->erasestate)
|
|
#endif
|
|
#define CONFIGDATA_BLOCK_HDR_SIZE 3
|
|
#define MTD_ERASED_FLAGS(dev) ((dev)->erasestate)
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct mtdconfig_struct_s
|
|
{
|
|
FAR struct mtd_dev_s *mtd; /* Contained MTD interface */
|
|
mutex_t lock; /* Supports mutual exclusion */
|
|
uint32_t blocksize; /* Size of blocks in contained MTD */
|
|
uint32_t erasesize; /* Size of erase block in contained MTD */
|
|
size_t nblocks; /* Number of blocks available */
|
|
size_t neraseblocks; /* Number of erase blocks available */
|
|
uint8_t erasestate; /* Erased value */
|
|
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 g_mtdconfig_fops =
|
|
{
|
|
mtdconfig_open, /* open */
|
|
mtdconfig_close, /* close */
|
|
mtdconfig_read, /* read */
|
|
NULL, /* write */
|
|
NULL, /* seek */
|
|
mtdconfig_ioctl, /* ioctl */
|
|
NULL, /* mmap */
|
|
NULL, /* truncate */
|
|
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 > 0)
|
|
{
|
|
/* 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(dev))
|
|
{
|
|
/* 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(dev))
|
|
{
|
|
#ifdef CONFIG_MTD_CONFIG_NAMED
|
|
if (phdr->name[0] == MTD_ERASED_ID(dev))
|
|
#else
|
|
if (phdr->id == MTD_ERASED_ID(dev))
|
|
#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 = 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] == MTD_ERASED_ID(dev))
|
|
#else
|
|
if (phdr->id == MTD_ERASED_ID(dev))
|
|
#endif
|
|
{
|
|
/* No more data in this erase block. */
|
|
|
|
src_offset = dev->erasesize;
|
|
continue;
|
|
}
|
|
|
|
if (phdr->flags == MTD_ERASED_FLAGS(dev))
|
|
{
|
|
/* 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 = 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(dev))
|
|
{
|
|
/* 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] == MTD_ERASED_ID(dev))
|
|
#else
|
|
if (hdr.id == MTD_ERASED_ID(dev))
|
|
#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 = nxmutex_lock(&dev->lock);
|
|
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 */
|
|
|
|
nxmutex_unlock(&dev->lock);
|
|
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] == MTD_ERASED_ID(dev))
|
|
#else
|
|
if (phdr->id == MTD_ERASED_ID(dev))
|
|
#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(dev))
|
|
{
|
|
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 = 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(dev);
|
|
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] == MTD_ERASED_ID(dev))
|
|
#else
|
|
if (offset > 0 && hdr.id == MTD_ERASED_ID(dev))
|
|
#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] != MTD_ERASED_ID(dev))
|
|
#else
|
|
if (hdr.id != MTD_ERASED_ID(dev))
|
|
#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
|
|
strlcpy(hdr.name, pdata->name, sizeof(hdr.name));
|
|
#else
|
|
hdr.id = pdata->id;
|
|
hdr.instance = pdata->instance;
|
|
#endif
|
|
hdr.len = pdata->len;
|
|
hdr.flags = MTD_ERASED_FLAGS(dev);
|
|
|
|
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(dev);
|
|
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 = 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 */
|
|
|
|
if (pdata->configdata && bytes_to_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 = 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(dev);
|
|
mtdconfig_writebytes(dev, offset, &hdr.flags, sizeof(hdr.flags));
|
|
|
|
ret = OK;
|
|
}
|
|
|
|
/* Free the buffer */
|
|
|
|
kmm_free(dev->buffer);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: mtdconfig_firstconfig
|
|
****************************************************************************/
|
|
|
|
static int mtdconfig_firstconfig(FAR struct mtdconfig_struct_s *dev,
|
|
FAR struct config_data_s *pdata)
|
|
{
|
|
int ret = -ENOENT;
|
|
off_t bytes_to_read;
|
|
struct mtdconfig_header_s hdr;
|
|
|
|
/* Allocate a temp block buffer */
|
|
|
|
dev->buffer = kmm_malloc(dev->blocksize);
|
|
if (dev->buffer == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
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] != MTD_ERASED_ID(dev))
|
|
#else
|
|
if (dev->readoff != 0 && hdr.id != MTD_ERASED_ID(dev))
|
|
#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)
|
|
{
|
|
goto errout;
|
|
}
|
|
|
|
/* Set other return data items */
|
|
|
|
#ifdef CONFIG_MTD_CONFIG_NAMED
|
|
strlcpy(pdata->name, hdr.name, sizeof(pdata->name));
|
|
#else
|
|
pdata->id = hdr.id;
|
|
pdata->instance = hdr.instance;
|
|
#endif
|
|
pdata->len = bytes_to_read;
|
|
}
|
|
else
|
|
{
|
|
ret = -ENOENT;
|
|
}
|
|
|
|
errout:
|
|
|
|
/* Free the buffer */
|
|
|
|
kmm_free(dev->buffer);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: mtdconfig_nextconfig
|
|
****************************************************************************/
|
|
|
|
static int mtdconfig_nextconfig(FAR struct mtdconfig_struct_s *dev,
|
|
FAR struct config_data_s *pdata)
|
|
{
|
|
int ret = -ENOENT;
|
|
off_t bytes_to_read;
|
|
struct mtdconfig_header_s hdr;
|
|
|
|
/* Allocate a temp block buffer */
|
|
|
|
dev->buffer = kmm_malloc(dev->blocksize);
|
|
if (dev->buffer == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = mtdconfig_readbytes(dev, dev->readoff, (FAR uint8_t *)&hdr,
|
|
sizeof(hdr));
|
|
if (ret < 0)
|
|
{
|
|
goto errout;
|
|
}
|
|
|
|
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] != MTD_ERASED_ID(dev))
|
|
#else
|
|
if (dev->readoff != 0 && hdr.id != MTD_ERASED_ID(dev))
|
|
#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)
|
|
{
|
|
goto errout;
|
|
}
|
|
|
|
#ifdef CONFIG_MTD_CONFIG_NAMED
|
|
strlcpy(pdata->name, hdr.name, sizeof(pdata->name));
|
|
#else
|
|
pdata->id = hdr.id;
|
|
pdata->instance = hdr.instance;
|
|
#endif
|
|
pdata->len = bytes_to_read;
|
|
}
|
|
else
|
|
{
|
|
ret = -ENOENT;
|
|
}
|
|
|
|
errout:
|
|
|
|
/* 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;
|
|
int ret = -ENOTTY;
|
|
|
|
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;
|
|
ret = mtdconfig_firstconfig(dev, pdata);
|
|
break;
|
|
|
|
case CFGDIOC_NEXTCONFIG:
|
|
|
|
/* Get the next config item */
|
|
|
|
pdata = (FAR struct config_data_s *)arg;
|
|
ret = mtdconfig_nextconfig(dev, pdata);
|
|
break;
|
|
|
|
case MTDIOC_BULKERASE:
|
|
|
|
/* Call the MTD's ioctl for this */
|
|
|
|
ret = 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)
|
|
{
|
|
poll_notify(&fds, 1, POLLIN | POLLOUT);
|
|
}
|
|
|
|
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;
|
|
|
|
/* 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;
|
|
|
|
/* And query the erase state */
|
|
|
|
ret = MTD_IOCTL(mtd, MTDIOC_ERASESTATE,
|
|
(unsigned long)((uintptr_t)&dev->erasestate));
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: MTD ioctl(MTDIOC_ERASESTATE) failed: %d\n", ret);
|
|
kmm_free(dev);
|
|
goto errout;
|
|
}
|
|
|
|
nxmutex_init(&dev->lock);
|
|
register_driver("/dev/config", &g_mtdconfig_fops, 0666, dev);
|
|
}
|
|
|
|
errout:
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: mtdconfig_unregister
|
|
*
|
|
* Description:
|
|
* Unregister a /dev/config device backed by a MTD.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int mtdconfig_unregister(void)
|
|
{
|
|
int ret;
|
|
struct file file;
|
|
FAR struct inode *inode;
|
|
FAR struct mtdconfig_struct_s *dev;
|
|
|
|
ret = file_open(&file, "/dev/config", O_CLOEXEC);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: open /dev/config failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
inode = file.f_inode;
|
|
dev = inode->i_private;
|
|
nxmutex_destroy(&dev->lock);
|
|
kmm_free(dev);
|
|
|
|
file_close(&file);
|
|
|
|
unregister_driver("/dev/config");
|
|
|
|
return OK;
|
|
}
|
|
|
|
#endif /* CONFIG_MTD_CONFIG */
|