/*
 * FreeRTOS+FAT SL V1.0.1 (C) 2014 HCC Embedded
 *
 * The FreeRTOS+FAT SL license terms are different to the FreeRTOS license 
 * terms.
 * 
 * FreeRTOS+FAT SL uses a dual license model that allows the software to be used 
 * under a standard GPL open source license, or a commercial license.  The 
 * standard GPL license (unlike the modified GPL license under which FreeRTOS 
 * itself is distributed) requires that all software statically linked with 
 * FreeRTOS+FAT SL is also distributed under the same GPL V2 license terms.  
 * Details of both license options follow:
 * 
 * - Open source licensing -
 * FreeRTOS+FAT SL is a free download and may be used, modified, evaluated and
 * distributed without charge provided the user adheres to version two of the 
 * GNU General Public License (GPL) and does not remove the copyright notice or 
 * this text.  The GPL V2 text is available on the gnu.org web site, and on the
 * following URL: http://www.FreeRTOS.org/gpl-2.0.txt.
 * 
 * - Commercial licensing -
 * Businesses and individuals who for commercial or other reasons cannot comply
 * with the terms of the GPL V2 license must obtain a commercial license before 
 * incorporating FreeRTOS+FAT SL into proprietary software for distribution in 
 * any form.  Commercial licenses can be purchased from 
 * http://shop.freertos.org/fat_sl and do not require any source files to be 
 * changed.
 *
 * FreeRTOS+FAT SL is distributed in the hope that it will be useful.  You
 * cannot use FreeRTOS+FAT SL unless you agree that you use the software 'as
 * is'.  FreeRTOS+FAT SL is provided WITHOUT ANY WARRANTY; without even the
 * implied warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. Real Time Engineers Ltd. and HCC Embedded disclaims all
 * conditions and terms, be they implied, expressed, or statutory.
 *
 * http://www.FreeRTOS.org
 * http://www.FreeRTOS.org/FreeRTOS-Plus
 *
 */

#include "../../api/fat_sl.h"
#include "../../psp/include/psp_string.h"

#include "volume.h"
#include "util.h"
#include "drv.h"
#include "fat.h"
#include "dir.h"
#include "file.h"

#include "../../version/ver_fat_sl.h"
#if VER_FAT_SL_MAJOR != 5 || VER_FAT_SL_MINOR != 2
 #error Incompatible FAT_SL version number!
#endif

#if F_FS_THREAD_AWARE == 1
  #include "f_lock.h"
#endif

F_VOLUME  gl_volume;                /* only one volume */
F_FILE    gl_file;                  /* file */
char      gl_sector[F_SECTOR_SIZE]; /* actual sector */

#if F_FILE_CHANGED_EVENT
F_FILE_CHANGED_EVENTFUNC  f_filechangedevent;
#endif


/* Defines the number of sectors per cluster on a sector number basis */
typedef struct
{
  unsigned long  max_sectors;
  unsigned char  sector_per_cluster;
} t_FAT32_CS;

static const t_FAT32_CS  FAT32_CS[] =
{
  { 0x00020000, 1 }     /* ->64MB */
  , { 0x00040000, 2 }   /* ->128MB */
  , { 0x00080000, 4 }   /* ->256MB */
  , { 0x01000000, 8 }   /* ->8GB */
  , { 0x02000000, 16 }  /* ->16GB */
  , { 0x0ffffff0, 32 }  /* -> ... */
};


/****************************************************************************
 *
 * _f_writebootrecord
 *
 * writing boot record onto a volume, it uses number of hidden sector variable
 *
 * INPUTS
 * phy - media physical descriptor
 *
 * RETURNS
 * error code or zero if successful
 *
 ***************************************************************************/
static unsigned char _f_writebootrecord ( F_PHY * phy )
{
  unsigned char   jump_code[] =
  {
    0xeb, 0x3c, 0x90
  };
  unsigned char   oem_name[] = "MSDOS5.0";
  unsigned char   executable_marker[] =
  {
    0x55, 0xaa
  };
  unsigned char * ptr = (unsigned char *)gl_sector;
  unsigned char   rs;
  unsigned short  mre;

  unsigned char   ret;
  unsigned char   _n = 0;

  if ( gl_volume.mediatype == F_FAT32_MEDIA )
  {  /*write FS_INFO*/
    unsigned char  a;

    rs = 32 + 4;
    mre = 0;

    psp_memset( ptr, 0, F_SECTOR_SIZE );

    for ( a = 0 ; a < rs ; a++ )
    {
      ret = _f_writeglsector( a ); /*erase reserved area*/
      if ( ret )
      {
        return ret;
      }
    }

    ptr = _setlong( 0x41615252, ptr ); /*signature*/
    ptr = _setcharzero( 480, ptr );    /*reserved*/
    ptr = _setlong( 0x61417272, ptr ); /*signature*/
    ptr = _setlong( 0xffffffff, ptr ); /*no last*/
    ptr = _setlong( 0xffffffff, ptr ); /*no hint*/
    ptr = _setcharzero( 12, ptr );     /*reserved*/
    ptr = _setlong( 0xaa550000, ptr ); /*trail*/


    ret = _f_writeglsector( 1 ); /*write FSINFO*/
    if ( ret )
    {
      return ret;
    }

    ret = _f_writeglsector( 1 + 6 ); /*write FSINFO*/
    if ( ret )
    {
      return ret;
    }
  }
  else
  {
    rs = 1;
    mre = 512;
  }

  ptr = (unsigned char *)gl_sector;
  ptr = _setchar( jump_code, sizeof( jump_code ), ptr );
  ptr = _setchar( oem_name, sizeof( oem_name ) - 1, ptr );
  ptr = _setword( F_SECTOR_SIZE, ptr );
  *ptr++ = gl_volume.bootrecord.sector_per_cluster;
  ptr = _setword( rs, ptr );  /* reserved sectors */
  *ptr++ = 2;                 /* number of FATs */
  ptr = _setword( mre, ptr ); /* max root entry */
  if ( phy->number_of_sectors < 0x10000 )
  {
    ptr = _setword( (unsigned short)phy->number_of_sectors, ptr );
  }
  else
  {
    ptr = _setword( 0, ptr );
  }

  *ptr++ = 0xf0;                /* media descriptor */
  ptr = _setword( (unsigned short)gl_volume.bootrecord.sector_per_FAT, ptr );
  ptr = _setword( phy->sector_per_track, ptr );
  ptr = _setword( phy->number_of_heads, ptr );
  ptr = _setlong( 0, ptr ); /* number of hidden sectors */
  if ( phy->number_of_sectors >= 0x10000 )
  {
    ptr = _setlong( phy->number_of_sectors, ptr );
  }
  else
  {
    ptr = _setlong( 0, ptr );                                       /* number of sectors */
  }

  if ( gl_volume.mediatype == F_FAT32_MEDIA )
  {
    ptr = _setlong( gl_volume.bootrecord.sector_per_FAT32, ptr );
    ptr = _setword( 0, ptr );
    ptr = _setword( 0, ptr );
    ptr = _setlong( 2, ptr );
    ptr = _setword( 1, ptr );
    ptr = _setword( 6, ptr );
    ptr = _setchar( NULL, 12, ptr );
    _n = 28;
  }


  ptr = _setword( 0, ptr ); /* logical drive num */
  *ptr++ = 0x29;            /* extended signature */
  ptr = _setlong( 0x11223344, ptr );
  ptr = _setchar( (const unsigned char *)"NO NAME    ", 11, ptr ); /* volume name */

  switch ( gl_volume.mediatype )
  {
    case F_FAT12_MEDIA:
      ptr = _setchar( (const unsigned char *)"FAT12   ", 8, ptr );
      break;

    case F_FAT16_MEDIA:
      ptr = _setchar( (const unsigned char *)"FAT16   ", 8, ptr );
      break;

    case F_FAT32_MEDIA:
      ptr = _setchar( (const unsigned char *)"FAT32   ", 8, ptr );
      break;

    default:
      return F_ERR_INVALIDMEDIA;
  } /* switch */

  ptr = _setchar( 0, 448 - _n, ptr );
  ptr = _setchar( executable_marker, sizeof( executable_marker ), ptr );

  if ( _n )
  {
    ret = _f_writeglsector( 6 );
    if ( ret )
    {
      return ret;
    }
  }


  return _f_writeglsector( 0 ); /*write bootrecord*/
} /* _f_writebootrecord */


/****************************************************************************
 *
 * _f_buildsectors
 *
 * INPUTS
 * phy - media physical descriptor
 *
 * calculate relative sector position from boot record
 *
 ***************************************************************************/
static unsigned char _f_buildsectors ( F_PHY * phy )
{
  gl_volume.mediatype = F_UNKNOWN_MEDIA;


  if ( gl_volume.bootrecord.sector_per_FAT )
  {
    gl_volume.firstfat.sector = 1;
    gl_volume.firstfat.num = gl_volume.bootrecord.sector_per_FAT;
    gl_volume.root.sector = gl_volume.firstfat.sector + ( gl_volume.firstfat.num * (unsigned long)( gl_volume.bootrecord.number_of_FATs ) );
    gl_volume.root.num = ( 512 * sizeof( F_DIRENTRY ) ) / F_SECTOR_SIZE;

    gl_volume._tdata.sector = gl_volume.root.sector + gl_volume.root.num;
    gl_volume._tdata.num = 0;  /*??*/
  }
  else
  {
    gl_volume.firstfat.sector = ( 32 + 4 );
    gl_volume.firstfat.num = gl_volume.bootrecord.sector_per_FAT32;
    gl_volume._tdata.sector = gl_volume.firstfat.sector;
    gl_volume._tdata.sector += gl_volume.firstfat.num * (unsigned long)( gl_volume.bootrecord.number_of_FATs );
    gl_volume._tdata.num = 0;  /*??*/

    {
      unsigned long  sectorcou = gl_volume.bootrecord.sector_per_cluster;
      gl_volume.root.sector = ( ( gl_volume.bootrecord.rootcluster - 2 ) * sectorcou ) + gl_volume._tdata.sector;
      gl_volume.root.num = gl_volume.bootrecord.sector_per_cluster;
    }
  }

  {
    unsigned long  maxcluster;
    maxcluster = phy->number_of_sectors;
    maxcluster -= gl_volume._tdata.sector;
    maxcluster /= gl_volume.bootrecord.sector_per_cluster;
    gl_volume.maxcluster = maxcluster;
  }

  if ( gl_volume.maxcluster < ( F_CLUSTER_RESERVED & 0xfff ) )
  {
    gl_volume.mediatype = F_FAT12_MEDIA;
  }
  else if ( gl_volume.maxcluster < ( F_CLUSTER_RESERVED & 0xffff ) )
  {
    gl_volume.mediatype = F_FAT16_MEDIA;
  }
  else
  {
    gl_volume.mediatype = F_FAT32_MEDIA;
  }

  return F_NO_ERROR;
} /* _f_buildsectors */



/****************************************************************************
 *
 * _f_prepareformat
 *
 * preparing boot record for formatting, it sets and calculates values
 *
 * INPUTS
 * phy - media physical descriptor
 * f_bootrecord - which bootrecord need to be prepare
 * number_of_hidden_sectors - where boot record starts
 * fattype - one of this definitions F_FAT12_MEDIA,F_FAT16_MEDIA,F_FAT32_MEDIA
 *
 * RETURNS
 * error code or zero if successful
 *
 ***************************************************************************/
static unsigned char _f_prepareformat ( F_PHY * phy, unsigned char fattype )
{
  if ( !phy->number_of_sectors )
  {
    return F_ERR_INVALIDSECTOR;
  }

  gl_volume.bootrecord.number_of_FATs = 2;
  gl_volume.bootrecord.media_descriptor = 0xf0;

  if ( fattype != F_FAT32_MEDIA )
  {
    unsigned long  _n;
    switch ( fattype )
    {
      case F_FAT12_MEDIA:
        _n = F_CLUSTER_RESERVED & 0xfff;
        break;

      case F_FAT16_MEDIA:
        _n = F_CLUSTER_RESERVED & 0xffff;
        break;

      default:
        return F_ERR_INVFATTYPE;
    }

    gl_volume.bootrecord.sector_per_cluster = 1;
    while ( gl_volume.bootrecord.sector_per_cluster )
    {
      if ( phy->number_of_sectors / gl_volume.bootrecord.sector_per_cluster < _n )
      {
        break;
      }

      gl_volume.bootrecord.sector_per_cluster <<= 1;
    }

    if ( !gl_volume.bootrecord.sector_per_cluster )
    {
      return F_ERR_MEDIATOOLARGE;
    }
  }

  else
  {
    unsigned char  i;
    for ( i = 0 ; i<( sizeof( FAT32_CS ) / sizeof( t_FAT32_CS ) ) - 1 && phy->number_of_sectors>FAT32_CS[i].max_sectors ; i++ )
    {
    }

    gl_volume.bootrecord.sector_per_cluster = FAT32_CS[i].sector_per_cluster;
  }

  if ( !gl_volume.bootrecord.sector_per_cluster )
  {
    return F_ERR_INVALIDMEDIA;                                               /*fat16 cannot be there*/
  }

  {
    long           secpercl = gl_volume.bootrecord.sector_per_cluster;
    long           nfat = gl_volume.bootrecord.number_of_FATs;
    unsigned long  roots;
    unsigned long  fatsec;

    roots = ( 512 * sizeof( F_DIRENTRY ) ) / F_SECTOR_SIZE;

    switch ( fattype )
    {
      case F_FAT32_MEDIA:
      {
        unsigned long  _n = (unsigned long)( 128 * secpercl + nfat );
        fatsec = ( phy->number_of_sectors - ( 32 + 4 ) + 2 * secpercl );
        fatsec += ( _n - 1 );
        fatsec /= _n;
        gl_volume.bootrecord.sector_per_FAT32 = fatsec;
        gl_volume.bootrecord.sector_per_FAT = 0;
      }
      break;

      case F_FAT16_MEDIA:
      {
        unsigned long  _n = (unsigned long)( 256 * secpercl + nfat );
        fatsec = ( phy->number_of_sectors - 1 - roots + 2 * secpercl );
        fatsec += ( _n - 1 );
        fatsec /= _n;
        gl_volume.bootrecord.sector_per_FAT = (unsigned short)( fatsec );
      }
      break;

      case F_FAT12_MEDIA:
      {
        unsigned long  _n = (unsigned long)( 1024 * secpercl + 3 * nfat );
        fatsec = ( phy->number_of_sectors - 1 - roots + 2 * secpercl );
        fatsec *= 3;
        fatsec += ( _n - 1 );
        fatsec /= _n;
        gl_volume.bootrecord.sector_per_FAT = (unsigned short)( fatsec );
      }
      break;

      default:
        return F_ERR_INVALIDMEDIA;
    } /* switch */

    return F_NO_ERROR;
  }
} /* _f_prepareformat */



/****************************************************************************
 *
 * _f_postformat
 *
 * erase fats, erase root directory, reset variables after formatting
 *
 * INPUTS
 * phy - media physical descriptor
 * fattype - one of this definitions F_FAT12_MEDIA,F_FAT16_MEDIA,F_FAT32_MEDIA
 *
 * RETURNS
 * error code or zero if successful
 *
 ***************************************************************************/
static unsigned char _f_postformat ( F_PHY * phy, unsigned char fattype )
{
  unsigned long  a;
  unsigned char  ret;

  _f_buildsectors( phy ); /*get positions*/
  if ( gl_volume.mediatype != fattype )
  {
    return F_ERR_MEDIATOOSMALL;
  }

  gl_volume.fatsector = (unsigned long)( -1 );

  {
    unsigned char * ptr = (unsigned char *)gl_sector;
    unsigned char   j = 2;
    unsigned long   i;

    psp_memset( ptr, 0, F_SECTOR_SIZE );

    switch ( gl_volume.mediatype )
    {
      case F_FAT16_MEDIA:
        j = 3;
        break;

      case F_FAT32_MEDIA:
        j = 11;
        break;
    }

    *ptr = gl_volume.bootrecord.media_descriptor;
    psp_memset( ptr + 1, 0xff, j );
    if ( gl_volume.mediatype == F_FAT32_MEDIA )
    {
      *( ptr + 8 ) = (unsigned char)( F_CLUSTER_LAST & 0xff );
    }

    (void)_f_writeglsector( gl_volume.firstfat.sector );
    (void)_f_writeglsector( gl_volume.firstfat.sector + gl_volume.firstfat.num );
    psp_memset( ptr, 0, ( j + 1 ) );

    for ( i = 1 ; i < gl_volume.firstfat.num ; i++ )
    {
      (void)_f_writeglsector( gl_volume.firstfat.sector + i );
      (void)_f_writeglsector( gl_volume.firstfat.sector + i + gl_volume.firstfat.num );
    }
  }

  for ( a = 0 ; a < gl_volume.root.num ; a++ ) /*reset root direntries*/
  {
    ret = _f_writeglsector( gl_volume.root.sector + a );
    if ( ret )
    {
      return ret;
    }
  }

  return _f_writebootrecord( phy );
} /* _f_postformat */


/****************************************************************************
 *
 * fn_hardformat
 *
 * Making a complete format on media, independently from master boot record,
 * according to media physical
 *
 * INPUTS
 * fattype - one of this definitions F_FAT12_MEDIA,F_FAT16_MEDIA,F_FAT32_MEDIA
 *
 * RETURNS
 * error code or zero if successful
 *
 ***************************************************************************/
unsigned char fn_hardformat ( unsigned char fattype )
{
  unsigned char  ret;
  int            mdrv_ret;
  F_PHY          phy;

  ret = _f_getvolume();
  if ( ret && ( ret != F_ERR_NOTFORMATTED ) )
  {
    return ret;
  }

  gl_volume.state = F_STATE_NEEDMOUNT;

  psp_memset( &phy, 0, sizeof( F_PHY ) );

  mdrv_ret = mdrv->getphy( mdrv, &phy );
  if ( mdrv_ret )
  {
    return F_ERR_ONDRIVE;
  }

  ret = _f_prepareformat( &phy, fattype ); /*no partition*/
  if ( ret )
  {
    return ret;
  }

  return _f_postformat( &phy, fattype );
} /* fn_hardformat */




/****************************************************************************
 *
 * _f_readbootrecord
 *
 * read boot record from a volume, it detects if there is MBR on the media
 *
 * RETURNS
 *
 * error code or zero if successful
 *
 ***************************************************************************/
static unsigned char _f_readbootrecord ( void )
{
  unsigned char   ret;
  unsigned char * ptr = (unsigned char *)gl_sector;
  unsigned long   maxcluster, _n;
  unsigned long   first_sector = 0;

  gl_volume.mediatype = F_UNKNOWN_MEDIA;


  ret = _f_readglsector( 0 );
  if ( ret )
  {
    return ret;
  }


  if ( ( ptr[0x1fe] != 0x55 ) || ( ptr[0x1ff] != 0xaa ) )
  {
    return F_ERR_NOTFORMATTED;                                              /*??*/
  }

  if ( ( ptr[0] != 0xeb ) && ( ptr[0] != 0xe9 ) )
  {
    first_sector = _f_getlong( &ptr[0x08 + 0x1be] ); /*start sector for 1st partioon*/

    ret = _f_readglsector( first_sector );
    if ( ret )
    {
      return ret;
    }

    if ( ( ptr[0x1fe] != 0x55 ) || ( ptr[0x1ff] != 0xaa ) )
    {
      return F_ERR_NOTFORMATTED;                                                /*??*/
    }

    if ( ( ptr[0] != 0xeb ) && ( ptr[0] != 0xe9 ) )
    {
      return F_ERR_NOTFORMATTED;                                        /*??*/
    }
  }

  ptr += 11;
  if ( _f_getword( ptr ) != F_SECTOR_SIZE )
  {
    return F_ERR_NOTSUPPSECTORSIZE;
  }

  ptr += 2;
  gl_volume.bootrecord.sector_per_cluster = *ptr++;
  gl_volume.firstfat.sector = _f_getword( ptr );
  ptr += 2;
  gl_volume.bootrecord.number_of_FATs = *ptr++;
  gl_volume.root.num = _f_getword( ptr );
  ptr += 2;
  gl_volume.root.num *= sizeof( F_DIRENTRY );
  gl_volume.root.num /= F_SECTOR_SIZE;
  maxcluster = _f_getword( ptr );
  ptr += 2;
  gl_volume.bootrecord.media_descriptor = *ptr++;
  gl_volume.firstfat.num = _f_getword( ptr );
  ptr += 6;
  _n = _f_getlong( ptr );
  ptr += 4;
  if ( _n < first_sector )
  {
    _n = first_sector;
  }

  gl_volume.firstfat.sector += _n;
  if ( !maxcluster )
  {
    maxcluster = _f_getlong( ptr );
  }

  ptr += 4;


  if ( gl_volume.firstfat.num )
  {
    gl_volume.root.sector = gl_volume.firstfat.sector + ( gl_volume.firstfat.num * gl_volume.bootrecord.number_of_FATs );
    gl_volume._tdata.sector = gl_volume.root.sector + gl_volume.root.num;
    gl_volume._tdata.num = 0;
    ptr += 3;
  }
  else
  {
    gl_volume.firstfat.num = _f_getlong( ptr );
    ptr += 8;
    gl_volume._tdata.sector = gl_volume.firstfat.sector;
    gl_volume._tdata.sector += gl_volume.firstfat.num * gl_volume.bootrecord.number_of_FATs;
    gl_volume._tdata.num = 0;
    gl_volume.bootrecord.rootcluster = _f_getlong( ptr );
    ptr += 23;
    gl_volume.root.num = gl_volume.bootrecord.sector_per_cluster;
    gl_volume.root.sector = ( ( gl_volume.bootrecord.rootcluster - 2 ) * gl_volume.root.num ) + gl_volume._tdata.sector;
  }

  gl_volume.bootrecord.serial_number = _f_getlong( ptr );

  maxcluster -= gl_volume._tdata.sector;
  maxcluster += _n;
  gl_volume.maxcluster = maxcluster / gl_volume.bootrecord.sector_per_cluster;

  if ( gl_volume.maxcluster < ( F_CLUSTER_RESERVED & 0xfff ) )
  {
    gl_volume.mediatype = F_FAT12_MEDIA;
  }
  else if ( gl_volume.maxcluster < ( F_CLUSTER_RESERVED & 0xffff ) )
  {
    gl_volume.mediatype = F_FAT16_MEDIA;
  }
  else
  {
    gl_volume.mediatype = F_FAT32_MEDIA;
  }

  if ( gl_volume.bootrecord.media_descriptor != 0xf8 )    /*fixdrive*/
  {
    if ( gl_volume.bootrecord.media_descriptor != 0xf0 )  /*removable*/
    {
      return F_ERR_NOTFORMATTED;      /*??*/
    }
  }

  return F_NO_ERROR;
} /* _f_readbootrecord */




/****************************************************************************
 *
 * _f_getvolume
 *
 * getting back a volume info structure of a given drive, it try to mounts
 * drive if it was not mounted before
 *
 * RETURNS
 *
 * error code or zero if successful
 *
 ***************************************************************************/
unsigned char _f_getvolume ( void )
{
  switch ( gl_volume.state )
  {
    case F_STATE_NONE:
      return F_ERR_ONDRIVE;

    case F_STATE_WORKING:

      if ( !_f_checkstatus() )
      {
        return F_NO_ERROR;
      }

    /* here we don't stop case flow,  */
    /* because we have to clean up this volume! */

    case F_STATE_NEEDMOUNT:
    {
      gl_file.modified = 0;
      gl_volume.modified = 0;
      gl_volume.lastalloccluster = 0;
      gl_volume.actsector = (unsigned long)( -1 );
      gl_volume.fatsector = (unsigned long)( -1 );

      gl_file.mode = F_FILE_CLOSE;

      gl_volume.cwd[0] = 0;     /*reset cwd*/
      gl_volume.mediatype = F_UNKNOWN_MEDIA;

      if ( mdrv->getstatus != NULL )
      {
        if ( mdrv->getstatus( mdrv ) & F_ST_MISSING )
        {
          gl_volume.state = F_STATE_NEEDMOUNT;         /*card missing*/
          return F_ERR_CARDREMOVED;
        }
      }

      if ( !_f_readbootrecord() )
      {
        gl_volume.state = F_STATE_WORKING;
        return F_NO_ERROR;
      }

      gl_volume.mediatype = F_UNKNOWN_MEDIA;
      return F_ERR_NOTFORMATTED;
    }
  } /* switch */

  return F_ERR_ONDRIVE;
} /* _f_getvolume */



/****************************************************************************
 *
 * fn_getfreespace
 *
 * get total/free/used/bad diskspace
 *
 * INPUTS
 * pspace - pointer where to store the information
 *
 * RETURNS
 * error code
 *
 ***************************************************************************/
unsigned char fn_getfreespace ( F_SPACE * pspace )
{
  unsigned char  ret;
  unsigned long  a;
  unsigned long  clustersize;

  ret = _f_getvolume();
  if ( ret )
  {
    return ret;
  }

  psp_memset( pspace, 0, sizeof( F_SPACE ) );
  pspace->total = gl_volume.maxcluster;

  gl_volume.fatsector = (unsigned long)-1;
  for ( a = 2 ; a < gl_volume.maxcluster + 2 ; a++ )
  {
    unsigned long  value;

    ret = _f_getclustervalue( a, &value );
    if ( ret )
    {
      return ret;
    }

    if ( !value )
    {
      ++( pspace->free );
    }
    else if ( value == F_CLUSTER_BAD )
    {
      ++( pspace->bad );
    }
    else
    {
      ++( pspace->used );
    }
  }

  clustersize = (unsigned long)( gl_volume.bootrecord.sector_per_cluster * F_SECTOR_SIZE );
  for ( a = 0 ; ( clustersize & 1 ) == 0 ; a++ )
  {
    clustersize >>= 1;
  }

  pspace->total_high = ( pspace->total ) >> ( 32 - a );
  pspace->total <<= a;
  pspace->free_high = ( pspace->free ) >> ( 32 - a );
  pspace->free <<= a;
  pspace->used_high = ( pspace->used ) >> ( 32 - a );
  pspace->used <<= a;
  pspace->bad_high = ( pspace->bad ) >> ( 32 - a );
  pspace->bad <<= a;

  return F_NO_ERROR;
} /* fn_getfreespace */


/****************************************************************************
 *
 * fn_getserial
 *
 * get serial number
 *
 * INPUTS
 * serial - pointer where to store the serial number
 *
 * RETURNS
 * error code
 *
 ***************************************************************************/
unsigned char fn_getserial ( unsigned long * serial )
{
  unsigned char  ret;

  ret = _f_getvolume();
  if ( ret )
  {
    return ret;
  }

  *serial = gl_volume.bootrecord.serial_number;
  return 0;
}


/*
** fs_init
**
** Initialize STHIN file system
**
** RETURN: F_NO_ERROR on success, other if error.
*/
unsigned char fs_init ( void )
{
  unsigned char  rc = F_NO_ERROR;

#if RTOS_SUPPORT
  rc = fsr_init();
  if ( rc )
  {
    return rc;
  }

#endif
  return rc;
} /* fs_init */


/*
** fs_delete
**
** Delete STHIN file system
**
** RETURN: F_NO_ERROR on success, other if error.
*/
unsigned char fs_delete ( void )
{
  unsigned char  rc = F_NO_ERROR;

#if RTOS_SUPPORT
  rc = fsr_delete();
  if ( rc )
  {
    return rc;
  }

#endif
  return rc;
} /* fs_delete */


/****************************************************************************
 *
 * fn_initvolume
 *
 * initiate a volume, this function has to be called 1st to set physical
 * driver function to a given volume
 *
 * RETURNS
 *
 * error code or zero if successful
 *
 ***************************************************************************/
unsigned char fn_initvolume ( F_DRIVERINIT initfunc )
{
#if F_FS_THREAD_AWARE == 1
	{
		extern xSemaphoreHandle fs_lock_semaphore;

		if( fs_lock_semaphore == NULL )
		{
			fs_lock_semaphore = xSemaphoreCreateMutex();
			if( fs_lock_semaphore == NULL )
			{
				return F_ERR_OS;
			}
		}
	}
#endif /* F_FS_THREAD_AWARE */

  gl_volume.state = F_STATE_NONE;

  mdrv = initfunc( 0 );
  if ( mdrv == NULL )
  {
    return F_ERR_INITFUNC;
  }

  gl_volume.state = F_STATE_NEEDMOUNT;

#if F_FILE_CHANGED_EVENT
  f_filechangedevent = 0;
#endif

  return _f_getvolume();
} /* fn_initvolume */


/****************************************************************************
 *
 * fn_delvolume
 *
 ***************************************************************************/
unsigned char fn_delvolume ( void )
{
  if ( mdrv->release )
  {
    (void)mdrv->release( mdrv );
  }

  return 0;
}
