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 Embedded Artists

FileSystems/QSPIFileSystem.cpp

Committer:
embeddedartists
Date:
2014-12-11
Revision:
9:a33326afd686
Parent:
0:6b68dac0d986
Child:
14:c21497031b1f

File content as of revision 9:a33326afd686:

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