/*
 * fatfs.c - FATFS file system API implementation.
 *
 * Author    Alain Greiner (2016,2017,2018)
 *
 * Copyright (c) UPMC Sorbonne Universites
 *
 * This file is part of ALMOS-MKH.
 *
 * ALMOS-MKH is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2.0 of the License.
 *
 * ALMOS-MKH is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with ALMOS-MKH; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */


#include <hal_kernel_types.h>
#include <hal_special.h>
#include <printk.h>
#include <thread.h>
#include <kmem.h>
#include <ppm.h>
#include <vfs.h>
#include <string.h>
#include <rpc.h>
#include <mapper.h>
#include <cluster.h>
#include <dev_ioc.h>
#include <fatfs.h>


//////////////////////////////////////////////////////////////////////////////////////////
//          Extern  variables         
//////////////////////////////////////////////////////////////////////////////////////////

extern vfs_ctx_t     fs_context[FS_TYPES_NR];   // allocated in kernel_init.c file

//////////////////////////////////////////////////////////////////////////////////////////
//              FATFS specific static functions 
//////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////
// These functions return the "offset" and "length" values of an 
// [offset,length] constant defined in the fatfs.h file.
//////////////////////////////////////////////////////////////////////////////////////////

static inline int get_length( int offset __attribute__((unused)), int length ) { return length; }

static inline int get_offset( int offset, int length __attribute__((unused)) ) { return offset; }

//////////////////////////////////////////////////////////////////////////////////////////
// This static function returns the LBA of the first sector of a FAT cluster.
// This function can be called by any thread running in any cluster.
//////////////////////////////////////////////////////////////////////////////////////////
// @ ctx	  :	pointer on FATFS context.
// @ cluster  :	cluster index in FATFS.
// @ return the lba value.
//////////////////////////////////////////////////////////////////////////////////////////
static inline uint32_t fatfs_lba_from_cluster( fatfs_ctx_t * ctx,
                                               uint32_t      cluster )
{
    return (ctx->cluster_begin_lba + ((cluster - 2) << 3));
}

//////////////////////////////////////////////////////////////////////////////////////////
// This function return an integer record value (one, two, or four bytes) 
// from a memory buffer, taking into account endianness.
//////////////////////////////////////////////////////////////////////////////////////////
// @ offset        : first byte of record in buffer.
// @ size          : record length in bytes (1/2/4).
// @ buffer        : pointer on buffer base.
// @ little endian : the most significant byte has the highest address when true.
// @ return the integer value in a 32 bits word.
//////////////////////////////////////////////////////////////////////////////////////////
static uint32_t fatfs_get_record( uint32_t    offset,
                                  uint32_t    size,
                                  uint8_t   * buffer,
                                  uint32_t    little_endian )
{
    uint32_t n;
    uint32_t res  = 0;

    if ( little_endian )
    {
        for( n = size ; n > 0 ; n-- ) res = (res<<8) | buffer[offset+n-1];
    }
    else // big_endian
    {
        for( n = 0 ; n < size ; n++ ) res = (res<<8) | buffer[offset+n];
    }
    return res;

}  // end fatfs_get_record()

//////////////////////////////////////////////////////////////////////////////////////////
// This function writes one, two, or four bytes from a 32 bits integer to a memory buffer,
// taking into account endianness.
//////////////////////////////////////////////////////////////////////////////////////////
// @ offset        : first byte of record in buffer.
// @ size          : record length in bytes (1/2/4).
// @ buffer        : pointer on buffer base.
// @ little endian : the most significant byte has the highest address when true.
// @ return the integer value in a 32 bits word.
//////////////////////////////////////////////////////////////////////////////////////////
static void fatfs_set_record( uint32_t    offset,
                              uint32_t    size,
                              uint8_t   * buffer,
                              uint32_t    little_endian,
                              uint32_t    value )
{
    uint32_t n;

    if ( little_endian )
    {
        for( n = size ; n > 0 ; n-- ) buffer[offset+n-1] = (uint8_t)(value>>((n-1)<<3));
    }
    else // big_endian
    {
        for( n = 0 ; n < size ; n++ ) buffer[offset+n] = (uint8_t)(value>>((size-1-n)<<3));
    }

}  // end fatfs_set_record()

//////////////////////////////////////////////////////////////////////////////////////////
// This static function retun in the <name> buffer a short name stored in
// a SFN FATFS directory entry.
/////////////////////////i////////////////////////////////////////////////////////////////
// @ buffer   : pointer on buffer containing the directory entry.
// @ name     : [out] buffer allocated by the caller.
//////////////////////////////////////////////////////////////////////////////////////////
static void fatfs_get_name_from_short( uint8_t * buffer,
                                       char    * name )
{
    uint32_t i;
    uint32_t j = 0;

    // get name
    for ( i = 0; i < 8 && buffer[i] != ' '; i++ )
    {
        name[j] = to_lower( buffer[i] );
        j++;
    }

    // get extension
    for ( i = 8; i < 8 + 3 && buffer[i] != ' '; i++ )
    {
        // we entered the loop so there is an extension. add the dot
        if ( i == 8 )
        {
            name[j] = '.';
            j++;
        }

        name[j] = to_lower( buffer[i] );
        j++;
    }

    name[j] = '\0';

}  // fatfs_get_name_from_short()

//////////////////////////////////////////////////////////////////////////////////////////
// This static function retun in the <name> buffer a partial name stored in
// a LFN FATFS directory entry.
/////////////////////////i////////////////////////////////////////////////////////////////
// @ buffer   : pointer on buffer containing the directory entry.
// @ name     : [out] buffer allocated by the caller.
//////////////////////////////////////////////////////////////////////////////////////////
static void fatfs_get_name_from_long( uint8_t * buffer,
                                      char    * name )
{
    uint32_t   name_offset   = 0;
    uint32_t   buffer_offset = get_length(LDIR_ORD);
    uint32_t   l_name_1      = get_length(LDIR_NAME_1);
    uint32_t   l_name_2      = get_length(LDIR_NAME_2);
    uint32_t   l_name_3      = get_length(LDIR_NAME_3);
    uint32_t   l_attr        = get_length(LDIR_ATTR);
    uint32_t   l_type        = get_length(LDIR_TYPE);
    uint32_t   l_chksum      = get_length(LDIR_CHKSUM);
    uint32_t   l_rsvd        = get_length(LDIR_RSVD);

    uint32_t   j             = 0;
    uint32_t   eof           = 0;

    while ( (buffer_offset != DIR_ENTRY_SIZE)  && (!eof) )
    {
        while (j != l_name_1 && !eof )
        {
            if ( (buffer[buffer_offset] == 0x00) || 
                 (buffer[buffer_offset] == 0xFF) )
            {
                eof = 1;
                continue;
            }
            name[name_offset] = buffer[buffer_offset];
            buffer_offset += 2;
            j += 2;
            name_offset++;
        }

        buffer_offset += (l_attr + l_type + l_chksum);
        j = 0;

        while (j != l_name_2 && !eof )
        {
            if ( (buffer[buffer_offset] == 0x00) || 
                 (buffer[buffer_offset] == 0xFF) )
            {
                eof = 1;
                continue;
            }
            name[name_offset] = buffer[buffer_offset];
            buffer_offset += 2;
            j += 2;
            name_offset++;
        }

        buffer_offset += l_rsvd;
        j = 0;

        while (j != l_name_3 && !eof )
        {
            if ( (buffer[buffer_offset] == 0x00) || 
                 (buffer[buffer_offset] == 0xFF) )
            {
                eof = 1;
                continue;
            }
            name[name_offset] = buffer[buffer_offset];
            buffer_offset += 2;
            j += 2;
            name_offset++;
        }
    }
    name[name_offset] = 0;

} // end fatfs_get_name_from_long()

//////////////////////////////////////////////////////////////////////////////////////////
// This static function analyse the <name> input argument, and returns in other
// output arguments various informations required to store the name in FATFS directory.
// The <name> length cannot be larger than 31 characters :
// - Short name (less than 13 characters) require 1 LFN entry.
// - Medium names (from 14 to 26 characters require 2 LFN entries.
// - Large names (up to 31 characters) require 3 LFN entries.
//////////////////////////////////////////////////////////////////////////////////////////
// @ name     : [in]  complete directory entry name.
// @ length   : [out] total number of characters in name.
// @ nb_lfn   : [out] number of LFN entries required to store the name.
// @ sfn      : [out] a legal SFN name extracted from name / upper case and 8-3 format.
// @ checksum : [out] checksum to be stored in SFN.
// @ returns 0 on success / returns 1 if the name length is larger than 31 characters.
//////////////////////////////////////////////////////////////////////////////////////////
static error_t fatfs_name_format( const char  * name,
                                  uint32_t    * length,
                                  uint32_t    * nb_lfn,
                                  char        * sfn,
                                  uint8_t     * checksum )
{
    // compute name length
    uint32_t name_length = strlen( name );
    *length = name_length;

    uint32_t suffix_length = 0;
    uint32_t prefix_length = 0;
    uint32_t dot_found     = 0;
    uint32_t i;

    // compute prefix and suffix length
    // only the last '.' is taken into account
    for ( i=0 ; i<name_length ; i++ )
    {
        if (name[i] == '.' )
        {
            if ( dot_found ) 
            {
                prefix_length += suffix_length + 1;
                suffix_length =  0;
            }
            else
            {
                dot_found = 1;
            }
        }
        else
        { 
            if ( dot_found)  suffix_length++;
            else             prefix_length++;
        }
    } 

    // build SFN prefix (8bits)
    if (prefix_length <= 8)
    {
        for( i=0 ; i<8 ; i++)
        {
            if ( i<prefix_length ) sfn[i] = to_upper( name[i] );
            else                   sfn[i] = 0x20;
        }
    }
    else
    {
        for( i=0 ; i<6 ; i++)
        {
            sfn[i] = to_upper( name[i] );
        }
        sfn[6] = 0x7E;
        sfn[7] = 0x31;
    }

    // build SFN suffix (3 bits)
    if ( suffix_length == 0 )
    {
        sfn[8]  = 0x20;
        sfn[9]  = 0x20;
        sfn[10] = 0x20;
    }
    else if ( suffix_length == 1 )
    {
        sfn[8]  = to_upper( name[name_length-1] );
        sfn[9]  = 0x20;
        sfn[10] = 0x20;
    }
    else if ( suffix_length == 2 )
    {
        sfn[8]  = to_upper( name[name_length-2] );
        sfn[9]  = to_upper( name[name_length-1] );
        sfn[10] = 0x20;
    }
    else
    {
        sfn[8]  = to_upper( name[name_length-suffix_length] );
        sfn[9]  = to_upper( name[name_length-suffix_length+1] );
        sfn[10] = to_upper( name[name_length-suffix_length+2] );
    }

    // compute 8 bits checksum
    uint8_t sum = 0;
    for ( i=0 ; i<11 ; i++ )
    {
        sum = (((sum & 0x01)<<7) | ((sum & 0xFE)>>1)) + sfn[i];
    }
    *checksum = sum;

    // set nb_lfn and length values
    if      ( name_length <= 13 )
    {
        *nb_lfn  = 1;
        return 0;
    }
    else if ( name_length <= 26 )
    {
        *nb_lfn  = 2;
        return 0;
    }
    else if ( name_length <= 31 )
    {
        *nb_lfn  = 3;
        return 0;
    }
    else
    {
        return 1;
    }
}   // end fatfs_name_format()  

//////////////////////////////////////////////////////////////////////////////////////////
// This static function - atomically - decrements "free_clusters", and updates 
// the "free_cluster_hint" shared variables in the FATFS context in the FAT cluster.
// It scan the FAT to find the first free slot larger than the <cluster> argument,
// and set "free_cluster_hint" <= (free - 1).
//
// WARNING : The free_lock protecting exclusive access to these variables
//           must be taken by the calling function. 
//////////////////////////////////////////////////////////////////////////////////////////
// @ cluster     : recently allocated cluster index in FAT.
//////////////////////////////////////////////////////////////////////////////////////////
static error_t fatfs_free_clusters_decrement( uint32_t  cluster )
{
    fatfs_ctx_t * loc_ctx;      // local pointer on local FATFS context
    fatfs_ctx_t * fat_ctx;      // local pointer on FATFS in cluster containing FAT mapper
    cxy_t         mapper_cxy;   // cluster identifier for cluster containing FAT mapper
    xptr_t        mapper_xp;    // extended pointer on FAT mapper
    xptr_t        hint_xp;      // extended pointer on "free_cluster_hint" shared variable
    xptr_t        numb_xp;      // extended pointer on "free_clusters" shared variable 
    uint32_t      numb;         // "free_clusters" variable current value
    uint32_t      page_id;      // page index in FAT mapper
    uint32_t      slot_id;      // slot index in one page of FAT (1024 slots per page)
    uint32_t      page_max;     // max number of pages in FAT mapper
    xptr_t        page_xp;      // extended pointer on current page in FAT mapper
    xptr_t        slot_xp;      // extended pointer on current slot in FAT mapper

#if DEBUG_FATFS_FREE_CLUSTERS
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
if( DEBUG_FATFS_FREE_CLUSTERS < (uint32_t)hal_get_cycles() )
printk("\n[%s] thread[%x,%x] enter for allocated cluster %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, cluster , cycle );
#endif

    // get local pointer on local FATFS context
    loc_ctx = fs_context[FS_TYPE_FATFS].extend;

    // get cluster containing FAT mapper
    mapper_xp  = loc_ctx->fat_mapper_xp;
    mapper_cxy = GET_CXY( mapper_xp ); 

    // get local pointer on FATFS context in FAT cluster
    fat_ctx = hal_remote_lpt( XPTR( mapper_cxy , &fs_context[FS_TYPE_FATFS].extend ) );

    // build extended pointers on free_clusters, and free_cluster_hint
    hint_xp = XPTR( mapper_cxy , &fat_ctx->free_cluster_hint );
    numb_xp = XPTR( mapper_cxy , &fat_ctx->free_clusters );

    // update "free_clusters"
    numb = hal_remote_l32( numb_xp );
    hal_remote_s32( numb_xp , numb - 1 );

    // scan FAT mapper to find the first free slot > cluster
    // and update "free_cluster_hint" as (free - 1)
    page_id  = (cluster + 1) >> 10;
    slot_id  = (cluster + 1) & 0x3FF;
    page_max = (loc_ctx->fat_sectors_count >> 3);

    // scan FAT mapper / loop on pages
    while ( page_id < page_max )            
    {
        // get current page from mapper
        page_xp = mapper_remote_get_page( mapper_xp , page_id );

        if( page_xp == XPTR_NULL )
        {
            printk("\n[ERROR] in %s : cannot access FAT mapper\n", __FUNCTION__ );
            return -1;
        }

        // scan FAT mapper / loop on slots
        while ( slot_id < 1024 )
        {
            // get extended pointer on current slot
            slot_xp = ppm_page2base( page_xp ) + (slot_id << 2);

            // test FAT slot value 
            if ( hal_remote_l32( slot_xp ) == FREE_CLUSTER )
            {
                // update "free_cluster_hint" <= (free - 1)
                hal_remote_s32( hint_xp , (page_id << 10) + slot_id - 1 );

#if DEBUG_FATFS_FREE_CLUSTERS 
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_FREE_CLUSTERS < (uint32_t)hal_get_cycles() )
printk("\n[%s] thread[%x,%x] exit / hint %x / free_clusters %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid,
hal_remote_l32(hint_xp), hal_remote_l32(numb_xp), cycle );
#endif
                return 0;
            }

            // increment  slot_id
            slot_id++;

        }  // end loop on slots

        // update loop variables
        page_id++;
        slot_id = 0;

    }  // end loop on pages

    // return error if no free cluster found 
    printk("\n[ERROR] in %s : No free cluster found\n", __FUNCTION__ );
    return -1;
    
}  // end fatfs_free_clusters_decrement()

//////////////////////////////////////////////////////////////////////////////////////////
// This static function atomically increments <free_clusters>, and updates 
// the <free_cluster_hint> shared variables in the FATFS context in the FAT cluster.
// If the released cluster index is smaller than the current (hint + 1) value, 
// it set "free_cluster_hint" <= cluster - 1.
//
// WARNING : The free_lock protecting exclusive access to these variables
//           must be taken by the calling function. 
//////////////////////////////////////////////////////////////////////////////////////////
// @ cluster     : recently released cluster index in FAT.
//////////////////////////////////////////////////////////////////////////////////////////
static void fatfs_free_clusters_increment( uint32_t  cluster )
{
    fatfs_ctx_t * loc_ctx;      // local pointer on local FATFS context
    fatfs_ctx_t * fat_ctx;      // local pointer on FATFS in cluster containing FAT mapper
    cxy_t         fat_cxy;      // cluster identifier for cluster containing FAT mapper
    xptr_t        hint_xp;      // extended pointer on "free_cluster_hint" shared variable
    xptr_t        numb_xp;      // extended pointer on "free_clusters" shared variable 
    uint32_t      hint;         // "free_cluster_hint" variable current value
    uint32_t      numb;         // "free_clusters" variable current value

    // get local pointer on local FATFS context
    loc_ctx = fs_context[FS_TYPE_FATFS].extend;

    // get cluster containing FAT mapper
    fat_cxy = GET_CXY( loc_ctx->fat_mapper_xp );

    // get local pointer on FATFS context in FAT cluster
    fat_ctx = hal_remote_lpt( XPTR( fat_cxy , &fs_context[FS_TYPE_FATFS].extend ) );

    // build extended pointers free_lock, free_clusters, and free_cluster_hint
    hint_xp = XPTR( fat_cxy , &fat_ctx->free_cluster_hint );
    numb_xp = XPTR( fat_cxy , &fat_ctx->free_clusters );

    // get current value of free_cluster_hint and free_clusters
    hint = hal_remote_l32( hint_xp );
    numb = hal_remote_l32( numb_xp );

    // update free_cluster_hint if required
    if ( cluster < (hint + 1) ) hal_remote_s32( hint_xp , (cluster - 1) );

    // update free_clusters
    hal_remote_s32( numb_xp , numb + 1 );

#if DEBUG_FATFS_FREE_CLUSTERS 
thread_t * this = CURRENT_THREAD;
if( DEBUG_FATFS_FREE_CLUSTERS < (uint32_t)hal_get_cycles() )
printk("\n[%s] thread[%x,%x] updates free cluster info : hint %x / number %x\n",
__FUNCTION__, this->process->pid, this->trdid,
hal_remote_l32( hint_xp ), hal_remote_l32( numb_xp ) );
#endif

}  // end fatfs_free_clusters_increment()

//////////////////////////////////////////////////////////////////////////////////////////
// This recursive function is called by the generic function fatfs_release_all_clusters()
// It release all clusters allocated to a given inode in the FAT mapper.
// The removal is done in reverse order of the linked list (from last to first).
// It does NOT update the FS on the IOC device.
//////////////////////////////////////////////////////////////////////////////////////////
// @ fat_mapper_xp : extended pointer on FAT mapper.
// @ cluster       : cluster index in FAT.
// @ return 0 if success / return -1 if error (cannot access FAT)
//////////////////////////////////////////////////////////////////////////////////////////
static error_t fatfs_recursive_release( xptr_t    fat_mapper_xp,
                                        uint32_t  cluster )
{
    uint32_t next;

    // get next cluster from FAT mapper
    if ( mapper_remote_get_32( fat_mapper_xp , cluster , &next ) ) return -1;

#if (DEBUG_FATFS_RELEASE_INODE & 1)
thread_t * this = CURRENT_THREAD;
if ( DEBUG_FATFS_RELEASE_INODE < (uint32_t)hal_get_cycles() )
printk("\n[%s] thread[%x,%x] access FAT for cluster %x / next %x\n",
__FUNCTION__, this->process->pid, this->trdid, cluster, next );
#endif

    if ( next < END_OF_CHAIN_CLUSTER_MIN )  // non terminal case
    {
        // call fatfs_recursive_release() on next cluster
        if ( fatfs_recursive_release( fat_mapper_xp , next ) ) return -1;
    }       

    // update current cluster in FAT mapper
    if ( mapper_remote_set_32( fat_mapper_xp, cluster , FREE_CLUSTER ) ) return -1;

    // Update free_cluster_hint and free_clusters in FAT context
    fatfs_free_clusters_increment( cluster );

    return 0;

}  // end fatfs_recursive_release()


//////////////////////////////////////////////////////////////////////////////////////////
//              FATFS specific extern functions 
//////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////
void fatfs_ctx_display( void )
{
    // get pointer on local FATFS context
    vfs_ctx_t   * vfs_ctx   = &fs_context[FS_TYPE_FATFS];
    fatfs_ctx_t * fatfs_ctx = (fatfs_ctx_t *)vfs_ctx->extend;

    printk("\n*** FAT context ***\n" 
           "- fat_sectors       = %d\n"
           "- sector size       = %d\n"
           "- cluster size      = %d\n"
           "- fat_first_lba     = %d\n"
           "- data_first_lba    = %d\n"
           "- root_dir_cluster  = %d\n"
           "- free_clusters     = %d\n"
           "- free_cluster_hint = %d\n"
           "- fat_mapper_xp     = %l\n",
           fatfs_ctx->fat_sectors_count,
           fatfs_ctx->bytes_per_sector,
           fatfs_ctx->sectors_per_cluster * fatfs_ctx->bytes_per_sector,
           fatfs_ctx->fat_begin_lba,
           fatfs_ctx->cluster_begin_lba,
           fatfs_ctx->root_dir_cluster,
           fatfs_ctx->free_clusters,
           fatfs_ctx->free_cluster_hint,
           fatfs_ctx->fat_mapper_xp );

}  // end fatfs_ctx_display()

//////////////////////////////////////////
void fatfs_display_fat( uint32_t  page_id,
                        uint32_t  nentries )
{
    uint32_t line;
    uint32_t maxline;

    // copute numner of lines to display
    maxline = nentries >> 3;
    if( nentries & 0x7 ) maxline++;

    // get pointer on local FATFS context
    vfs_ctx_t   * vfs_ctx   = &fs_context[FS_TYPE_FATFS];
    fatfs_ctx_t * fatfs_ctx = (fatfs_ctx_t *)vfs_ctx->extend;

    // get extended pointer on FAT mapper
    xptr_t fat_mapper_xp = fatfs_ctx->fat_mapper_xp;

    // get extended pointer and cluster of FAT mapper requested page 
    xptr_t     page_xp  = mapper_remote_get_page( fat_mapper_xp , page_id );

    // get extended pointer on requested page base
    xptr_t     base_xp  = ppm_page2base( page_xp );

    printk("\n***** FAT content / page %d *****\n", page_id );
    for( line = 0 ; line < maxline ; line++ )
    {
        printk("%x : %X | %X | %X | %X | %X | %X | %X | %X\n", (line<<3),
        hal_remote_l32( base_xp + ((line<<5)      ) ),
        hal_remote_l32( base_xp + ((line<<5) + 4  ) ),
        hal_remote_l32( base_xp + ((line<<5) + 8  ) ),
        hal_remote_l32( base_xp + ((line<<5) + 12 ) ),
        hal_remote_l32( base_xp + ((line<<5) + 16 ) ),
        hal_remote_l32( base_xp + ((line<<5) + 20 ) ),
        hal_remote_l32( base_xp + ((line<<5) + 24 ) ),
        hal_remote_l32( base_xp + ((line<<5) + 28 ) ) );
    }

}  // end fatfs_display_fat()

///////////////////////////////////////////////////////
error_t fatfs_get_cluster( uint32_t   first_cluster_id,
                           uint32_t   searched_page_index,
                           uint32_t * searched_cluster_id )
{
    xptr_t     current_page_xp;        // pointer on current page descriptor
    uint32_t * buffer;                 // pointer on current page (array of uint32_t)
    uint32_t   current_page_index;     // index of current page in FAT 
    uint32_t   current_page_offset;    // offset of slot in current page
    uint32_t   page_count_in_file;     // index of page in file (index in linked list)
    uint32_t   next_cluster_id;        // content of current FAT slot

assert( (searched_page_index > 0) ,
"no FAT access required for first page\n");

#if DEBUG_FATFS_GET_CLUSTER
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
if( DEBUG_FATFS_GET_CLUSTER < cycle )
printk("\n[%s] thread[%x,%x] enter / first_cluster_id %d / searched_index / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, first_cluster_id, searched_page_index, cycle );
#endif

    // get local pointer on local FATFS context
    fatfs_ctx_t * ctx = fs_context[FS_TYPE_FATFS].extend;

    // get extended pointer and cluster on FAT mapper
    xptr_t mapper_xp  = ctx->fat_mapper_xp;
    cxy_t  mapper_cxy = GET_CXY( mapper_xp );

    // initialize loop variable (1024 slots per page)
    current_page_index  = first_cluster_id >> 10;
    current_page_offset = first_cluster_id & 0x3FF;
    page_count_in_file  = 0;
    next_cluster_id     = 0xFFFFFFFF;

    // scan FAT (i.e. traverse FAT linked list)
    while( page_count_in_file < searched_page_index )
    {
        // get pointer on current page descriptor
        current_page_xp = mapper_remote_get_page( mapper_xp , current_page_index );

        if( current_page_xp == XPTR_NULL )
        {
            // TODO
            return -1;
        }

        // get pointer on buffer for current page
        xptr_t base_xp = ppm_page2base( current_page_xp );
        buffer = (uint32_t *)GET_PTR( base_xp );

        // get FAT slot content 
        next_cluster_id = hal_remote_l32( XPTR( mapper_cxy , &buffer[current_page_offset] ) );

#if (DEBUG_FATFS_GET_CLUSTER & 1)
if( DEBUG_FATFS_GET_CLUSTER < cycle )
printk("\n[%s] traverse FAT / current_page_index = %d\n"
"current_page_offset = %d / next_cluster_id = %d\n",
__FUNCTION__, current_page_index, current_page_offset , next_cluster_id );
#endif

        // update loop variables
        current_page_index  = next_cluster_id >> 10;
        current_page_offset = next_cluster_id & 0x3FF;
        page_count_in_file++;
    }

    if( next_cluster_id == 0xFFFFFFFF ) return -1;
   
#if DEBUG_FATFS_GET_CLUSTER
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_GET_CLUSTER < cycle )
printk("\n[%s] thread[%x,%x] exit / searched_cluster_id = %d / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, next_cluster_id / cycle );
#endif

    *searched_cluster_id = next_cluster_id;
    return 0;

}  // end fatfs_get_cluster()



///////////////////////////////////////////////////////////////////////////////////////
// Generic API : the following functions are called by the kernel VFS
//               and must be defined by all supported file systems.
///////////////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////
fatfs_ctx_t * fatfs_ctx_alloc( void )
{
    kmem_req_t    req;
	req.type    = KMEM_FATFS_CTX;
	req.size    = sizeof(fatfs_ctx_t);
    req.flags   = AF_KERNEL | AF_ZERO;

	return (fatfs_ctx_t *)kmem_alloc( &req );
}

//////////////////////////////////////////////
void fatfs_ctx_init( fatfs_ctx_t * fatfs_ctx )
{
    error_t       error;
    kmem_req_t    req;
    uint8_t     * buffer;

#if DEBUG_FATFS_CTX_INIT
uint32_t cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_CTX_INIT < cycle )
printk("\n[%s] thread[%x,%x] enter for fatfs_ctx = %x / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, fatfs_ctx , cycle );
#endif

// check argument
assert( (fatfs_ctx != NULL) , "pointer on FATFS context is NULL\n" );

// check only cluster 0 does FATFS init
assert( (local_cxy == 0) , "only cluster 0 can initialize FATFS\n");

    // allocate a 512 bytes buffer to store the boot record
	req.type    = KMEM_512_BYTES;
    req.flags   = AF_KERNEL | AF_ZERO;
	buffer      = (uint8_t *)kmem_alloc( &req );

    if( buffer == NULL ) 
    {
        printk("\n[PANIC] in %s : cannot allocate buffer\n", __FUNCTION__ );
        hal_core_sleep();
    }
     
    // load the BOOT record from device
    error = dev_ioc_sync_read( buffer , 0 , 1 );

    if ( error )
    {
        printk("\n[PANIC] in %s : cannot access boot record\n", __FUNCTION__ );
        hal_core_sleep();
    }

#if (DEBUG_FATFS_CTX_INIT & 0x1)
if( DEBUG_FATFS_CTX_INIT < cycle )
{
    uint32_t   line;
    uint32_t   byte = 0;
    printk("\n***** %s : FAT boot record\n", __FUNCTION__ );
    for ( line = 0 ; line < 32 ; line++ )
    {
        printk(" %X | %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x |\n",
               byte,
               buffer[byte+ 0],buffer[byte+ 1],buffer[byte+ 2],buffer[byte+ 3],
               buffer[byte+ 4],buffer[byte+ 5],buffer[byte+ 6],buffer[byte+ 7],
               buffer[byte+ 8],buffer[byte+ 9],buffer[byte+10],buffer[byte+11],
               buffer[byte+12],buffer[byte+13],buffer[byte+14],buffer[byte+15] );

         byte += 16;
    }
}
#endif

    // get sector size from boot record
    uint32_t sector_size = fatfs_get_record( BPB_BYTSPERSEC , buffer , 1 );
    if ( sector_size != 512 )
    {
        printk("\n[PANIC] in %s : sector size must be 512 bytes\n", __FUNCTION__ );
        hal_core_sleep();
    }

    // get cluster size from boot record
    uint32_t nb_sectors = fatfs_get_record( BPB_SECPERCLUS , buffer , 1 );
    if ( nb_sectors != 8 )
    {
        printk("\n[PANIC] in %s : cluster size must be 8 sectors\n", __FUNCTION__ );
        hal_core_sleep();
    }

    // get number of FAT copies from boot record
    uint32_t nb_fats = fatfs_get_record( BPB_NUMFATS , buffer , 1 );
    if ( nb_fats != 1 )
    {
        printk("\n[PANIC] in %s : number of FAT copies must be 1\n", __FUNCTION__ );
        hal_core_sleep();
    }

    // get number of sectors in FAT from boot record
    uint32_t fat_sectors = fatfs_get_record( BPB_FAT32_FATSZ32 , buffer , 1 );
    if ( (fat_sectors & 0xF) != 0 )
    {
        printk("\n[PANIC] in %s : FAT size not multiple of 16 sectors\n", __FUNCTION__ );
        hal_core_sleep();
    }

    // get root cluster from boot record
    uint32_t root_cluster = fatfs_get_record( BPB_FAT32_ROOTCLUS , buffer , 1 );
    if ( root_cluster != 2 ) 
    {
        printk("\n[PANIC] in %s : root cluster index must be 2\n", __FUNCTION__ );
        hal_core_sleep();
    }

    // get FAT lba from boot record
    uint32_t fat_lba = fatfs_get_record( BPB_RSVDSECCNT , buffer , 1 );

    // get FS_INFO sector lba from boot record
    uint32_t fs_info_lba = fatfs_get_record( BPB_FAT32_FSINFO , buffer , 1 );

    // load the FS_INFO record from device
    error = dev_ioc_sync_read( buffer , fs_info_lba , 1 );

    if ( error )
    {
        printk("\n[PANIC] in %s : cannot access FS_INFO record\n", __FUNCTION__ );
        hal_core_sleep();
    }

    // get free clusters number from FS_INFO record
    uint32_t free_clusters = fatfs_get_record( FS_FREE_CLUSTERS , buffer , 1 );
    if ( free_clusters >= fat_sectors << 7 )
    {
        printk("\n[PANIC] in %s : unconsistent free_clusters\n", __FUNCTION__ );
        hal_core_sleep();
    }

    // get cluster hint from FS_INFO record
    uint32_t free_cluster_hint = fatfs_get_record( FS_FREE_CLUSTER_HINT , buffer , 1 );
    if ( free_cluster_hint >= fat_sectors << 7 )
    {
        printk("\n[PANIC] in %s : unconsistent free_cluster_hint\n", __FUNCTION__ );
        hal_core_sleep();
    }

    // release the 512 bytes buffer
    req.type = KMEM_512_BYTES;
    req.ptr  = buffer;
    kmem_free( &req );

    // allocate a mapper for the FAT itself
    mapper_t * fat_mapper = mapper_create( FS_TYPE_FATFS );
    if ( fat_mapper == NULL )
    {
        printk("\n[PANIC] in %s : no memory for FAT mapper\n", __FUNCTION__ );
        hal_core_sleep();
    }

    // WARNING : the inode field MUST be NULL for the FAT mapper
    fat_mapper->inode = NULL;

    // initialize the FATFS context
    fatfs_ctx->fat_begin_lba         = fat_lba;
    fatfs_ctx->fat_sectors_count     = fat_sectors; 
    fatfs_ctx->bytes_per_sector      = sector_size;
    fatfs_ctx->sectors_per_cluster   = nb_sectors;
    fatfs_ctx->cluster_begin_lba     = fat_lba + fat_sectors;
    fatfs_ctx->root_dir_cluster      = 2;
    fatfs_ctx->fat_mapper_xp         = XPTR( local_cxy , fat_mapper );
    fatfs_ctx->free_clusters         = free_clusters;
    fatfs_ctx->free_cluster_hint     = free_cluster_hint;

    remote_queuelock_init( XPTR( local_cxy , &fatfs_ctx->free_lock ) , LOCK_FATFS_FREE );

#if DEBUG_FATFS_CTX_INIT
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_CTX_INIT < cycle )
printk("\n[%s]  thread[%x,%x] exit for fatfs_ctx = %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, fatfs_ctx, cycle );
#endif

}  // end fatfs_ctx_init()

/////////////////////////////////////////////////
void fatfs_ctx_destroy( fatfs_ctx_t * fatfs_ctx )
{
    kmem_req_t    req;
    req.type = KMEM_FATFS_CTX;
    req.ptr  = fatfs_ctx;
    kmem_free( &req );
}

///////////////////////////////////////////////
error_t fatfs_add_dentry( vfs_inode_t  * inode,
                          vfs_dentry_t * dentry )
{
    error_t       error;
    uint32_t      length;        // dentry name length
    uint32_t      nb_lfn;        // number or required LFN
    char          sfn[11];       // buffer for SFN name
    uint8_t       checksum;      // name checksum 
    mapper_t    * mapper;        // loal pointer on parent inode mapper
    xptr_t        mapper_xp;     // extended pointer on parent inode mapper
    xptr_t        child_xp;      // extended pointer on child inode 
    cxy_t         child_cxy;     // child inode cluster
    vfs_inode_t * child_ptr;     // child inode local pointer
    uint32_t      size;          // child inode size
    uint32_t      type;          // child inode type
    uint32_t      cluster;       // child inode cluster index

#if DEBUG_FATFS_ADD_DENTRY
char       dir_name[CONFIG_VFS_MAX_NAME_LENGTH];
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
vfs_inode_get_name( XPTR( local_cxy , inode ) , dir_name );
if( DEBUG_FATFS_ADD_DENTRY < cycle )
printk("\n[%s]  thread[%x,%x] enter / parent <%s> / child <%s> / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, dir_name, dentry->name, cycle );
#endif

// check arguments
assert( (inode != NULL) , "inode pointer is NULL\n" );
assert( (dentry != NULL) , "dentry pointer is NULL\n" );
assert( (inode->type == INODE_TYPE_DIR) , "inode is not a directory\n" );
assert( (inode->mapper != NULL ) , "mapper pointer is NULL\n" );
  
    // get pointers on directory mapper
    mapper    = inode->mapper;
    mapper_xp = XPTR( local_cxy , mapper );

    // get extended pointers on remote child inode
    child_xp  = dentry->child_xp;
    child_cxy = GET_CXY( child_xp );
    child_ptr = GET_PTR( child_xp );

    // get relevant infos from child inode
    type    =                     hal_remote_l32( XPTR( child_cxy , &child_ptr->type ) );
    size    =                     hal_remote_l32( XPTR( child_cxy , &child_ptr->size ) );
    cluster = (uint32_t)(intptr_t)hal_remote_lpt( XPTR( child_cxy , &child_ptr->extend ) );

    // analyse dentry name
    error = fatfs_name_format( dentry->name,
                               &length,
                               &nb_lfn,
                               sfn,
                               &checksum );
    if ( error )
    {
        printk("\n[ERROR] in %s : dentry name > 31 bytes\n", __FUNCTION__ );
        return -1;
    }
                               
    // Search end of directory with two embedded loops:
    // - scan the pages in the mapper
    // - scan the entries in each page to find NO_MORE_ENTRY

    xptr_t     page_xp;                 // extended pointer on page descriptor
    xptr_t     base_xp;                 // extended pointer on page base
    uint8_t  * base;                    // local pointer on page base (array of bytes)
    uint32_t   page_id = 0;             // page index in mapper
    uint32_t   offset  = 0;             // position in page
    uint32_t   found   = 0;             // NO_MORE_ENTRY found

    // loop on pages in mapper
    while ( found == 0 )
    {
        // get extended pointer on page descriptor in mapper 
        page_xp  = mapper_remote_get_page( mapper_xp , page_id );

        if ( page_xp == XPTR_NULL )
        {
            printk("\n[ERROR] in %s : cannot extend directory mapper\n", __FUNCTION__ );
            return -1;
        }
        
        // get pointer on page base
        base_xp = ppm_page2base( page_xp );
        base = GET_PTR( base_xp );

        // loop on directory entries in this page
        while ( (offset < 4096) && (found == 0) )
        {
            if ( fatfs_get_record( LDIR_ORD, (base + offset), 0 ) == NO_MORE_ENTRY )
            {
                found = 1;
            }  
            else
            {
                offset = offset + 32;
            }
        }  // end loop on entries

        if ( found == 0 )
        {
            page_id++;
            offset = 0;
        }
    }  // end loop on pages

    // Modify the directory mapper: depending on the name length,
    // the new child requires to write (3, 4, or 5) directory entries.
    // To actually register the new child, we use a 5 steps FSM
    // (one state per entry to be written), that is traversed as:
    // LFN3 -> LFN2 -> LFN1 -> NORMAL -> NOMORE 
    // At most two pages are modified:
    // - the page containing the NO_MORE_ENTRY is always modified
    // - the following page can be modified if the name spread on to pages.

    char * name = dentry->name;

    uint32_t step;          // FSM state

    if      ( nb_lfn == 1 ) step = 3;
    else if ( nb_lfn == 2 ) step = 4;
    else if ( nb_lfn == 3 ) step = 5;
    
    uint8_t  * entry;       // pointer on directory entry to be written
    uint32_t   i;           // byte index in one 32 bytes directory
    uint32_t   c;           // character index in name

    while ( step )   
    {
        // when the new child is split on two pages,
        // we need to access a new page in mapper
        if ( offset >= 4096 )
        {
            // copy the modified page to IOC device
            fatfs_move_page( page_xp , IOC_SYNC_WRITE );   

            // get the next page in FAT mapper
            page_xp  = mapper_remote_get_page( mapper_xp , page_id + 1 );

            if ( page_xp == XPTR_NULL )
            {
                printk("\n[ERROR] in %s : cannot extend directory mapper\n", __FUNCTION__ );
                return -1;
            }
        
            // get pointer on page base
            base_xp = ppm_page2base( page_xp );
            base = GET_PTR( base_xp );
            
            // update offset
            offset = 0;
        }

        // compute directory entry address
        entry = base + offset;

#if (DEBUG_FATFS_ADD_DENTRY & 1)
if( DEBUG_FATFS_ADD_DENTRY < cycle )
printk("\n[%s] FSM step = %d / offset = %x / nb_lfn = %d\n",
__FUNCTION__, step, offset, nb_lfn );
#endif

        // write 32 bytes (one directory entry) per iteration
        switch ( step )
        {
            case 5:   // write LFN3 entry
            {
                c = 26;
                // scan the 32 bytes in dir_entry
                for ( i = 0 ; i < 32 ; i++ )
                {
                    if (i == 0)
                    {
                        if ( nb_lfn == 3) entry[i] = 0x43;
                        else              entry[i] = 0x03;
                    }
                    else if ( ( ((i >= 1 ) && (i<=10) && ((i&1)==1))   ||
                                ((i >= 14) && (i<=25) && ((i&1)==0))   ||
                                ((i >= 28) && (i<=31) && ((i&1)==0)) ) &&
                              ( c < length ) )
                    {
                                          entry[i] = name[c];
                                          c++;
                    }
                    else if (i == 11)     entry[i] = 0x0F;
                    else if (i == 13)     entry[i] = checksum;
                    else                  entry[i] = 0x00;
                }
                step--;
                break;
            }
            case 4:   // write LFN2 entry  
            {
                c = 13;
                // scan the 32 bytes in dir_entry
                for ( i = 0 ; i < 32 ; i++ )
                {
                    if (i == 0)
                    {
                        if ( nb_lfn == 2) entry[i] = 0x42;
                        else              entry[i] = 0x02;
                    }
                    else if ( ( ((i >= 1 ) && (i<=10) && ((i&1)==1))   ||
                                ((i >= 14) && (i<=25) && ((i&1)==0))   ||
                                ((i >= 28) && (i<=31) && ((i&1)==0)) ) &&
                              ( c < length ) )
                    {
                                          entry[i] = name[c];
                                          c++;
                    }
                    else if (i == 11)     entry[i] = 0x0F;
                    else if (i == 13)     entry[i] = checksum;
                    else                  entry[i] = 0x00;
                }
                step--;
                break;
            }
            case 3:   // Write LFN1 entry   
            {
                c = 0;
                // scan the 32 bytes in dir_entry
                for ( i = 0 ; i < 32 ; i++ )
                {
                    if (i == 0)
                    {
                        if ( nb_lfn == 1) entry[i] = 0x41;
                        else              entry[i] = 0x01;
                    }
                    else if ( ( ((i >= 1 ) && (i<=10) && ((i&1)==1))   ||
                                ((i >= 14) && (i<=25) && ((i&1)==0))   ||
                                ((i >= 28) && (i<=31) && ((i&1)==0)) ) &&
                              ( c < length ) )
                    {
                                          entry[i] = name[c];
                                          c++;
                    }
                    else if (i == 11)     entry[i] = 0x0F;
                    else if (i == 13)     entry[i] = checksum;
                    else                  entry[i] = 0x00;
                }
                step--;
                break;
            }
            case 2:   // write NORMAL entry     
            {
                // scan the 32 bytes in dir_entry
                for ( i = 0 ; i < 32 ; i++ )
                {
                    if      ( i < 11 )                              // 8.3 SFN
                    {
                                          entry[i] = sfn[i];
                    }
                    else if (i == 11)                               // ATTR
                    {
                        if (type == INODE_TYPE_DIR)  entry[i] = 0x10;
                        else                         entry[i] = 0x20;
                    }
                    else if (i == 20)     entry[i] = cluster>>16;   // cluster.B2
                    else if (i == 21)     entry[i] = cluster>>24;   // cluster.B3
                    else if (i == 26)     entry[i] = cluster>>0;    // cluster.B0
                    else if (i == 27)     entry[i] = cluster>>8;    // cluster.B1
                    else if (i == 28)     entry[i] = size>>0;       // size.B0
                    else if (i == 29)     entry[i] = size>>8;       // size.B1
                    else if (i == 30)     entry[i] = size>>16;      // size.B2
                    else if (i == 31)     entry[i] = size>>24;      // size.B3
                    else                  entry[i] = 0x00;
                }

                // update the "extend" field in dentry descriptor
                dentry->extend = (void*)(intptr_t)(((page_id<<12) + offset)>>5);

                step--;
                break;
            }
            case 1:   // write NOMORE entry  
            {
                entry [0] = 0x00;
                step--;
                break;
            }
        } // end switch step

        offset += 32;

    } // exit while     

    // copy the modified page to the IOC device
    fatfs_move_page( page_xp , IOC_SYNC_WRITE );   

#if DEBUG_FATFS_ADD_DENTRY
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_ADD_DENTRY < cycle )
printk("\n[%s]  thread[%x,%x] exit / parent <%s> / child <%s> / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, dir_name, dentry->name, cycle );
#endif

    return 0;

}  // end fatfs_add_dentry()

//////////////////////////////////////////////////
error_t fatfs_remove_dentry( vfs_inode_t  * inode,
                             vfs_dentry_t * dentry )
{
    xptr_t     mapper_xp;  // extended pointer on mapper
    mapper_t * mapper;     // local pointer on mapper
    xptr_t     page_xp;    // extended pointer on mapper page descriptor
    xptr_t     base_xp;    // extended pointer on mapper page base
    uint8_t  * base;       // local pointer on mapper page base

#if DEBUG_FATFS_REMOVE_DENTRY
char       dir_name[CONFIG_VFS_MAX_NAME_LENGTH];
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
vfs_inode_get_name( XPTR( local_cxy , inode ) , dir_name );
if( DEBUG_FATFS_REMOVE_DENTRY < cycle )
printk("\n[%s] thread[%x,%x] enter / parent <%s> / child <%s> / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, dir_name, dentry->name, cycle );
#endif

// check arguments
assert( (inode != NULL) , "inode pointer is NULL\n" );
assert( (dentry != NULL) , "dentry pointer is NULL\n" );
assert( (inode->type == INODE_TYPE_DIR) , "inode is not a directory\n" );
assert( (inode->mapper != NULL ) , "mapper pointer is NULL\n" );

    // get pointers on directory mapper
    mapper    = inode->mapper;
    mapper_xp = XPTR( local_cxy , mapper );

    // compute number of LFN entries
    uint32_t nb_lfn;
    uint32_t name_length = strlen( dentry->name );

    if      ( name_length <= 13 ) nb_lfn  = 1;
    else if ( name_length <= 26 ) nb_lfn  = 2;
    else                          nb_lfn  = 3;

    // we must invalidate (2, 3 or 4) 32 bytes entries:
    // the NORMAL entry (registered in dentry->extend) and all preceding LFN entries
    // At most two pages are modified:
    // - the page containing the NORMAL entry is always modified.
    // - the preceding page is modified when the name spread on two pages. 

    // get 32 bytes directory entry index from dentry->extend 
    uint32_t  dentry_id  = (uint32_t)(intptr_t)dentry->extend; 

    // get page index and offset in parent directory mapper
    uint32_t  page_id    = dentry_id >> 7;
    uint32_t  offset     = (dentry_id & 0x7F)<<5;

#if DEBUG_FATFS_REMOVE_DENTRY & 1
if( DEBUG_FATFS_REMOVE_DENTRY < cycle )
printk("\n[%s] dentry_id %x / page_id %x / offset %x\n",
__FUNCTION__, dentry_id, page_id, offset );
#endif

    // get extended pointer on page descriptor from parent directory mapper
    page_xp  = mapper_remote_get_page( mapper_xp , page_id );

    if ( page_xp == XPTR_NULL )
    {
        printk("\n[ERROR] in %s : cannot extend directory mapper\n", __FUNCTION__ );
        return -1;
    }
        
    // get pointers on page base
    base_xp = ppm_page2base( page_xp );
    base    = GET_PTR( base_xp );

    // invalidate NORMAL entry in directory cache
    base[offset] = 0xE5;

    // invalidate LFN entries
    while ( nb_lfn )
    {
        if (offset == 0)  // we must load page (page_id - 1)
        {

// check page_id 
assert( (page_id > 0), "page_id and offset cannot be both 0\n" );

            // copy the modified page to the IOC device
            fatfs_move_page( page_xp , IOC_SYNC_WRITE );   

            // get extended pointer on page descriptor from parent directory mapper
            page_xp  = mapper_remote_get_page( mapper_xp , page_id );

            if ( page_xp == XPTR_NULL )
            {
                printk("\n[ERROR] in %s : cannot access directory mapper\n", __FUNCTION__ );
                return -1;
            }
        
            // get pointers on page base
            base_xp = ppm_page2base( page_xp );
            base    = GET_PTR( base_xp );

            // update offset
            offset = 4096;
        }

        offset = offset - 32;

// check for LFN entry
assert( (fatfs_get_record( DIR_ATTR, base + offset, 0 ) == ATTR_LONG_NAME_MASK ),
"this directory entry must be a LFN\n");

        // invalidate LFN entry
        base[offset] = 0xE5;

        nb_lfn--;
    }     

    // copy the modified page to the IOC device
    fatfs_move_page( page_xp , IOC_SYNC_WRITE );   
    

#if DEBUG_FATFS_REMOVE_DENTRY
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_REMOVE_DENTRY < cycle )
printk("\n[%s] thread[%x,%x] exit / parent %s / child %s / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, dir_name, dentry->name, cycle );
#endif

    return 0;

}  // end fatfs_remove_dentry

/////////////////////////////////////////////////////
error_t fatfs_get_dentry( vfs_inode_t * parent_inode,
                          char        * name,
                          xptr_t        child_inode_xp )
{
    // Two embedded loops to scan the directory mapper:
    // - scan the parent directory mapper pages 
    // - scan the directory entries in each 4 Kbytes page

#if DEBUG_FATFS_GET_DENTRY
char       parent_name[CONFIG_VFS_MAX_NAME_LENGTH];
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
vfs_inode_get_name( XPTR( local_cxy , parent_inode ) , parent_name );
if( DEBUG_FATFS_GET_DENTRY < cycle )
printk("\n[%s]  thread[%x,%x] enter for child <%s> in parent <%s> / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, name , parent_name , cycle );
#endif

// check parent_inode and child_inode
assert( (parent_inode != NULL) , "parent_inode is NULL\n" );
assert( (child_inode_xp != XPTR_NULL ) , "child_inode is XPTR_NULL\n" );

    mapper_t * mapper    = parent_inode->mapper;
    xptr_t     mapper_xp = XPTR( local_cxy , mapper );

// check parent mapper
assert( (mapper != NULL) , "parent mapper is NULL\n");
    
    char       cname[CONFIG_VFS_MAX_NAME_LENGTH];  // name extracter from each directory entry

    char       lfn1[16];         // buffer for one partial cname
    char       lfn2[16];         // buffer for one partial cname
    char       lfn3[16];         // buffer for one partial cname
    xptr_t     page_xp;          // extended pointer on page descriptor
    xptr_t     base_xp;          // extended pointer on page base
    uint8_t  * base;             // local pointer on page base
    uint8_t    attr;             // directory entry ATTR field
    uint8_t    ord;              // directory entry ORD field
    uint32_t   seq;              // sequence index
    uint32_t   lfn       = 0;    // LFN entries number
    uint32_t   size      = 0;    // searched file/dir size (bytes) 
    uint32_t   cluster   = 0;    // searched file/dir cluster index
    uint32_t   is_dir    = 0;    // searched file/dir type
    int32_t    found     = 0;    // not found (0) / name found (1) / end of dir (-1)
    uint32_t   page_id   = 0;    // page index in mapper
    uint32_t   dentry_id = 0;    // directory entry index 
    uint32_t   offset    = 0;    // byte offset in page

    // scan the parent directory mapper
    while ( found == 0 )
    {
        // get one page
        page_xp = mapper_remote_get_page( mapper_xp , page_id );

        if( page_xp == XPTR_NULL) return EIO;

        // get page base
        base_xp = ppm_page2base( page_xp );
        base    = (uint8_t *)GET_PTR( base_xp );

#if (DEBUG_FATFS_GET_DENTRY & 0x1)
if( DEBUG_FATFS_GET_DENTRY < cycle )
mapper_display_page( mapper_xp , page_id , 256 );
#endif
        // scan this page until end of directory, end of page, or name found
        while( (offset < 4096) && (found == 0) )
        {
            attr = fatfs_get_record( DIR_ATTR , base + offset , 0 );   
            ord  = fatfs_get_record( LDIR_ORD , base + offset , 0 );   

            if (ord == NO_MORE_ENTRY)                 // no more entry => break
            {
                found = -1;
            }
            else if ( ord == FREE_ENTRY )             // free entry => skip
            {
                offset = offset + 32;
            }
            else if ( attr == 0x28 )                  // volune_id => skip
            {
                offset = offset + 32;
            }
            else if ( attr == ATTR_LONG_NAME_MASK )   // LFN entry => get partial cname
            {
                seq = ord & 0x3;
                lfn = (seq > lfn) ? seq : lfn;   
                if      ( seq == 1 ) fatfs_get_name_from_long( base + offset, lfn1 );
                else if ( seq == 2 ) fatfs_get_name_from_long( base + offset, lfn2 );
                else if ( seq == 3 ) fatfs_get_name_from_long( base + offset, lfn3 );
                offset = offset + 32;
            }
            else                                 // NORMAL entry
            {
                // build the extracted name
                if      ( lfn == 0 )
                {
                    fatfs_get_name_from_short( base + offset , cname );
                }
                else if ( lfn == 1 )
                {
                    strcpy( cname      , lfn1 );
                }   
                else if ( lfn == 2 ) 
                {
                    strcpy( cname      , lfn1 );
                    strcpy( cname + 13 , lfn2 );
                }
                else if ( lfn == 3 ) 
                {
                    strcpy( cname      , lfn1 );
                    strcpy( cname + 13 , lfn2 );
                    strcpy( cname + 26 , lfn3 );
                }

                // get dentry arguments if extracted cname == searched name
                if ( strcmp( name , cname ) == 0 )
                {
                    cluster = (fatfs_get_record( DIR_FST_CLUS_HI , base + offset , 1 ) << 16) |
                              (fatfs_get_record( DIR_FST_CLUS_LO , base + offset , 1 )      ) ;
                    dentry_id = ((page_id<<12) + offset)>>5;
                    is_dir    = ((attr & ATTR_DIRECTORY) == ATTR_DIRECTORY);
                    size      = fatfs_get_record( DIR_FILE_SIZE , base + offset , 1 );
                    found     = 1;
                }
                offset = offset + 32;
                lfn    = 0;
            }
        }  // end loop on directory entries in page

        page_id++;
        offset = 0;

    }  // end loop on pages

    // analyse the result of scan

    if ( found == -1 )  // found end of directory => failure 
    {

#if DEBUG_FATFS_GET_DENTRY
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_GET_DENTRY < cycle )
printk("\n[%s]  thread[%x,%x] exit / child <%s> not found / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, name, cycle );
#endif

        return -1;
    }

    // get child inode cluster and local pointer
    cxy_t          inode_cxy = GET_CXY( child_inode_xp );
    vfs_inode_t  * inode_ptr = GET_PTR( child_inode_xp );

    // build extended pointer on parent dentried root
    xptr_t parents_root_xp = XPTR( inode_cxy , &inode_ptr->parents );

// check child inode has at least one parent
assert( (xlist_is_empty( parents_root_xp ) == false ), "child inode must have one parent\n");

    // get dentry pointers and cluster
    xptr_t         dentry_xp  = XLIST_FIRST( parents_root_xp , vfs_dentry_t , parents );
    vfs_dentry_t * dentry_ptr = GET_PTR( dentry_xp );
    cxy_t          dentry_cxy = GET_CXY( dentry_xp );

// check dentry descriptor in same cluster as parent inode
assert( (dentry_cxy == local_cxy) , "illegal dentry cluster\n" );

    // update the child inode "type", "size", and "extend" fields
    vfs_inode_type_t type = (is_dir) ? INODE_TYPE_DIR : INODE_TYPE_FILE;

    hal_remote_s32( XPTR( inode_cxy , &inode_ptr->type   ) , type );
    hal_remote_s32( XPTR( inode_cxy , &inode_ptr->size   ) , size );
    hal_remote_s32( XPTR( inode_cxy , &inode_ptr->extend ) , cluster );

    // update the dentry "extend" field
    dentry_ptr->extend = (void *)(intptr_t)dentry_id;

#if DEBUG_FATFS_GET_DENTRY
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_GET_DENTRY < cycle )
printk("\n[%s]  thread[%x,%x] exit / child <%s> loaded in <%s> / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, name, parent_name, cycle );
#endif

    return 0;

}  // end fatfs_get_dentry()

///////////////////////////////////////////////////////
error_t fatfs_get_user_dir( struct vfs_inode_s * inode,
                            struct dirent      * array, 
                            uint32_t             max_dirent,
                            uint32_t             min_dentry,
                            bool_t               detailed,
                            uint32_t           * entries,
                            bool_t             * done )
{
    // Two embedded loops to scan the directory mapper:
    // - scan the parent directory mapper pages starting always from page 0
    // - scan the 32 bytes NORMAL/LFN directory entries in each page
    // Only valid dentries are copied : dentry_id >= min_dentry && dirent_id < dirent_max

#if DEBUG_FATFS_GET_USER_DIR
char       inode_name[CONFIG_VFS_MAX_NAME_LENGTH];
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
vfs_inode_get_name( XPTR( local_cxy , inode ) , inode_name );
if( DEBUG_FATFS_GET_USER_DIR < cycle )
printk("\n[%s]  thread[%x,%x] enter for inode <%s> / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, inode_name , cycle );
#endif

    mapper_t * mapper    = inode->mapper;
    xptr_t     mapper_xp = XPTR( local_cxy , mapper );

// check mapper pointer
assert( (mapper != NULL) , "mapper is NULL\n");
    
// TODO handle the detailed flag
assert( (detailed == false), "detailed argument not supported/n");

    char       cname[CONFIG_VFS_MAX_NAME_LENGTH];  // name extracted from each dentry

    char       lfn1[16];           // buffer for one partial cname
    char       lfn2[16];           // buffer for one partial cname
    char       lfn3[16];           // buffer for one partial cname
    xptr_t     page_xp;            // extended pointer on page descriptor
    xptr_t     base_xp;            // extended pointer on page base
    uint8_t  * base;               // local pointer on page base
    uint8_t    attr;               // directory entry ATTR field
    uint8_t    ord;                // directory entry ORD field
    uint32_t   seq;                // sequence index
    uint32_t   lfn       = 0;      // LFN entries number
    uint32_t   offset    = 0;      // byte offset in page
    uint32_t   page_id   = 0;      // page index in mapper
    uint32_t   dentry_id = 0;      // valid (i.e. copied) dentry index in mapper 
    uint32_t   dirent_id = 0;      // slot index in dirent array to initialize
    bool_t     end       = false;  // true if end of directory found

    // loop on mapper pages
    while ( (end == false) && (dirent_id < max_dirent) )
    {
        // get one page from mapper
        page_xp = mapper_remote_get_page( mapper_xp , page_id );

        if( page_xp == XPTR_NULL) return -1;

        // get page base
        base_xp = ppm_page2base( page_xp );
        base    = (uint8_t *)GET_PTR( base_xp );

#if (DEBUG_FATFS_GET_USER_DIR & 0x1)
if( DEBUG_FATFS_GET_USER_DIR < cycle )
mapper_display_page( mapper_xp , page_id , 256 );
#endif 
        // loop on NORMAL/LFN (32 bytes) directory entries in this page
        while( (end == false) && (offset < 4096) )
        {
            // compute condition to copy one dentry to dirent array
            bool_t valid = (dentry_id >= min_dentry) && (dirent_id <  max_dirent );

            attr = fatfs_get_record( DIR_ATTR , base + offset , 0 );   
            ord  = fatfs_get_record( LDIR_ORD , base + offset , 0 );   

            if (ord == NO_MORE_ENTRY)                 // no more entry => break
            {
                end = true;
            }
            else if ( ord == FREE_ENTRY )             // free entry => skip
            {
                offset = offset + 32;
            }
            else if ( attr == 0x28 )                  // volune_id => skip
            {
                offset = offset + 32;
            }
            else if ( attr == ATTR_LONG_NAME_MASK )   // LFN entry
            {
                if( valid )
                {
                    // get partial cname
                    seq = ord & 0x3;
                    lfn = (seq > lfn) ? seq : lfn;   
                    if      ( seq == 1 ) fatfs_get_name_from_long( base + offset, lfn1 );
                    else if ( seq == 2 ) fatfs_get_name_from_long( base + offset, lfn2 );
                    else if ( seq == 3 ) fatfs_get_name_from_long( base + offset, lfn3 );
                }
                offset = offset + 32;
            }
            else                                     // NORMAL entry
            {
                // increment dentry_id
                dentry_id++;

                if( valid )
                {
                    // build the complete cname
                    if      ( lfn == 0 )
                    {
                        fatfs_get_name_from_short( base + offset , cname );
                    }
                    else if ( lfn == 1 )
                    {
                        strcpy( cname      , lfn1 );
                    }   
                    else if ( lfn == 2 ) 
                    {
                        strcpy( cname      , lfn1 );
                        strcpy( cname + 13 , lfn2 );
                    }
                    else if ( lfn == 3 ) 
                    {
                        strcpy( cname      , lfn1 );
                        strcpy( cname + 13 , lfn2 );
                        strcpy( cname + 26 , lfn3 );
                    }
                    
                    // copy cname into dirent array
                    strcpy( array[dirent_id].d_name , cname ); 

                    // increment dirent_id
                    dirent_id++;
                }
                offset = offset + 32;
                lfn    = 0;
            }
        }  // end loop on directory entries in page

        page_id++;
        offset = 0;

    }  // end loop on pages

    // return result of scan
    *done    = end;
    *entries = dirent_id;

#if DEBUG_FATFS_GET_USER_DIR
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_GET_USER_DIR < cycle )
printk("\n[%s]  thread[%x,%x] exit for inode <%s> / %d entries / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, inode_name, dirent_id, cycle );
#endif

    return 0;

}  // end fatfs_get_user_dir()

///////////////////////////////////////////////
error_t fatfs_sync_inode( vfs_inode_t * inode )
{

// check inode pointer and cluster index
assert( (inode != NULL)                  , "inode pointer undefined\n" );
assert( (inode->mapper != NULL )         , "mapper pointer undefined\n" );
assert( (inode->type == INODE_TYPE_FILE) , "inode must be a file\n" );     

#if DEBUG_FATFS_SYNC_INODE
char       name[CONFIG_VFS_MAX_NAME_LENGTH];
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
vfs_inode_get_name( XPTR( local_cxy , inode ) , name );
if( DEBUG_FATFS_SYNC_INODE < cycle )
printk("\n[%s] thread[%x,%x] enter for <%s> / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, name, cycle );
#endif

    error_t    error;
    mapper_t * mapper;
    page_t   * page;
    uint32_t   page_id;

    // get mapper from inode 
    mapper = inode->mapper;

    // compute max number of pages in mapper from file size
    uint32_t size  = inode->size;
    uint32_t pages = size >> CONFIG_PPM_PAGE_SHIFT;
    if( size & CONFIG_PPM_PAGE_MASK ) pages++; 
         
    // get pointer on mapper radix tree
    grdxt_t * rt = &mapper->rt;

    // scan all pages
    for( page_id = 0 ; page_id < pages ; page_id++ )
    {
        // get page descriptor from mapper
        page = grdxt_lookup( rt , page_id );

        // check all existing pages
        if ( page != NULL )
        {
            if ( page->flags & PG_DIRTY )
            {

#if (DEBUG_FATFS_SYNC_INODE & 1)
if( DEBUG_FATFS_SYNC_INODE < cycle )
printk("\n[%s] thread[%x,%x] synchronizes page %d from <%s> mapper to IOC device\n",
__FUNCTION__, page_id, name );
#endif
                // build extended pointer on page descriptor
                xptr_t page_xp = XPTR( local_cxy , page );

                // move page from mapper to device
                error = fatfs_move_page( page_xp , IOC_WRITE );

                if ( error )  return -1;

                // reset page dirty flag
                ppm_page_undo_dirty( page_xp );
            }
        }
    }  // end loop on pages

#if DEBUG_FATFS_SYNC_INODE
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_SYNC_INODE < cycle )
printk("\n[%s] thread[%x,%x] exit for <%s> / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, name, cycle );
#endif

    return 0;

}  // end fatfs_sync_inode()

//////////////////////////////
error_t fatfs_sync_fat( void )
{

#if DEBUG_FATFS_SYNC_FAT
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
if( DEBUG_FATFS_SYNC_FAT < cycle )
printk("\n[%s] thread[%x,%x] enter / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, cycle );
#endif

    uint32_t   page_id;
    error_t    error;

    // get FAT mapper pointers an cluster 
    fatfs_ctx_t * fatfs_ctx  = fs_context[FS_TYPE_FATFS].extend;
    xptr_t        mapper_xp  = fatfs_ctx->fat_mapper_xp;
    cxy_t         mapper_cxy = GET_CXY( mapper_xp );
    mapper_t    * mapper_ptr = GET_PTR( mapper_xp );

    // compute max number of 4 Kbytes pages in FAT mapper 
    // TODO : this could be improved (see fatfs.h) [AG]
    uint32_t   pages = fatfs_ctx->fat_sectors_count >> 3;
         
    // get pointers on remote FAT mapper radix tree
    grdxt_t  * rt_ptr = &mapper_ptr->rt;
    xptr_t     rt_xp  = XPTR( mapper_cxy , rt_ptr );

    // scan all pages
    for( page_id = 0 ; page_id < pages ; page_id++ )
    {
        // get extended pointer on page descriptor from FAT mapper
        xptr_t page_xp = grdxt_remote_lookup( rt_xp , page_id );

        // check all existing pages
        if ( page_xp != XPTR_NULL )
        {
            page_t * page_ptr = GET_PTR( page_xp );
            uint32_t flags    = hal_remote_l32( XPTR( mapper_cxy , &page_ptr->flags ) );

            if ( flags & PG_DIRTY )
            {

#if (DEBUG_FATFS_SYNC_FAT & 1)
if( DEBUG_FATFS_SYNC_FAT < cycle )
printk("\n[%s] thread[%x,%x] synchronizes page %d from FAT mapper to IOC device\n",
__FUNCTION__, page_id );
#endif
                // move page from mapper to device
                error = fatfs_move_page( page_xp , IOC_SYNC_WRITE );

                if ( error )  return -1;

                // reset page dirty flag
                ppm_page_undo_dirty( page_xp );
            }
        }
    }  // end loop on pages

#if DEBUG_FATFS_SYNC_FAT
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_SYNC_FAT < cycle )
printk("\n[%s] thread[%x,%x] exit / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, cycle );
#endif

    return 0;

}  // end fatfs_sync_fat()

////////////////////////////////////
error_t fatfs_sync_free_info( void )
{

#if DEBUG_FATFS_SYNC_FSINFO
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
if( DEBUG_FATFS_SYNC_FSINFO < cycle )
printk("\n[%s] thread[%x,%x] enter / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, cycle );
#endif

    uint8_t     * buffer;   // dynamically allocated aligned 512 bytes buffer
    kmem_req_t    req;
    error_t       error;

    // get FS_INFO lba, free_ from FATFS context
    fatfs_ctx_t * fatfs_ctx  = fs_context[FS_TYPE_FATFS].extend;
    uint32_t      lba        = fatfs_ctx->fs_info_lba;
    uint32_t      hint       = fatfs_ctx->free_cluster_hint;
    uint32_t      number     = fatfs_ctx->free_clusters;

    // allocate buffer to store the FS_INFO sector
	req.type    = KMEM_512_BYTES;
    req.flags   = AF_KERNEL | AF_ZERO;
	buffer      = (uint8_t *)kmem_alloc( &req );
    if( buffer == NULL ) 
    {
        printk("\n[PANIC] in %s : cannot allocate buffer\n", __FUNCTION__ );
        return ENOMEM;
    }
     
    // load the FS_INFO sector from device to buffer
    error = dev_ioc_read( buffer , lba , 1 );
    if ( error )
    {
        printk("\n[PANIC] in %s : cannot read FS_INFO record\n", __FUNCTION__ );
        return EIO;
    }

    // update buffer 
    fatfs_set_record( FS_FREE_CLUSTERS     , buffer , 1 , number );
    fatfs_set_record( FS_FREE_CLUSTER_HINT , buffer , 1 , hint );

    // write modified FS_INFO sector from buffer to device
    error = dev_ioc_write( buffer , lba , 1 );
    if ( error )
    {
        printk("\n[PANIC] in %s : cannot write FS_INFO record\n", __FUNCTION__ );
        return EIO;
    }

    // release the 512 bytes buffer
    req.type = KMEM_512_BYTES;
    req.ptr  = buffer;
    kmem_free( &req );

#if DEBUG_FATFS_SYNC_FSINFO
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_SYNC_FSINFO < cycle )
printk("\n[%s] thread[%x,%x] exit / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, cycle );
#endif

    return 0;

}  // end fatfs_sync_fs_info()

//////////////////////////////////////////////////////////
error_t fatfs_cluster_alloc( uint32_t * searched_cluster )
{
    uint32_t      page_id;        // page index in mapper
    uint32_t      slot_id;        // slot index in page (1024 slots per page)
    uint32_t      hint;           // first free cluster index in FAT
    uint32_t      free_clusters;  // total number of free clusters
    vfs_ctx_t   * vfs_ctx;        // local pointer on VFS context (same in all clusters)
    fatfs_ctx_t * loc_fatfs_ctx;  // local pointer on local FATFS context
    fatfs_ctx_t * fat_fatfs_ctx;  // local pointer on FATFS context in FAT cluster
    xptr_t        mapper_xp;      // extended pointer on FAT mapper
    cxy_t         mapper_cxy;     // Fat mapper cluster identifier
    xptr_t        page_xp;        // extended pointer on current page descriptor in mapper
    xptr_t        slot_xp;        // extended pointer on FAT slot defined by hint
    xptr_t        lock_xp;        // extended pointer on lock protecting free clusters info
    xptr_t        hint_xp;        // extended pointer on free_cluster_hint in FAT cluster
    xptr_t        numb_xp;        // extended pointer on free_clusters_number in FAT cluster

#if DEBUG_FATFS_CLUSTER_ALLOC
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
if( DEBUG_FATFS_CLUSTER_ALLOC < cycle )
printk("\n[%s] thread[%x,%x] enter / cycle = %d\n",
__FUNCTION__, this->process->pid, this->trdid, cycle );
#endif

    // get local pointer on VFS context (same in all clusters)
    vfs_ctx = &fs_context[FS_TYPE_FATFS];

    // get local pointer on local FATFS context
    loc_fatfs_ctx = vfs_ctx->extend;

    // get extended pointer and cluster on FAT mapper
    mapper_xp  = loc_fatfs_ctx->fat_mapper_xp;
    mapper_cxy = GET_CXY( mapper_xp );
    
    // get local pointer on FATFS context in FAT cluster 
    fat_fatfs_ctx = hal_remote_lpt( XPTR( mapper_cxy , &vfs_ctx->extend ) );

    // build relevant extended pointers in on free clusters info in FAT cluster 
    lock_xp = XPTR( mapper_cxy , &fat_fatfs_ctx->free_lock );
    hint_xp = XPTR( mapper_cxy , &fat_fatfs_ctx->free_cluster_hint );
    numb_xp = XPTR( mapper_cxy , &fat_fatfs_ctx->free_clusters );

    // take the lock protecting free clusters
    remote_queuelock_acquire( lock_xp );

    // get hint and free_clusters values from FATFS context 
    hint          = hal_remote_l32( hint_xp );
    free_clusters = hal_remote_l32( numb_xp );
        
    // get page index & slot index for the first free cluster 
    page_id  = (hint + 1) >> 10;
    slot_id  = (hint + 1) & 0x3FF;

    // get relevant page from mapper
    page_xp = mapper_remote_get_page( mapper_xp , page_id );

    if( page_xp == XPTR_NULL )
    {
        printk("\n[ERROR] in %s : cannot acces FAT mapper\n", __FUNCTION__ );
        return -1;
    }

    // build extended pointer on free cluster slot 
    slot_xp = ppm_page2base( page_xp ) + (slot_id<<2);
         
#if (DEBUG_FATFS_CLUSTER_ALLOC & 1)
if( DEBUG_FATFS_CLUSTER_ALLOC < cycle )
printk("\n[%s] thread[%x,%x] get free info / hint %x / free_clusters %x\n",
__FUNCTION__, this->process->pid, this->trdid, hint, free_clusters );
#endif

    // check "free_clusters"
    if ( free_clusters == 0 )
    {
        printk("\n[ERROR] in %s : no more free FATFS clusters\n", __FUNCTION__ );
        remote_queuelock_acquire( lock_xp );
        return -1;
    }
    else if ( free_clusters < CONFIG_VFS_FREE_CLUSTERS_MIN )
    {
        printk("\n[WARNING] in %s : only %n free FATFS clusters\n", 
        __FUNCTION__, CONFIG_VFS_FREE_CLUSTERS_MIN );
    }

    // check "hint" 
    if( hal_remote_l32( slot_xp ) != FREE_CLUSTER )
    { 
        printk("\n[ERROR] in %s : illegal hint cluster\n", __FUNCTION__ );
        remote_queuelock_acquire( lock_xp );
        return -1;
    }

    // update allocated cluster in FAT mapper
    hal_remote_s32( slot_xp , END_OF_CHAIN_CLUSTER_MAX );

    // update free cluster info
    fatfs_free_clusters_decrement( hint + 1 );

    // release free clusters busylock
    remote_queuelock_release( lock_xp );

#if DEBUG_FATFS_CLUSTER_ALLOC 
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_CLUSTER_ALLOC < cycle )
printk("\n[%s] thread[%x,%x] exit / cluster %x / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, hint + 1, cycle );
#endif

    *searched_cluster = hint + 1;
    return 0;

}  // end fat_cluster_alloc()

//////////////////////////////////////////////
error_t fatfs_release_inode( xptr_t inode_xp )
{
    vfs_ctx_t   * vfs_ctx;        // local pointer on VFS context (same in all clusters).
    fatfs_ctx_t * loc_fatfs_ctx;  // local pointer on local FATFS context
    fatfs_ctx_t * fat_fatfs_ctx;  // local pointer on FATFS context in FAT cluster
    xptr_t        mapper_xp;      // extended pointer on FAT mapper
    cxy_t         mapper_cxy;     // Fat mapper cluster identifier
    xptr_t        lock_xp;        // extended pointer on lock protecting free clusters info.
    xptr_t        first_xp;       // extended pointer on inode extension
    uint32_t      first_cluster;  // first cluster index for released inode
    vfs_inode_t * inode_ptr;
    cxy_t         inode_cxy;

// check inode pointer 
assert( (inode_xp != XPTR_NULL) , "inode pointer is NULL\n" );

    // get first_cluster from inode extension
    inode_ptr     = GET_PTR( inode_xp );
    inode_cxy     = GET_CXY( inode_xp );
    first_xp      = XPTR( inode_cxy , &inode_ptr->extend );
    first_cluster = (uint32_t)(intptr_t)hal_remote_lpt( first_xp );

// check first cluster index
assert( (first_cluster != 0) , "inode extend is NULL\n" );

#if DEBUG_FATFS_RELEASE_INODE
char       name[CONFIG_VFS_MAX_NAME_LENGTH];
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
vfs_inode_get_name( inode_xp , name );
if( DEBUG_FATFS_RELEASE_INODE < cycle )
printk("\n[%s] thread[%x,%x] enter for <%s> / first_cluster %x / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, name, first_cluster, cycle );
#endif

    // get local pointer on VFS context (same in all clusters)
    vfs_ctx = &fs_context[FS_TYPE_FATFS];

    // get local pointer on local FATFS context
    loc_fatfs_ctx = vfs_ctx->extend;

    // get extended pointer and cluster on FAT mapper
    mapper_xp  = loc_fatfs_ctx->fat_mapper_xp;
    mapper_cxy = GET_CXY( mapper_xp );
    
    // get local pointer on FATFS context in FAT cluster 
    fat_fatfs_ctx = hal_remote_lpt( XPTR( mapper_cxy , &vfs_ctx->extend ) );

    // get extended pointer on free clusters lock in FAT cluster
    lock_xp = XPTR( mapper_cxy , &fat_fatfs_ctx->free_lock );

    // take lock protecting free clusters
    remote_queuelock_acquire( lock_xp );

    // call the recursive function to release all clusters from FAT mapper
    if ( fatfs_recursive_release( mapper_xp , first_cluster ) )
    {
        printk("\n[ERROR] in %s : cannot update FAT mapper\n", __FUNCTION__ );
        remote_queuelock_release( lock_xp );
        return -1;
    }

    // release lock protecting free cluster
    remote_queuelock_release( lock_xp );

#if (DEBUG_FATFS_RELEASE_INODE & 1)
if( DEBUG_FATFS_RELEASE_INODE < cycle )
printk("\n[%s] inode <%s> removed from FAT mapper\n", __FUNCTION__, name );
#endif

    // update FAT on IOC device (from FAT mapper)
    if ( fatfs_sync_fat() )
    {
        printk("\n[ERROR] in %s : cannot update FAT on device\n", __FUNCTION__ );
        return -1;
    }

#if (DEBUG_FATFS_RELEASE_INODE & 1)
if( DEBUG_FATFS_RELEASE_INODE < cycle )
printk("\n[%s] inode <%s> removed from FAT on IOC device\n", __FUNCTION__, name );
#endif

    // update FS-INFO sector on IOC device (from FATFS context)
    if ( fatfs_sync_free_info() )
    {
        printk("\n[ERROR] in %s: cannot update FS_INFO on device\n", __FUNCTION__ );
        return -1;
    }

#if DEBUG_FATFS_RELEASE_INODE
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_RELEASE_INODE < cycle )
printk("\n[%s] thread[%x,%x] removed <%s> inode from FATFS / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, name, cycle );
#endif

    return 0;

}  // end fatfs_release_inode()

////////////////////////////////////////////
error_t fatfs_move_page( xptr_t     page_xp,
                         cmd_type_t cmd_type )
{
    error_t       error;
    vfs_inode_t * inode_ptr;
    mapper_t    * mapper_ptr;      
    uint32_t      page_id;     // page index in mapper

#if DEBUG_FATFS_MOVE_PAGE
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
char       name[CONFIG_VFS_MAX_NAME_LENGTH];
#endif

    // get page cluster an local pointer
    cxy_t    page_cxy = GET_CXY( page_xp );
    page_t * page_ptr = GET_PTR( page_xp );

    // get mapper pointer and page index from page descriptor
    mapper_ptr = hal_remote_lpt( XPTR( page_cxy , &page_ptr->mapper ) );
    page_id    = hal_remote_l32( XPTR( page_cxy , &page_ptr->index ) );

    // get pointer on local FATFS context
    fatfs_ctx_t * fatfs_ctx = fs_context[FS_TYPE_FATFS].extend;

    // get page base address
    xptr_t    base_xp = ppm_page2base( page_xp );
    uint8_t * buffer  = (uint8_t *)GET_PTR( base_xp );
 
    // get inode pointer from mapper
    inode_ptr  = hal_remote_lpt( XPTR( page_cxy , &mapper_ptr->inode ) );

    ////////////////////////////// it is the FAT mapper
    if( inode_ptr == NULL )
    {
        // get lba from FATFS context and page_id 
        uint32_t      lba        = fatfs_ctx->fat_begin_lba + (page_id << 3);
 
        // access device
        if     ( cmd_type == IOC_SYNC_READ  ) error = dev_ioc_sync_read ( buffer , lba , 8 );
        else if( cmd_type == IOC_SYNC_WRITE ) error = dev_ioc_sync_write( buffer , lba , 8 );
        else if( cmd_type == IOC_READ       ) error = dev_ioc_read      ( buffer , lba , 8 );
        else if( cmd_type == IOC_WRITE      ) error = dev_ioc_write     ( buffer , lba , 8 );
        else                                  error = -1;

        if( error ) return EIO;

#if (DEBUG_FATFS_MOVE_PAGE & 0x1)
if( DEBUG_FATFS_MOVE_PAGE < cycle )
mapper_display_page( XPTR(page_cxy , mapper_ptr) , page_id );
#endif

#if DEBUG_FATFS_MOVE_PAGE
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_MOVE_PAGE < cycle )
{
    if ( (cmd_type == IOC_READ) || (cmd_type == IOC_SYNC_READ) )
         printk("\n[%s] thread[%x,%x] load page %d of FAT / cycle %d\n",
         __FUNCTION__, this->process->pid, this->trdid, page_id, cycle );
    else
        printk("\n[%s] thread[%x,%x] sync page %d of FAT / cycle %d\n",
        __FUNCTION__, this->process->pid, this->trdid, page_id, cycle );
}
#endif

    }
    ///////////////////////// it is an inode mapper
    else                       
    {

#if DEBUG_FATFS_MOVE_PAGE
vfs_inode_get_name( XPTR( page_cxy , inode_ptr ) , name );
#endif

        uint32_t  searched_cluster;
        uint32_t  first_cluster;

        // get first_cluster from inode extension
        void * extend = hal_remote_lpt( XPTR( page_cxy , &inode_ptr->extend ) );
        first_cluster = (uint32_t)(intptr_t)extend;

        // compute searched_cluster
        if( page_id == 0 )            // no need to access FAT mapper
        {
            // searched cluster is first cluster
            searched_cluster = first_cluster;
        }
        else                        // FAT mapper access required
        {
            // access FAT mapper to get searched cluster
            error = fatfs_get_cluster( first_cluster,
                                       page_id,
                                       &searched_cluster );
            if( error )  return EIO;
        }

        // get lba from searched_cluster
        uint32_t lba = fatfs_lba_from_cluster( fatfs_ctx , searched_cluster );

        // access device
        if     ( cmd_type == IOC_SYNC_READ  ) error = dev_ioc_sync_read ( buffer , lba , 8 );
        else if( cmd_type == IOC_SYNC_WRITE ) error = dev_ioc_sync_write( buffer , lba , 8 );
        else if( cmd_type == IOC_READ       ) error = dev_ioc_read      ( buffer , lba , 8 );
        else if( cmd_type == IOC_WRITE      ) error = dev_ioc_write     ( buffer , lba , 8 );
        else                                  error = -1;

        if( error ) return EIO;

#if (DEBUG_FATFS_MOVE_PAGE & 0x1)
if( DEBUG_FATFS_MOVE_PAGE < cycle )
mapper_display_page( XPTR(page_cxy , mapper_ptr) , page_id );
#endif

#if DEBUG_FATFS_MOVE_PAGE
cycle = (uint32_t)hal_get_cycles();
if(DEBUG_FATFS_MOVE_PAGE < cycle)
{
    if ( (cmd_type == IOC_READ) || (cmd_type == IOC_SYNC_READ) )
        printk("\n[%s] thread[%x,%x] load page %d of <%s> inode / cycle %d\n",
        __FUNCTION__, this->process->pid, this->trdid, page_id, name, cycle );
    else
        printk("\n[%s] thread[%x,%x] sync page %d of <%s> inode / cycle %d\n",
        __FUNCTION__, this->process->pid, this->trdid, page_id, name, cycle );
}
#endif

    }

    return 0;

}  // end fatfs_move_page()


