465 lines
14 KiB
C
465 lines
14 KiB
C
/****************************************************************************
|
|
* fs/spiffs/src/spiffs_mtd.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.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/mtd/mtd.h>
|
|
|
|
#include "spiffs.h"
|
|
#include "spiffs_mtd.h"
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_SPIFFS_MTDDUMP
|
|
static inline void spiffs_mtd_dump(FAR const char *msg,
|
|
FAR const uint8_t *buffer,
|
|
unsigned int buflen)
|
|
{
|
|
lib_dumpbuffer(msg, buffer, buflen);
|
|
}
|
|
|
|
#else
|
|
# define spiffs_mtd_dump(m,b,l)
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_mtd_write
|
|
*
|
|
* Description:
|
|
* Write data to FLASH memory
|
|
*
|
|
* Input Parameters:
|
|
* fs - A reference to the volume structure
|
|
* offset - The byte offset to write to
|
|
* len - The number of bytes to write
|
|
* src - A reference to the bytes to be written
|
|
*
|
|
* Returned Value:
|
|
* On success, the number of bytes written is returned. On failure, a
|
|
* negated errno value is returned.
|
|
*
|
|
****************************************************************************/
|
|
|
|
ssize_t spiffs_mtd_write(FAR struct spiffs_s *fs, off_t offset, size_t len,
|
|
FAR const uint8_t *src)
|
|
{
|
|
size_t remaining;
|
|
ssize_t ret;
|
|
off_t blkmask;
|
|
off_t blkstart;
|
|
off_t blkend;
|
|
int16_t blksize;
|
|
int16_t nblocks;
|
|
int16_t blkoffset;
|
|
|
|
spiffs_mtdinfo("offset=%ld len=%lu\n", (long)offset, (unsigned long)len);
|
|
spiffs_mtd_dump("Writing", src, len);
|
|
|
|
DEBUGASSERT(fs != NULL && fs->mtd != NULL && src != NULL && len > 0);
|
|
|
|
remaining = len;
|
|
|
|
#ifdef CONFIG_MTD_BYTE_WRITE
|
|
/* Try to do the byte write */
|
|
|
|
ret = MTD_WRITE(fs->mtd, offset, len, src);
|
|
if (ret < 0)
|
|
#endif
|
|
{
|
|
/* We will have to do block write(s)
|
|
*
|
|
* blksize - Size of one block. We assume that the block size is a
|
|
* power of two.
|
|
* blkmask - Mask that isolates fractional block bytes.
|
|
* blkoffset - The offset of data in the first block written
|
|
* blkstart - Start block number (aligned down)
|
|
* blkend - End block number (aligned down)
|
|
*/
|
|
|
|
blksize = fs->geo.blocksize;
|
|
blkmask = blksize - 1;
|
|
blkoffset = offset & blkmask;
|
|
blkstart = offset / blksize;
|
|
blkend = (offset + len - 1) / blksize;
|
|
|
|
spiffs_mtdinfo("blkoffset=%d blkstart=%ld blkend=%ld\n",
|
|
blkoffset, (long)blkstart, (long)blkend);
|
|
|
|
/* Check if we have to do a read-modify-write on the first block. We
|
|
* need to do this if the blkoffset is not zero. In that case we need
|
|
* write only the data at the end of the block.
|
|
*/
|
|
|
|
if (blkoffset != 0)
|
|
{
|
|
FAR uint8_t *wptr = fs->mtd_work;
|
|
int16_t maxcopy;
|
|
int16_t nbytes;
|
|
|
|
ret = MTD_BREAD(fs->mtd, blkstart, 1, wptr);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: MTD_BREAD() failed: %zd\n", ret);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
spiffs_mtd_dump("Read leading partial block", wptr, blksize);
|
|
|
|
/* Copy the data in place */
|
|
|
|
maxcopy = blksize - blkoffset;
|
|
nbytes = MIN(remaining, maxcopy);
|
|
|
|
spiffs_mtdinfo("Leading partial block: "
|
|
"blkstart=%jd blkoffset=%d nbytes=%d\n",
|
|
(intmax_t)blkstart, blkoffset, nbytes);
|
|
|
|
memcpy(&wptr[blkoffset], src, nbytes);
|
|
|
|
/* Write back the modified block */
|
|
|
|
spiffs_mtd_dump("Write leading partial block", wptr, blksize);
|
|
|
|
ret = MTD_BWRITE(fs->mtd, blkstart, 1, wptr);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: MTD_BWRITE() failed: %zd\n", ret);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* Update block numbers and counts */
|
|
|
|
blkoffset = 0;
|
|
remaining -= nbytes;
|
|
src += nbytes;
|
|
blkstart++;
|
|
}
|
|
|
|
/* Write all intervening complete blocks... all at once */
|
|
|
|
nblocks = blkend - blkstart;
|
|
if (remaining > 0 && (remaining & blkmask) == 0)
|
|
{
|
|
/* The final block is a complete transfer */
|
|
|
|
nblocks++;
|
|
}
|
|
|
|
spiffs_mtdinfo("Whole blocks=%d blkstart=%lu remaining=%lu\n",
|
|
nblocks, (unsigned long)blkstart,
|
|
(unsigned long)remaining);
|
|
|
|
if (nblocks > 0)
|
|
{
|
|
spiffs_mtd_dump("Write whole blocks", src, blksize * nblocks);
|
|
|
|
ret = MTD_BWRITE(fs->mtd, blkstart, nblocks, src);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: MTD_BWRITE() failed: %zd\n", ret);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
src += (remaining & ~blkmask);
|
|
remaining = (remaining & blkmask);
|
|
|
|
/* The real end block is the next block (if remainder > 0) */
|
|
|
|
blkend++;
|
|
}
|
|
|
|
/* Check if we need to perform a read-modify-write on the final block.
|
|
* If the remaining bytes to write is less then a full block, then we
|
|
* need write only the data at the beginning of the block.
|
|
*/
|
|
|
|
if (remaining > 0)
|
|
{
|
|
ret = MTD_BREAD(fs->mtd, blkend, 1, fs->mtd_work);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: MTD_BREAD() failed: %zd\n", ret);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
spiffs_mtd_dump("Read trailing partial block",
|
|
fs->mtd_work, blksize);
|
|
|
|
/* Copy the data in place */
|
|
|
|
spiffs_mtdinfo("Trailing partial block: "
|
|
"blkend=%ld remaining=%lu\n",
|
|
(long)blkend, (unsigned long)remaining);
|
|
|
|
memcpy(fs->mtd_work, src, remaining);
|
|
|
|
/* Write back the modified block */
|
|
|
|
spiffs_mtd_dump("Write trailing partial block",
|
|
fs->mtd_work, blksize);
|
|
|
|
ret = MTD_BWRITE(fs->mtd, blkend, 1, fs->mtd_work);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: MTD_BWRITE() failed: %zd\n", ret);
|
|
return (ssize_t)ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (ssize_t)len;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_mtd_read
|
|
*
|
|
* Description:
|
|
* Read data from FLASH memory
|
|
*
|
|
* Input Parameters:
|
|
* fs - A reference to the volume structure
|
|
* offset - The byte offset to read from
|
|
* len - The number of bytes to read
|
|
* dest - The user provide location to store the bytes read from FLASH.
|
|
*
|
|
* Returned Value:
|
|
* On success, the number of bytes read is returned. On failure, a
|
|
* negated errno value is returned.
|
|
*
|
|
****************************************************************************/
|
|
|
|
ssize_t spiffs_mtd_read(FAR struct spiffs_s *fs, off_t offset, size_t len,
|
|
FAR uint8_t *dest)
|
|
{
|
|
#ifdef CONFIG_SPIFFS_MTDDUMP
|
|
FAR uint8_t *saved_dest = dest;
|
|
#endif
|
|
size_t remaining;
|
|
ssize_t ret;
|
|
off_t blkmask;
|
|
off_t blkstart;
|
|
off_t blkend;
|
|
int16_t blksize;
|
|
int16_t nblocks;
|
|
int16_t blkoffset;
|
|
|
|
spiffs_mtdinfo("offset=%ld len=%lu\n", (long)offset, (unsigned long)len);
|
|
|
|
DEBUGASSERT(fs != NULL && fs->mtd != NULL && dest != NULL && len > 0);
|
|
|
|
remaining = len;
|
|
|
|
/* Try to do the byte read */
|
|
|
|
ret = MTD_READ(fs->mtd, offset, len, dest);
|
|
if (ret < 0)
|
|
{
|
|
/* We will have to do block read(s)
|
|
*
|
|
* blksize - Size of one block. We assume that the block size is a
|
|
* power of two.
|
|
* blkmask - Mask that isolates fractional block bytes.
|
|
* blkoffset - The offset of data in the first block read.
|
|
* blkstart - Start block number (aligned down)
|
|
* blkend - End block number (aligned down)
|
|
*/
|
|
|
|
blksize = fs->geo.blocksize;
|
|
blkmask = blksize - 1;
|
|
blkoffset = offset & blkmask;
|
|
blkstart = offset / blksize;
|
|
blkend = (offset + len - 1) / blksize;
|
|
|
|
spiffs_mtdinfo("blkoffset=%d blkstart=%ld blkend=%ld\n",
|
|
blkoffset, (long)blkstart, (long)blkend);
|
|
|
|
/* Check if we have to do a partial read on the first block. We
|
|
* need to do this if the blkoffset is not zero. In that case we need
|
|
* read only the data at the end of the block.
|
|
*/
|
|
|
|
if (blkoffset != 0)
|
|
{
|
|
FAR uint8_t *wptr = fs->mtd_work;
|
|
size_t maxcopy;
|
|
ssize_t nbytes;
|
|
|
|
ret = MTD_BREAD(fs->mtd, blkstart, 1, wptr);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: MTD_BREAD() failed: %zd\n", ret);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
spiffs_mtd_dump("Read leading partial block", wptr, blksize);
|
|
|
|
/* Copy the data from the block */
|
|
|
|
maxcopy = blksize - blkoffset;
|
|
nbytes = MIN(remaining, maxcopy);
|
|
|
|
spiffs_mtdinfo("Leading partial block: "
|
|
"blkstart=%jd blkoffset=%d nbytes=%zd\n",
|
|
(intmax_t)blkstart, blkoffset, nbytes);
|
|
|
|
memcpy(dest, &wptr[blkoffset], nbytes);
|
|
|
|
/* Update block numbers and counts */
|
|
|
|
blkoffset = 0;
|
|
remaining -= nbytes;
|
|
dest += nbytes;
|
|
blkstart++;
|
|
}
|
|
|
|
/* Read all intervening complete blocks... all at once */
|
|
|
|
nblocks = blkend - blkstart;
|
|
if (remaining > 0 && (remaining & blkmask) == 0)
|
|
{
|
|
/* The final block is a complete transfer */
|
|
|
|
nblocks++;
|
|
}
|
|
|
|
spiffs_mtdinfo("Whole blocks=%d blkstart=%lu remaining=%lu\n",
|
|
nblocks, (unsigned long)blkstart,
|
|
(unsigned long)remaining);
|
|
|
|
if (nblocks > 0)
|
|
{
|
|
ret = MTD_BREAD(fs->mtd, blkstart, nblocks, dest);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: MTD_BREAD() failed: %zd\n", ret);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
spiffs_mtd_dump("Read whole blocks", dest, blksize * nblocks);
|
|
|
|
dest += (remaining & ~blkmask);
|
|
remaining = (remaining & blkmask);
|
|
|
|
/* The real end block is the next block (if remainder > 0) */
|
|
|
|
blkend++;
|
|
}
|
|
|
|
/* Check if we need to perform a partial read on the final block.
|
|
* If the remaining bytes to write is less then a full block, then we
|
|
* need write only the data at the beginning of the block.
|
|
*/
|
|
|
|
if (remaining > 0)
|
|
{
|
|
ret = MTD_BREAD(fs->mtd, blkend, 1, fs->mtd_work);
|
|
if (ret < 0)
|
|
{
|
|
ferr("ERROR: MTD_BREAD() failed: %zd\n", ret);
|
|
return (ssize_t)ret;
|
|
}
|
|
|
|
/* Copy the data from the block */
|
|
|
|
spiffs_mtdinfo("Trailing partial block: "
|
|
"blkend=%ld remaining=%lu\n",
|
|
(long)blkend, (unsigned long)remaining);
|
|
spiffs_mtd_dump("Read trailing partial block",
|
|
fs->mtd_work, blksize);
|
|
|
|
memcpy(dest, fs->mtd_work, remaining);
|
|
}
|
|
}
|
|
|
|
spiffs_mtd_dump("Read", saved_dest, len);
|
|
return (ssize_t)len;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: spiffs_mtd_erase
|
|
*
|
|
* Description:
|
|
* Read data from FLASH memory
|
|
*
|
|
* Input Parameters:
|
|
* fs - A reference to the volume structure
|
|
* offset - The byte offset to begin erasing
|
|
* len - The number of bytes to erase
|
|
*
|
|
* Returned Value:
|
|
* On success, the number of bytes erased is returned. On failure, a
|
|
* negated errno value is returned.
|
|
*
|
|
****************************************************************************/
|
|
|
|
ssize_t spiffs_mtd_erase(FAR struct spiffs_s *fs, off_t offset, size_t len)
|
|
{
|
|
int16_t erasesize;
|
|
off_t eblkstart;
|
|
off_t eblkend;
|
|
ssize_t nerased;
|
|
|
|
spiffs_mtdinfo("offset=%ld len=%lu\n", (long)offset, (unsigned long)len);
|
|
|
|
DEBUGASSERT(fs != NULL && fs->mtd != NULL);
|
|
|
|
/* We will have to do block read(s)
|
|
*
|
|
* erasesize - Size of one erase block.
|
|
* eblkstart - Start erase block number (aligned down)
|
|
* eblkend - End block number (aligned down)
|
|
*/
|
|
|
|
erasesize = fs->geo.erasesize;
|
|
eblkstart = offset / erasesize;
|
|
eblkend = (offset + len) / erasesize;
|
|
|
|
/* Must be even multiples of the erase block size */
|
|
|
|
DEBUGASSERT(offset == erasesize * eblkstart);
|
|
DEBUGASSERT(len == erasesize * eblkend - offset);
|
|
|
|
nerased = MTD_ERASE(fs->mtd, eblkstart, eblkend - eblkstart);
|
|
if (nerased < 0)
|
|
{
|
|
ferr("ERROR: MTD_ERASE() failed: %zd\n", nerased);
|
|
return nerased;
|
|
}
|
|
|
|
return erasesize * nerased;
|
|
}
|