Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: DM_FATFileSystem EthernetInterface HTTPClient mbed-rtos mbed-src
Fork of DMSupport by
FileSystems/QSPIFileSystem.cpp
- Committer:
- destinyXfate
- Date:
- 2016-06-02
- Revision:
- 42:39e97a2ea6be
- Parent:
- 33:8a0a99d54bf8
File content as of revision 42:39e97a2ea6be:
/*
* Copyright 2014 Embedded Artists AB
*
* Licensed 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.
*/
#include "QSPIFileSystem.h"
#include "mbed_debug.h"
#include "SPIFI.h"
/******************************************************************************
* Defines and typedefs
*****************************************************************************/
#define QSPI_DBG 0
#define IS_ADDR_IN_SPIFI(__addr) ( (((uint32_t)(__addr)) & 0xff000000) == SPIFI_MEM_BASE )
#define MEM_SIZE (memInfo.memSize)
#define ERASE_SIZE (memInfo.eraseBlockSize)
#define NUM_BLOCKS ((int)(memInfo.numEraseBlocks))
typedef uint32_t toc_entry_t;
#define TOC_BLOCK_ADDR (memInfo.tocBlockAddr) //(SPIFI_MEM_BASE + (NUM_BLOCKS - 1)*ERASE_SIZE)
#define TOC_SIZE (memInfo.tocSizeInBytes) //(sizeof(toc_entry_t) * NUM_BLOCKS)
#define NUM_TOCS (memInfo.numTocs) //((int)(ERASE_SIZE/TOC_SIZE))
#define NUM_TOC_BLOCKS ((NUM_TOCS * TOC_SIZE) / ERASE_SIZE)
#define NUM_TOC_ENTRIES ((int)(TOC_SIZE/sizeof(toc_entry_t)))
#define TOC_UNUSED (0xffffffff)
#define TOC_MAX (NUM_BLOCKS - 1)
#define TOC_VALID_MASK (1UL<<31)
#define TOC_RESERVED_MASK (1UL<<30)
#define TOC_USED_MASK (1UL<<29)
#define TOC_FILE_MASK (1UL<<28)
#define TOC_FSIZE_MASK (0x3ffff)
#define TOC_MANDAT_SET_MASK (0x0ffc0000)
#define MANDATORY_BITS_SET(__v) (((__v)&TOC_MANDAT_SET_MASK) == TOC_MANDAT_SET_MASK)
#define VALID_TOC_ENTRY(__v) (((__v)&TOC_VALID_MASK) == 0)
#define USED_TOC_ENTRY(__v) (VALID_TOC_ENTRY(__v) && (((__v)&TOC_USED_MASK) == 0))
#define TOC_IS_FILE(__v) (USED_TOC_ENTRY(__v) && (((__v)&TOC_FILE_MASK) == 0))
#define TOC_IS_RESERVED(__v) (VALID_TOC_ENTRY(__v) && (((__v)&TOC_RESERVED_MASK) == 0))
#define FILESIZE(__v) ((__v) & 0x3ffff)
#define FS_MIN(__a, __b) (((__a) < (__b)) ? (__a) : (__b))
// Mask to compare the different access modes. In LPCXpresso this was defined
// but not in uVision
#ifndef O_ACCMODE
#define O_ACCMODE (O_RDONLY | O_WRONLY | O_RDWR)
#endif
/*
* The file header currently only consists of the filename (including path)
* and the string terminating character, but by separating the file name
* length from the size of the header in the code it allows future additions
* to the header without too much code modification.
*/
#define HEADER_DNAME_MAXLEN (250)
#define HEADER_FNAME_STRLEN (HEADER_DNAME_MAXLEN + 5)
#define HEADER_FNAME_LEN (HEADER_FNAME_STRLEN + 1)
#define HEADER_LEN (HEADER_FNAME_LEN) // only filename in header for now
typedef enum
{
FS_OK,
FS_ERR_NOT_FORMATTED,
FS_ERR_NO_FILE,
FS_ERR_FILE_EXIST,
FS_ERR_INVALID_PARAM,
FS_ERR_DISK_FULL,
FS_ERR_SPIFI,
FS_ERR_MALLOC,
// FS_ERR_SPIFI_* return codes are listed in the User's Manual
// as possible return values from spifi_init(), spifi_program()
// and spifi_erase() calls.
FS_ERR_SPIFI_INTERNAL_ERROR = 0x20002, // 0x20002, Internal error in API code
FS_ERR_SPIFI_TIMEOUT = 0x20003, // 0x20003, Time-out waiting for program or erase to begin: protection could not be removed.
FS_ERR_SPIFI_OPERAND = 0x20004, // 0x20004, Operand error (i.e. invalid params)
FS_ERR_SPIFI_STATUS = 0x20005, // 0x20005, Device status error
FS_ERR_SPIFI_EXT_DEVICE_ID = 0x20006, // 0x20006, Unknown extended device ID value
FS_ERR_SPIFI_DEVICE_ID = 0x20007, // 0x20007, Unknown device ID code
FS_ERR_SPIFI_DEVICE_TYPE = 0x20008, // 0x20008, Unknown device type code
FS_ERR_SPIFI_MANUFACTURER = 0x20009, // 0x20009, Unknown manufacturer code
FS_ERR_SPIFI_INVALID_JDEC_ID = 0x2000A, // 0x2000A, No operative serial flash (JEDEC ID all zeroes or all ones)
FS_ERR_SPIFI_ERASE_CONFLICT = 0x2000B, // 0x2000B, S_CALLER_ERASE is included in options, and erasure is required.
FS_ERR_SPIFI_VERIFICATION, // other, Other non-zero values can occur if options selects verification.
// They will be the address in the SPIFI memory area at which the first discrepancy was found.
} fresult;
// The number of times to re-attempt a spifi_program() or spifi_erase()
// if the last one reported a verification error.
#define NUM_VERIFICATION_ATTEMPTS (1)
typedef struct
{
uint32_t memSize;
uint32_t eraseBlockSize;
uint32_t numEraseBlocks;
uint32_t tocBlockAddr;
uint32_t numTocs;
uint32_t tocSizeInBytes;
char memName[30];
} meminfo_t;
typedef struct
{
int tocIdx;
uint32_t size;
uint16_t lastBlock;
} fileHandle_t;
/******************************************************************************
* Local variables
*****************************************************************************/
static toc_entry_t* TOC = NULL;
static int activeTOC = -1;
static const SPIFI_RTNS *spifi = NULL;
static SPIFIobj* obj;
static SPIFIopers opers;
static char addr_conflict_buff[PROG_SIZE];
static meminfo_t memInfo = {0,0,0,0,0,0,{0}};
/******************************************************************************
* Forward Declarations of Local Functions
*****************************************************************************/
static fresult qspifs_init();
static fresult qspifs_translateSpifiError(int rc);
static fresult qspifs_readTOC(void);
static fresult qspifs_saveTOC(void);
static fresult qspifs_findFile(const char* filename, fileHandle_t* fh);
static fresult qspifs_fileSize(int tocIdx, uint32_t* pSize);
static fresult qspifs_eraseBlock(int block);
static fresult qspifs_allocateFile(const char* filename, int neededBlocks, int* pTocIdx);
static void qspifs_deleteFile(fileHandle_t* fh);
static fresult qspifs_allocateSpace(fileHandle_t* fh, uint32_t size);
static fresult qspifs_format(unsigned int minReservedBytes);
static fresult qspifs_write(fileHandle_t* fh, const uint8_t * const pData, uint32_t size);
static bool qspifs_startsWith(const char* prefix, const char* str);
/******************************************************************************
* Local Functions
*****************************************************************************/
/******************************************************************************
*
* Description:
* Initializes spifi, identifies the chip and reads the file system's
* table of content.
*
* Params:
* None
*
* Returns:
* FS_OK or one of the FS_ERR_* error codes
*
*****************************************************************************/
static fresult qspifs_init()
{
if (spifi == NULL) {
SPIFI::SpifiError err;
err = SPIFI::instance().init();
if (err != SPIFI::Ok) {
spifi = NULL;
return FS_ERR_SPIFI;
}
SPIFI::instance().internalData(&obj, &spifi);
/* Make sure it is a tested flash module */
switch (SPIFI::instance().device()) {
case SPIFI::Spansion_S25FL032:
strcpy(memInfo.memName, "Spansion S25FL032");
break;
case SPIFI::Winbond_W25Q64FV:
strcpy(memInfo.memName, "Winbond W25Q64FV");
break;
case SPIFI::Macronix_MX25L6435E:
strcpy(memInfo.memName, "Macronix_MX25L6435E");
break;
case SPIFI::Macronix_MX25L12835F:
strcpy(memInfo.memName, "Macronix_MX25L12835F");
break;
case SPIFI::SpecifiedInBios:
strcpy(memInfo.memName, "Specified in BIOS");
break;
case SPIFI::UnknownDevice:
default:
debug("INIT: Memory is unknown and may not work as expected\n");
strcpy(memInfo.memName, "Unknown - check ID");
/*
* If this happens, check the manufacturer and device information
* and compare with the data sheet for your chip. Also make sure
* that the sector sizes are the same (i.e. 64KB) for your chip.
* If everything is the same then add an exception for your chip.
*/
break;
}
/*
* The size of the erase blocks gives the number of erase blocks on the
* flash which in turn dictates the size of each TOC. For a flash with
* a large erase block size (Spansion_S25FL032 has 64Kb blocks) the
* the number of blocks is low, resulting in a small TOC with many fitting
* inside a single erase block.
* For a large flash with small erase block size (Macronix_MX25L12835F is
* 16Mb with 4Kb erase blocks) the number of blocks is high, resulting in
* a large TOC which doesn't even fit in one erase block.
*
* 4Mb, 64Kb erase block => TOC size 256b => 256 TOC/erase block
* 8Mb, 4Kb erase block => TOC size 8192b => 0.5 TOC/erase block
* 16Mb, 4Kb erase block => TOC size 16384b => 0.25 TOC/erase block
*
* In all cases we select a number of TOCs so that we use 64Kb of the
* memory for them. This will reduce wear on the flash as the TOC can
* be moved around.
*/
memInfo.memSize = obj->memSize;
memInfo.eraseBlockSize = SPIFI::instance().eraseBlockSize();
memInfo.numEraseBlocks = memInfo.memSize / memInfo.eraseBlockSize;
memInfo.tocSizeInBytes = sizeof(toc_entry_t) * memInfo.numEraseBlocks;
memInfo.numTocs = (64*1024) / memInfo.tocSizeInBytes;
if (memInfo.numTocs < 2) {
// just in case a unknown size combination appears
memInfo.numTocs = 2;
}
memInfo.tocBlockAddr = SPIFI_MEM_BASE + (NUM_BLOCKS * ERASE_SIZE) - (memInfo.numTocs * memInfo.tocSizeInBytes);
debug_if(QSPI_DBG, "INIT: Found %dMB %s\n", memInfo.memSize/0x100000, memInfo.memName);
if (TOC != NULL) {
delete TOC;
}
TOC = (toc_entry_t*)malloc(TOC_SIZE);
if (TOC == NULL) {
debug_if(QSPI_DBG, "INIT: Failed to allocate memory for TOC\n");
spifi = NULL;
return FS_ERR_MALLOC;
}
}
if (activeTOC == -1)
{
return qspifs_readTOC();
}
return FS_OK;
}
/******************************************************************************
*
* Description:
* Converts the return value from one of the spifi_init(), spifi_program()
* or spifi_erase() calls into a FS_* error code to simplify it for the
* fs_qspi API user.
* This function also attempts to detect the verification failure error.
* When a verification error occurs the spifi_* functions returns the
* conflicting address and not an error code. As this can be any address
* it is difficult to test but this function converts it into the
* FS_ERR_SPIFI_VERIFICATION error code which can be tested against.
*
* Params:
* [in] rc - The return code from any of the spifi_* functions
*
* Returns:
* FS_OK or one of the FS_ERR_* error codes
*
*****************************************************************************/
static fresult qspifs_translateSpifiError(int rc)
{
fresult res;
if (rc == 0)
{
res = FS_OK;
}
else if ((rc >= FS_ERR_SPIFI_INTERNAL_ERROR) && (rc <= FS_ERR_SPIFI_ERASE_CONFLICT))
{
// This is a known error code
res = (fresult)rc;
}
else if (opers.options & (S_VERIFY_PROG | S_VERIFY_ERASE))
{
// As verification was selected and rc is not in the list of known
// codes this falls into this category in the User's Manual:
//
// "Other non-zero values can occur if options selects verification.
// They will be the address in the SPIFI memory area at which the
// first discrepancy was found."
res = FS_ERR_SPIFI_VERIFICATION;
}
else
{
// Should never happen :-) as all listed error codes are covered but
// to be on the safe side and not interpret this as a success, a generic
// error is set.
res = FS_ERR_SPIFI;
}
return res;
}
/******************************************************************************
*
* Description:
* Reads the table of contents (TOC). The TOC is stored in the last erase
* block on the QSPI flash. As the QSPI flash is not exactly RW (might
* require erasing before writing) the TOC is relocated inside the erase
* block everytime it is saved (see saveTOC()). The currently valid TOC
* is allways the last one stored.
*
* Params:
* None
*
* Returns:
* FS_OK or one of the FS_ERR_* error codes
*
*****************************************************************************/
static fresult qspifs_readTOC(void)
{
int i, j;
toc_entry_t* p;
uint8_t invalid = 0;
int lastValid = -1;
// Search for the first unused TOC, keeping track of the valid
// ones as we go.
for (i = 0; (i < (int)NUM_TOCS) && !invalid; i++)
{
p = (toc_entry_t*)(TOC_BLOCK_ADDR + i*TOC_SIZE);
for (j = 0; j < NUM_BLOCKS; j++)
{
if (!VALID_TOC_ENTRY(*p) || !MANDATORY_BITS_SET(*p))
{
// invalid TOC entry, stop looking
invalid = 1;
break;
}
p++;
}
if (!invalid)
{
// this TOC was ok, but perhaps there is a newer one?
lastValid = i;
}
}
if (lastValid == -1)
{
// no valid TOCs on the flash
return FS_ERR_NOT_FORMATTED;
}
else
{
// previous entry was ok so use that
activeTOC = lastValid;
p = (toc_entry_t*)(TOC_BLOCK_ADDR + activeTOC*TOC_SIZE);
memcpy(TOC, p, TOC_SIZE);
return FS_OK;
}
}
/******************************************************************************
*
* Description:
* Saves the table of contents (TOC). The TOC is stored in the last erase
* block on the QSPI flash. As the QSPI flash is not exactly RW (might
* require erasing before writing) the TOC is first compared with what is
* stored in the QSPI flash and if there are no changes or all changes
* only require bit changes 1->0 then the current TOC can be overwritten.
* If bit value changes 0->1 are required then the current stored TOC
* cannot be overwritten and the new TOC is instead stored in the next
* available space. If the entire last block is filled then it is erased
* and the new TOC is placed at the start of it.
*
* Params:
* None
*
* Returns:
* FS_OK or one of the FS_ERR_* error codes
*
*****************************************************************************/
static fresult qspifs_saveTOC(void)
{
int i, rc = 0;
uint32_t* pSrc;
uint32_t* pDest;
uint32_t tmp;
uint8_t identical = 1;
// active TOC same as the one we want to save?
pSrc = (uint32_t*)TOC;
pDest = (uint32_t*)(TOC_BLOCK_ADDR + activeTOC*TOC_SIZE);
for (i = 0; i < NUM_TOC_ENTRIES; i++)
{
if (*pSrc != *pDest)
{
identical = 0;
tmp = ((*pDest) ^ (*pSrc)) & (*pSrc);
if (tmp > 0)
{
// found a change that contains 0->1 bit modification which
// requires erasing or a new location
activeTOC = (activeTOC + 1)%NUM_TOCS;
if (activeTOC == 0)
{
// no more free TOCs so an erase is needed
#if 0
opers.options &= ~S_CALLER_ERASE;
opers.options |= S_FORCE_ERASE;
#else
opers.dest = (char *) TOC_BLOCK_ADDR;
opers.length = TOC_SIZE * NUM_TOCS;
opers.scratch = NULL;
opers.protect = 0;
opers.options = S_NO_VERIFY;
rc = spifi->spifi_erase(obj, &opers);
if (rc) {
return qspifs_translateSpifiError(rc);
}
#endif
}
break;
}
}
pSrc++;
pDest++;
}
if (!identical)
{
opers.length = FS_MIN(TOC_SIZE, PROG_SIZE);
opers.scratch = NULL;
opers.protect = 0;
opers.options = S_VERIFY_PROG | S_CALLER_ERASE;
for (uint32_t i = 0; i < (TOC_SIZE / PROG_SIZE); i++)
{
opers.dest = (char *)(TOC_BLOCK_ADDR + activeTOC*TOC_SIZE + i*PROG_SIZE);
rc = spifi->spifi_program(obj, ((char*)TOC)+i*PROG_SIZE, &opers);
if (rc)
{
break;
}
}
return qspifs_translateSpifiError(rc);
}
return FS_OK;
}
/******************************************************************************
*
* Description:
* Searches the file system for a file with the specified name and
* (if found) returns the file's position in the TOC.
*
* Note that the content of fh is only valid if FS_OK is returned.
*
* Params:
* [in] filename - The name of the file to find
* [out] fh - The handle with the file information
*
* Returns:
* FS_OK or one of the FS_ERR_* error codes
*
*****************************************************************************/
static fresult qspifs_findFile(const char* filename, fileHandle_t* fh)
{
int i;
if (activeTOC == -1)
{
return FS_ERR_NOT_FORMATTED;
}
// Look at all blocks except for the reserved ones
for (i = 0; i < NUM_BLOCKS; i++)
{
if (TOC_IS_FILE(TOC[i]) && !TOC_IS_RESERVED(TOC[i]))
{
// found a file, see if name matches
char* p = (char*)(SPIFI_MEM_BASE + i*ERASE_SIZE);
if (strncmp(filename, p, HEADER_FNAME_LEN) == 0)
{
// found a matching name
fh->tocIdx = i;
fresult res = qspifs_fileSize(fh->tocIdx, &fh->size);
if (res == FS_OK) {
fh->lastBlock = fh->tocIdx + ((fh->size + HEADER_LEN)/ ERASE_SIZE);
}
return FS_OK;
}
}
}
return FS_ERR_NO_FILE;
}
/******************************************************************************
*
* Description:
* Calculates and returns the file's size.
*
* Note that the content of pSize is only valid if FS_OK is returned.
*
* Params:
* [in] tocIdx - The file's position in the TOC
* [out] pSize - The file's size
*
* Returns:
* FS_OK or one of the FS_ERR_* error codes
*
*****************************************************************************/
static fresult qspifs_fileSize(int tocIdx, uint32_t* pSize)
{
int i;
if (tocIdx < 0 || tocIdx > NUM_BLOCKS || !TOC_IS_FILE(TOC[tocIdx]))
{
return FS_ERR_NO_FILE;
}
*pSize = 0;
// A file is always stored in sequential blocks so start with the files
// first block and as long as it is full continue sum up the occupied
// block sizes. As soon as a non-full block is found that must be the
// file's last block.
for (i = tocIdx; i < NUM_BLOCKS; i++)
{
*pSize += FILESIZE(TOC[i]);
if (FILESIZE(TOC[i]) < ERASE_SIZE)
{
// last block in chain
break;
}
}
// Remove the filename header from the file's size
*pSize -= HEADER_LEN;
return FS_OK;
}
/******************************************************************************
*
* Description:
* Erases everything in one block on the QSPI flash.
*
* Params:
* [in] block - The block's number
*
* Returns:
* FS_OK or one of the FS_ERR_* error codes
*
*****************************************************************************/
static fresult qspifs_eraseBlock(int block)
{
opers.dest = (char *)(block * ERASE_SIZE);
opers.length = ERASE_SIZE;
opers.scratch = NULL;
opers.protect = 0;
opers.options = S_NO_VERIFY;
return qspifs_translateSpifiError(spifi->spifi_erase (obj, &opers));
}
/******************************************************************************
*
* Description:
* Creates a new file if there is enough space for it on the file system.
* The TOC is searched for a unused sequence of blocks of at least the
* needed size. That block is marked as used and the file's name is stored
* in the first bytes of the file's first block.
*
* Note: The filename will not be tested for uniqueness.
* Note: The value of pTocIdx will only be valid if FS_OK is returned.
*
* Params:
* [in] filename - The name of the new file
* [in] neededBlocks - The number of blocks (in sequence) to allocate
* [out] pTocIdx - The new file's position in the TOC
*
* Returns:
* FS_OK or one of the FS_ERR_* error codes
*
*****************************************************************************/
static fresult qspifs_allocateFile(const char* filename, int neededBlocks, int* pTocIdx)
{
int i, rc;
if (activeTOC == -1)
{
return FS_ERR_NOT_FORMATTED;
}
// Look at all blocks except for the reserved ones
for (i = 0; i < NUM_BLOCKS; i++)
{
//TODO: Improve search to use gaps to avoid having to move files
// that are written to
if (!USED_TOC_ENTRY(TOC[i]) && !TOC_IS_RESERVED(TOC[i]))
{
int j;
for (j = 1; j < neededBlocks; j++)
{
if (USED_TOC_ENTRY(TOC[i+j]) || TOC_IS_RESERVED(TOC[i+j]))
{
// not enough free blocks in sequence, skip past these
// tested entries and continue searching
i += j;
break;
}
}
if (j == neededBlocks)
{
const char* pSrc = filename;
if (IS_ADDR_IN_SPIFI(filename))
{
// The SPIFI ROM driver cannot write data from SPIFI into
// SPIFI (i.e. cannot read and write at the same time).
// The workaround is to copy the source data into a buffer
// in local memory and use that as source for the write
// instead.
memcpy(addr_conflict_buff, filename, strlen(filename)+1);
pSrc = addr_conflict_buff;
}
// Erase the new file's first block and store the filename at the
// start of it
opers.length = strlen(pSrc)+1;
opers.scratch = NULL;
opers.protect = 0;
opers.options = S_VERIFY_PROG | S_FORCE_ERASE;// S_CALLER_ERASE;
opers.dest = (char *)(i*ERASE_SIZE);
rc = spifi->spifi_program(obj, (char*)pSrc, &opers);
if (rc) {
return qspifs_translateSpifiError(rc);
}
TOC[i] &= ~(TOC_VALID_MASK | TOC_USED_MASK | TOC_FILE_MASK | TOC_FSIZE_MASK);
TOC[i] |= HEADER_LEN;
*pTocIdx = i;
return FS_OK;
}
}
}
return FS_ERR_DISK_FULL;
}
/******************************************************************************
*
* Description:
* Deletes the specified file by marking all its blocks as unused in
* the TOC.
*
* Note: The deleted blocks are not erased here - that is done when they
* are allocated the next time.
*
* Params:
* [in] fh - The file handle with information about what to delete
*
* Returns:
* None
*
*****************************************************************************/
static void qspifs_deleteFile(fileHandle_t* fh)
{
int i;
for (i = fh->lastBlock; i >= fh->tocIdx; i--)
{
TOC[i] = ~TOC_VALID_MASK;
}
}
/******************************************************************************
*
* Description:
* Ensures that the specified file can grow to the wanted size.
* If the file size will increase enough to need one or more new blocks
* and there isn't enough space then an attempt is made to move the
* current file to a large enough space somewhere else.
*
* If there are more free block(s) at the end of the file then it is not
* moved and instead those blocks are marked as used.
*
* Note: The filename will not be tested for uniqueness.
* Note: The value of pTocIdx will only be valid if FS_OK is returned.
*
* Params:
* [in/out] fh - The current file handle, might be updated after a move
* [in] size - The wanted new size
*
* Returns:
* FS_OK or one of the FS_ERR_* error codes
*
*****************************************************************************/
static fresult qspifs_allocateSpace(fileHandle_t* fh, uint32_t size)
{
uint16_t oldNumBlocks = (fh->size + HEADER_LEN) / ERASE_SIZE;
uint16_t newNumBlocks = (fh->size + HEADER_LEN + size) / ERASE_SIZE;
uint16_t numNeeded = newNumBlocks - oldNumBlocks;
fresult res = FS_OK;
if (numNeeded > 0)
{
uint16_t i;
for (i = 0; i < numNeeded; i++)
{
if (USED_TOC_ENTRY(TOC[fh->tocIdx + oldNumBlocks + 1 + i]) ||
TOC_IS_RESERVED(TOC[fh->tocIdx + oldNumBlocks + 1 + i]))
{
fileHandle_t fhNew;
// have to move the chain
char* filename = (char*)(SPIFI_MEM_BASE + fh->tocIdx * ERASE_SIZE);
res = qspifs_allocateFile(filename, newNumBlocks, &(fhNew.tocIdx));
if (res == FS_OK)
{
// copy data
fhNew.lastBlock = fhNew.tocIdx;
fhNew.size = 0;
res = qspifs_write(&fhNew, (uint8_t*)(SPIFI_MEM_BASE + fh->tocIdx * ERASE_SIZE + HEADER_LEN), fh->size);
}
if (res == FS_OK)
{
// remove old entries
qspifs_deleteFile(fh);
// modify old handle to point to new information
fh->lastBlock = fhNew.lastBlock;
fh->size = fhNew.size;
fh->tocIdx = fhNew.tocIdx;
}
if (res != FS_OK)
{
// not possible to relocate the file => abort
return res;
}
break;
}
}
// have space that is unused, so mark as used
for (i = 0; i < numNeeded; i++)
{
int tocIdx = fh->tocIdx + oldNumBlocks + 1 + i;
TOC[tocIdx] &= ~TOC_USED_MASK;
qspifs_eraseBlock(tocIdx);
}
}
return res;
}
/******************************************************************************
*
* Description:
* Adds a file system to the QSPI flash. The entire flash will be erase
* except for the minReservedBytes first bytes. That reserved area (rounded
* up to the closest even multiple of the erase block size) can be used
* for anything and will never be touched by the file system. That area is
* typically used for executing programs from when the internal flash is
* full.
*
* The file system will have a table of content (TOC) placed at the start
* of the last erase block on the flash.
*
* Params:
* [in] minReservedBytes - The number of bytes to ignore at the start of
* the flash.
*
* Returns:
* FS_OK on success or one of the FS_ERR_* on failure
*
*****************************************************************************/
static fresult qspifs_format(unsigned int minReservedBytes)
{
int rc;
uint32_t i, numReserved = 0;
if (minReservedBytes > 0) {
numReserved = (minReservedBytes + ERASE_SIZE - 1) / ERASE_SIZE;
if (numReserved >= (uint32_t)(NUM_BLOCKS - 2)) {
// Too many of the erase blocks are reserved - not even room for one file
return FS_ERR_INVALID_PARAM;
}
}
#if 0 // works but is really slow
// Erase all non-reserved blocks
for (i = numReserved; i < NUM_BLOCKS; i++) {
opers.dest = (char *) (i * ERASE_SIZE);
opers.length = ERASE_SIZE;
opers.scratch = NULL;
opers.protect = 0;
opers.options = S_NO_VERIFY;
rc = spifi->spifi_erase(&obj, &opers);
if (rc) {
return qspifs_translateSpifiError(rc);
}
}
#else
// Erase all non-reserved blocks
opers.dest = (char *) (numReserved * ERASE_SIZE);
opers.length = MEM_SIZE - (numReserved * ERASE_SIZE);
opers.scratch = NULL;
opers.protect = 0;
opers.options = S_NO_VERIFY;
rc = spifi->spifi_erase(obj, &opers);
if (rc) {
return qspifs_translateSpifiError(rc);
}
#endif
// Create the TOC, mark requested blocks as reserved and mark the TOC's
// block(s) as reserved as well.
for (i = 0; i < numReserved; i++) {
TOC[i] = ~(TOC_VALID_MASK | TOC_RESERVED_MASK);
}
for (; i < (NUM_BLOCKS - NUM_TOC_BLOCKS); i++) {
TOC[i] = ~TOC_VALID_MASK;
}
for (; i < (uint32_t)NUM_BLOCKS; i++) {
TOC[i] = ~(TOC_VALID_MASK | TOC_RESERVED_MASK);
}
// Save the TOC in the last block
activeTOC = 0;
fresult res = qspifs_saveTOC();
if (res != FS_OK) {
activeTOC = -1;
return res;
}
// opers.dest = (char *) TOC_BLOCK_ADDR;
// opers.length = TOC_SIZE;
// opers.scratch = NULL;
// opers.protect = 0;
// opers.options = S_VERIFY_PROG | S_CALLER_ERASE;
// rc = spifi->spifi_program(&obj, (char*) TOC, &opers);
// if (rc) {
// return qspifs_translateSpifiError(rc);
// }
// Read back TOC to be sure it worked
return qspifs_readTOC();
}
/******************************************************************************
*
* Description:
* Deletes all files on the file system. This is a "quick format" that
* leaves all blocks untouched and only modifies the TOC. Any reserved
* blocks are kept reserved.
*
* The purpose of this function is to make it easy to clear the file system
* without going through a time consuming complete erase every time.
*
* Params:
* None
*
* Returns:
* FS_OK on success or one of the FS_ERR_* on failure
*
*****************************************************************************/
// static fresult qspifs_deleteAllFiles(void)
// {
// for (int i = 0; i < NUM_BLOCKS; i++)
// {
// if (!TOC_IS_RESERVED(TOC[i])) {
// TOC[i] = ~TOC_VALID_MASK;
// }
// }
//
// return qspifs_saveTOC();
// }
/******************************************************************************
*
* Description:
* Appends the data to the end of the file.
*
* Params:
* [in] fh - The handle to the file as returned from fs_open_append()
* [in] pData - The data to save
* [in] size - Number of bytes to save
*
* Returns:
* FS_OK on success or one of the FS_ERR_* on failure
*
*****************************************************************************/
static fresult qspifs_write(fileHandle_t* fh, const uint8_t * const pData, uint32_t size)
{
uint32_t left = size;
const uint8_t* pSrc = pData;
int rc, i;
fresult res;
int failed_attempts = 0;
do {
res = qspifs_allocateSpace(fh, size);
if (res != FS_OK) {
break;
}
opers.dest = (char *) (SPIFI_MEM_BASE + fh->tocIdx * ERASE_SIZE
+ HEADER_LEN + fh->size);
opers.scratch = NULL;
opers.protect = 0;
opers.options = S_VERIFY_PROG; // | S_FORCE_ERASE;
while ((res == FS_OK) && (left > 0)) {
if (left >= PROG_SIZE) {
opers.length = PROG_SIZE;
} else {
opers.length = left;
}
if (IS_ADDR_IN_SPIFI(pData)) {
memcpy(addr_conflict_buff, pSrc, opers.length);
rc = spifi->spifi_program(obj, addr_conflict_buff, &opers);
} else {
rc = spifi->spifi_program(obj, (char*) pSrc, &opers);
}
res = qspifs_translateSpifiError(rc);
if ((res == FS_ERR_SPIFI_VERIFICATION)
&& (++failed_attempts <= NUM_VERIFICATION_ATTEMPTS)) {
// The verification process failed.
// In all the observed occasions re-running the exact same
// spifi_program command again yielded a 0 as a return value
// the second time.
// The quick'N'dirty fix is to re-run that program instruction
// NUM_VERIFICATION_ATTEMPTS more time(s) when this happens.
res = FS_OK;
continue;
}
if (res != FS_OK) {
// Got an error but cannot exit this function here as parts of the data
// (previous loops?) may have been written so the TOC must be updated.
break;
}
pSrc += opers.length;
opers.dest += opers.length;
left -= opers.length;
failed_attempts = 0;
}
// update file information
fh->size = fh->size + size - left;
fh->lastBlock = fh->tocIdx + ((fh->size + HEADER_LEN)/ ERASE_SIZE);
left = fh->size + HEADER_LEN;
for (i = 0; i <= (fh->lastBlock - fh->tocIdx); i++) {
TOC[fh->tocIdx + i] &= ~TOC_FSIZE_MASK;
TOC[fh->tocIdx + i] |= FS_MIN(ERASE_SIZE, left);
left -= FILESIZE(TOC[fh->tocIdx + i]);
}
if (res == FS_OK) {
res = qspifs_saveTOC();
} else {
// Want to save the TOC but not overwrite the previous error with
// a possibly successful TOC saving thus making it seem like there
// was no error
qspifs_saveTOC();
}
} while (0);
return res;
}
/******************************************************************************
*
* Description:
* Tests if str starts with prefix. A prefix of NULL or an empty string
* results in a positive result regardless of the content of str.
*
* Params:
* [in] prefix - The prefix to look for
* [in] str - The string to search for prefix
*
* Returns:
* True if the specified string starts with prefix
*
*****************************************************************************/
static bool qspifs_startsWith(const char* prefix, const char* str)
{
const char* pA = prefix;
const char* pB = str;
if (pA == NULL)
{
return true;
}
for (; *pA != '\0'; pA++, pB++)
{
if (*pB != *pA)
{
return false;
}
}
return true;
}
/******************************************************************************
* Class Declarations
*****************************************************************************/
class QSPIFileHandle : public FileHandle {
public:
QSPIFileHandle(fileHandle_t* handle, int flags);
virtual int close();
virtual ssize_t write(const void *buffer, size_t length);
virtual ssize_t read(void *buffer, size_t length);
virtual int isatty();
virtual off_t lseek(off_t position, int whence);
virtual int fsync();
virtual off_t flen();
protected:
fileHandle_t fh;
bool allowReading;
bool allowWriting;
uint32_t pos;
};
class QSPIDirHandle : public DirHandle {
public:
static QSPIDirHandle* openDir(const char* dirname);
virtual ~QSPIDirHandle();
virtual int closedir();
virtual struct dirent *readdir();
virtual void rewinddir();
private:
QSPIDirHandle(const char* dirname);
int findFileWithPrefix(const char* prefix, int startTOCIdx, int maxTOCIdx) const;
protected:
char* dirname;
int nextTocIdx;
bool isRoot;
struct dirent cur_entry;
};
/******************************************************************************
* Class Implementations
*****************************************************************************/
QSPIFileHandle::QSPIFileHandle(fileHandle_t* handle, int flags)
{
fh = *handle;
int accmode = (flags & O_ACCMODE);
allowReading = (accmode == O_RDONLY) || (accmode == O_RDWR);
allowWriting = (accmode == O_WRONLY) || (accmode == O_RDWR) || (flags & O_APPEND);
pos = 0;
}
int QSPIFileHandle::close()
{
delete this;
return 0;
}
ssize_t QSPIFileHandle::write(const void *buffer, size_t length)
{
if (!allowWriting) {
return -1;
}
fresult res = qspifs_write(&fh, (const uint8_t*)buffer, length);
if (res == FS_OK) {
// A write is always 'append' in this file system so the file
// position is always end of file after a write
pos = fh.size;
return length;
}
return -1;
}
ssize_t QSPIFileHandle::read(void *buffer, size_t length)
{
if (!allowReading) {
return -1;
}
if (pos >= fh.size) {
return 0;
}
uint32_t len = FS_MIN(length, fh.size - pos);
const char* pData = (const char*)(SPIFI_MEM_BASE + fh.tocIdx*ERASE_SIZE + HEADER_LEN + pos);
memcpy(buffer, pData, len);
pos += len;
return len;
}
int QSPIFileHandle::isatty()
{
return 0;
}
off_t QSPIFileHandle::lseek(off_t position, int whence)
{
switch (whence) {
case SEEK_SET:
pos = position;
break;
case SEEK_CUR:
pos += position;
break;
case SEEK_END:
pos = fh.size + position;
break;
default:
return -1;
}
return pos;
}
int QSPIFileHandle::fsync()
{
return 0; // always synced
}
off_t QSPIFileHandle::flen()
{
return fh.size;
}
QSPIDirHandle::QSPIDirHandle(const char* dirname) {
size_t len = strlen(dirname);
this->dirname = (char*)malloc(len + 2); // null termination and possible ending '/'
if (this->dirname != NULL) {
if (len == 0 || ((len == 1) && (dirname[0] == '/'))) {
isRoot = true;
this->dirname[0] = '\0';
} else {
isRoot = false;
memcpy(this->dirname, dirname, len+1);
if (dirname[len - 1] != '/') {
this->dirname[len] = '/';
this->dirname[len+1] = '\0';
}
}
cur_entry.d_name[HEADER_FNAME_STRLEN] = '\0';
rewinddir();
}
}
QSPIDirHandle::~QSPIDirHandle()
{
if (dirname != NULL) {
delete dirname;
dirname = NULL;
}
}
QSPIDirHandle* QSPIDirHandle::openDir(const char* dirname)
{
QSPIDirHandle* d = new QSPIDirHandle(dirname);
if (d->dirname == NULL) {
// failed to allocate memory for the folder name
delete d;
d = NULL;
} else if (!d->isRoot) {
if (d->findFileWithPrefix(d->dirname, 0, NUM_BLOCKS) == NUM_BLOCKS) {
// There are no files in this directory, i.e. it does not exist
delete d;
d = NULL;
}
}
return d;
}
int QSPIDirHandle::closedir() {
delete this;
return 0;
}
int QSPIDirHandle::findFileWithPrefix(const char* prefix, int startTOCIdx, int maxTOCIdx) const
{
for (int i = startTOCIdx; i < maxTOCIdx; i++) {
if (TOC_IS_FILE(TOC[i])) {
const char* filename = (const char*) (SPIFI_MEM_BASE + i * ERASE_SIZE);
if (qspifs_startsWith(prefix, filename)) {
return i;
}
}
}
return NUM_BLOCKS; // no match
}
struct dirent *QSPIDirHandle::readdir() {
if (nextTocIdx < NUM_BLOCKS) {
for (int i = nextTocIdx; i < NUM_BLOCKS; i++) {
int possible = findFileWithPrefix(dirname, i, NUM_BLOCKS);
if (possible < NUM_BLOCKS) {
const char* fullfilename = (const char*) (SPIFI_MEM_BASE + possible * ERASE_SIZE);
const char* filename = fullfilename + strlen(dirname);
if (strchr(filename, '/') == NULL) {
// file is not in any sub folder so it is truly in the wanted dir
nextTocIdx = possible + 1;
strcpy(cur_entry.d_name, filename);
return &cur_entry;
}
// this is a file in a subfolder and should not be reported,
// but the folder name itself should
strcpy(cur_entry.d_name, fullfilename);
char* pSlash = strchr(cur_entry.d_name + strlen(dirname), '/');
pSlash++;
*pSlash = '\0';
// now that cur_entry.d_name contains the folder's complete
// path with a trailing '/', see if it has occurred earlier
int older = findFileWithPrefix(cur_entry.d_name, 0, i);
if (older < possible) {
// already reported, move past this entry
i = possible;
} else {
// found a new subfolder
nextTocIdx = possible + 1;
strcpy(cur_entry.d_name, filename);
char* pSlash = strchr(cur_entry.d_name, '/');
// pSlash++; //with ++ the returned dir name is "mydir/" without ++ "mydir" is returned
*pSlash = '\0';
return &cur_entry;
}
}
}
}
return NULL;
}
void QSPIDirHandle::rewinddir() {
nextTocIdx = 0;
}
QSPIFileSystem::QSPIFileSystem(const char* name) :
FileSystemLike(name) {
activeTOC = -1;
spifi = NULL;
}
// All modes are supported but:
//
// 1) All writes are treated as appends
// 2) Truncation is only to size 0, i.e. effectively a delete
// 3) File position operations work like this:
// ReadOnly - dictates where to read from
// WriteOnly - ignored, writes are always at the end
// ReadWrite - dictates where to read from, writes ignore it but
// sets the position to the end afterwards
//
FileHandle *QSPIFileSystem::open(const char *filename, int flags)
{
fresult res = qspifs_init();
// if (res == FS_OK) {
// if ((flags & O_ACCMODE) == O_RDONLY) {
// // ok
// } else if (flags & O_APPEND) {
// // ok
// } else {
// // not supported yet, this includes all combination of flags
// // allowing writing at specific positions in the file. This file system
// // only allows appending
// res = FS_ERR_INVALID_PARAM;
// }
// }
if (res == FS_OK) {
if (strlen(filename) > HEADER_FNAME_STRLEN) {
// Filename is too long
res = FS_ERR_INVALID_PARAM;
}
}
if (res == FS_OK) {
// Handle truncation by silently deleting the file before
// attempting to open it
if (flags & O_TRUNC) {
remove(filename);
}
}
if (res == FS_OK) {
fileHandle_t fh = {0,0,0};
res = qspifs_findFile(filename, &fh);
if ((res == FS_ERR_NO_FILE) && (flags & O_CREAT)) {
res = qspifs_allocateFile(filename, 1, &fh.tocIdx);
}
if (res == FS_OK) {
res = qspifs_saveTOC();
}
if (res == FS_OK) {
return new QSPIFileHandle(&fh, flags);
}
}
debug_if(QSPI_DBG, "QSPIFS: Failed to open: %d\n", res);
return NULL;
}
int QSPIFileSystem::remove(const char *filename)
{
fileHandle_t fh = {0,0,0};
fresult res = qspifs_init();
if (res == FS_OK) {
res = qspifs_findFile(filename, &fh);
}
if (res == FS_OK) {
qspifs_deleteFile(&fh);
res = qspifs_saveTOC();
}
else if (res == FS_ERR_NO_FILE) {
// file does not exist so treat it as a successful deletion
res = FS_OK;
}
if (res != FS_OK) {
debug_if(QSPI_DBG, "QSPIFS: Failed to delete %s: %d\n", filename, res);
return -1;
}
return 0;
}
int QSPIFileSystem::rename(const char *oldname, const char *newname)
{
fileHandle_t fhOld = {0,0,0};
fileHandle_t fhNew = {0,0,0};
fresult res = qspifs_init();
if (res == FS_OK) {
res = qspifs_findFile(oldname, &fhOld);
}
if (res == FS_OK) {
// Make sure the destination file doesn't exist
res = qspifs_findFile(newname, &fhNew);
if (res == FS_OK) {
res = FS_ERR_FILE_EXIST;
} else if (res == FS_ERR_NO_FILE) {
res = FS_OK;
}
}
if (res == FS_OK) {
int numNeededBlocks = 1 + ((fhOld.size + HEADER_LEN) / ERASE_SIZE);
res = qspifs_allocateFile(newname, numNeededBlocks, &fhNew.tocIdx);
if (res == FS_OK) {
const uint8_t* pData = (const uint8_t*)(SPIFI_MEM_BASE + fhOld.tocIdx*ERASE_SIZE + HEADER_LEN);
res = qspifs_write(&fhNew, pData, fhOld.size);
if (res == FS_OK) {
qspifs_deleteFile(&fhOld);
} else {
qspifs_deleteFile(&fhNew);
}
}
qspifs_saveTOC();
}
if (res != FS_OK) {
debug_if(QSPI_DBG, "QSPIFS: Failed to rename '%s' to '%s': %d\n", oldname, newname, res);
return -1;
}
return 0;
}
DirHandle *QSPIFileSystem::opendir(const char *name)
{
if (isformatted()) {
if (*name == '\0') {
// opendir("/qspi/") will result in a call to this function with name=""
return QSPIDirHandle::openDir(name);
}
FileHandle* fh = open(name, O_RDONLY);
if (fh != NULL) {
// Attempting to open a file as a dir
delete fh;
return NULL;
}
// printf("opendir: name '%s'\n", name);
if (strlen(name) <= HEADER_DNAME_MAXLEN) {
return QSPIDirHandle::openDir(name);
}
}
return NULL;
}
int QSPIFileSystem::mkdir(const char *name, mode_t mode)
{
// Creating folders is always successful as there are no folders in this filesystem
return 0;
}
int QSPIFileSystem::format(unsigned int fsSizeInMB)
{
fresult res = qspifs_init();
if (res == FS_OK || res == FS_ERR_NOT_FORMATTED) {
if (((fsSizeInMB<<20) > memInfo.memSize) || (fsSizeInMB < 1)) {
debug_if(QSPI_DBG, "QSPIFS: Failed to format to size %d MByte: error %d\n", fsSizeInMB, res);
return -1;
}
activeTOC = -1;
res = qspifs_format(memInfo.memSize - (fsSizeInMB<<20));
}
if (res != FS_OK) {
debug_if(QSPI_DBG, "QSPIFS: Failed to format: %d\n", res);
return -1;
}
return 0;
}
bool QSPIFileSystem::isformatted()
{
fresult res = qspifs_init();
if (res == FS_OK) {
return true;
} else if (res == FS_ERR_NOT_FORMATTED) {
return false;
}
debug_if(QSPI_DBG, "QSPIFS: Failed to detect status: %d\n", res);
return false;
}
bool QSPIFileSystem::getMemoryBoundaries(uint32_t* pStartAddr, uint32_t* pEndAddr)
{
if (isformatted())
{
*pEndAddr = 0x28000000 + memInfo.memSize;
// Look at all blocks except for the reserved ones
for (int i = 0; i < NUM_BLOCKS; i++)
{
if (!TOC_IS_RESERVED(TOC[i]))
{
// Found first non-reserved erase block, indicating the start of
// the file system.
*pStartAddr = SPIFI_MEM_BASE + i*ERASE_SIZE;
return true;
}
}
// The entire file system seems to be reserved which should never happen
// but just in case, report it as beeing 1MB in size.
*pStartAddr = *pEndAddr - 1024*1024;
return true;
}
return false;
}
