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:
2015-01-16
Revision:
22:1a58a518435c
Parent:
19:2efb6f5f69a4
Child:
29:b1ec19000e15

File content as of revision 22:1a58a518435c:

/*
 *  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:
        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 < 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)
{
  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;
}