nuttx/fs/spiffs/src/spiffs_mtd.c
2021-03-09 23:18:28 +08:00

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;
}