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
FileSystems/QSPIFileSystem.cpp
- Committer:
- embeddedartists
- Date:
- 2015-01-26
- Revision:
- 29:b1ec19000e15
- Parent:
- 22:1a58a518435c
- Child:
- 33:8a0a99d54bf8
File content as of revision 29:b1ec19000e15:
/* * 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()) { 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; }