Subdirectory provided by Embedded Artists
Dependencies: DM_FATFileSystem DM_HttpServer DM_USBHost EthernetInterface USBDevice mbed-rpc mbed-rtos mbed-src
Dependents: lpc4088_displaymodule_hello_world_Sept_2018
Fork of DMSupport by
Diff: FileSystems/QSPIFileSystem.cpp
- Revision:
- 0:6b68dac0d986
- Child:
- 9:a33326afd686
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/FileSystems/QSPIFileSystem.cpp Fri Nov 21 11:42:51 2014 +0000 @@ -0,0 +1,1480 @@ +/* + * Copyright 2013 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 (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: + /* For the Spansion memory the TOC occupies 256bytes and the TOC block will + hold 256 TOCs. */ + strcpy(memInfo.memName, "Spansion S25FL032"); + memInfo.memSize = obj->memSize; + memInfo.eraseBlockSize = 64*1024; + memInfo.numEraseBlocks = memInfo.memSize / memInfo.eraseBlockSize; + memInfo.tocSizeInBytes = sizeof(toc_entry_t) * memInfo.numEraseBlocks; + memInfo.numTocs = memInfo.eraseBlockSize / memInfo.tocSizeInBytes; + memInfo.tocBlockAddr = SPIFI_MEM_BASE + (NUM_BLOCKS * ERASE_SIZE) - (memInfo.numTocs * memInfo.tocSizeInBytes); + break; + + case SPIFI::Winbond_W25Q64FV: + /* For the Winbond memory the TOC occupies 8192 bytes and that is bigger than + one erase block (which is 4096 bytes). It is possible to either keep only + one TOC or to create a couple to reduce wear on the memory. In this case + the multiple TOCs option is used. */ + strcpy(memInfo.memName, "Winbond W25Q64FV"); + memInfo.memSize = obj->memSize; + memInfo.eraseBlockSize = 4*1024; + memInfo.numEraseBlocks = memInfo.memSize / memInfo.eraseBlockSize; + memInfo.tocSizeInBytes = sizeof(toc_entry_t) * memInfo.numEraseBlocks; + memInfo.numTocs = 8; + memInfo.tocBlockAddr = SPIFI_MEM_BASE + (NUM_BLOCKS * ERASE_SIZE) - (memInfo.numTocs * memInfo.tocSizeInBytes); + break; + + case SPIFI::Macronix_MX25L6435EM2I: + /* For the Macronix memory the TOC occupies 8192 bytes and that is bigger than + one erase block (which is 4096 bytes). It is possible to either keep only + one TOC or to create a couple to reduce wear on the memory. In this case + the multiple TOCs option is used. */ + strcpy(memInfo.memName, "Macronix_MX25L6435EM2I"); + memInfo.memSize = obj->memSize; + memInfo.eraseBlockSize = 4*1024; + memInfo.numEraseBlocks = memInfo.memSize / memInfo.eraseBlockSize; + memInfo.tocSizeInBytes = sizeof(toc_entry_t) * memInfo.numEraseBlocks; + memInfo.numTocs = 8; + memInfo.tocBlockAddr = SPIFI_MEM_BASE + (NUM_BLOCKS * ERASE_SIZE) - (memInfo.numTocs * memInfo.tocSizeInBytes); + break; + + case SPIFI::UnknownDevice: + default: + debug("INIT: Memory is unknown and may not work as expected\n"); + + // Asuming it has 64Kb erase blocks (i.e. same setup as the Spansion S25FL032 + strcpy(memInfo.memName, "Unknown - check ID"); + memInfo.memSize = obj->memSize; + memInfo.eraseBlockSize = 64*1024; + memInfo.numEraseBlocks = memInfo.memSize / memInfo.eraseBlockSize; + memInfo.tocSizeInBytes = sizeof(toc_entry_t) * memInfo.numEraseBlocks; + memInfo.numTocs = memInfo.eraseBlockSize / memInfo.tocSizeInBytes; + memInfo.tocBlockAddr = SPIFI_MEM_BASE + (NUM_BLOCKS * ERASE_SIZE) - (memInfo.numTocs * memInfo.tocSizeInBytes); + + /* + * 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; + } + + 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 < 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 (int 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 i, rc; + int numReserved = 0; + + if (minReservedBytes > 0) { + numReserved = (minReservedBytes + ERASE_SIZE - 1) / ERASE_SIZE; + if (numReserved >= (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 < 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) +{ + 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; +}