/**************************************************************************** * 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 #include #include #include #include #include #include #include #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; }