/*
 * fatfs.c - FATFS file system API implementation.
 *
 * Author    Alain Greiner (2016,2017,2018,2019,2020)
 *
 * 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>

#define LITTLE_ENDIAN  1

//////////////////////////////////////////////////////////////////////////////////////////
//          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 local
// array of bytes, taking into account the global LITTLE_ENDIAN parameter:
// if LITTLE_ENDIAN is true, the most significant byte has the highest address.
//////////////////////////////////////////////////////////////////////////////////////////
// @ offset        : first byte in array.
// @ nbytes        : record length in bytes (1/2/4).
// @ buffer        : local pointer on byte array.
// @ return the integer value in a 32 bits word.
//////////////////////////////////////////////////////////////////////////////////////////
static uint32_t fatfs_get_record( uint32_t    offset,
                                  uint32_t    nbytes,
                                  uint8_t   * buffer )
{
    uint32_t i;
    uint32_t res = 0;

    if ( LITTLE_ENDIAN )
    {
        for( i = nbytes ; i > 0 ; i-- ) res = (res<<8) | buffer[offset+i-1];
    }
    else 
    {
        for( i = 0 ; i < nbytes ; i++ ) res = (res<<8) | buffer[offset+i];
    }
    return res;

}  // end fatfs_get_record()

//////////////////////////////////////////////////////////////////////////////////////////
// This function return an integer record value (one, two, or four bytes) from a remote
// array of bytes, taking into account the global LITTLE_ENDIAN parameter:
// if LITTLE_ENDIAN is true, the most significant byte has the highest address.
//////////////////////////////////////////////////////////////////////////////////////////
// @ offset        : first byte in array.
// @ nbytes        : record length in bytes (1/2/4).
// @ buffer_xp     : extended pointer on byte array.
// @ return the integer value in a 32 bits word.
//////////////////////////////////////////////////////////////////////////////////////////
static uint32_t fatfs_get_remote_record( uint32_t   offset,
                                         uint32_t   nbytes,
                                         xptr_t     buffer_xp )
{
    uint32_t i;
    uint32_t res = 0;

    if ( LITTLE_ENDIAN )
    {
        for( i = nbytes ; i > 0 ; i-- )
        {
            res = (res<<8) | hal_remote_lb( buffer_xp + offset + i-1 );
        }
    }
    else 
    {
        for( i = 0 ; i < nbytes ; i++ )
        {
            res = (res<<8) | hal_remote_lb( buffer_xp + offset + i );
        }
    }
    return res;

}  // end fatfs_get_remote_record()

/*

//////////////////////////////////////////////////////////////////////////////////////////
// This function writes one, two, or four bytes from a 32 bits integer to a local
// array of bytes, taking into account the global LITTLE_ENDIAN parameter:
// if LITTLE_ENDIAN is true, the most significant byte has the highest address.
//////////////////////////////////////////////////////////////////////////////////////////
// @ offset        : first byte in array.
// @ nbytes        : record length in bytes (1/2/4).
// @ buffer        : local pointer on byte array.
// @ value         : 32 bits integer value.
//////////////////////////////////////////////////////////////////////////////////////////
static void fatfs_set_record( uint32_t    offset,
                              uint32_t    nbytes,
                              uint8_t   * buffer,
                              uint32_t    value )
{
    uint32_t i;

    if ( LITTLE_ENDIAN )
    {
        for( i = nbytes ; i > 0 ; i-- ) buffer[offset+i-1] = (uint8_t)(value>>((i-1)<<3));
    }
    else
    {
        for( i = 0 ; i < nbytes ; i++ ) buffer[offset+i] = (uint8_t)(value>>((nbytes-1-i)<<3));
    }

}  // end fatfs_set_record()

*/

//////////////////////////////////////////////////////////////////////////////////////////
// This function writes one, two, or four bytes from a 32 bits integer to a remote
// array of bytes, taking into account the global LITTLE_ENDIAN parameter:
// if LITTLE_ENDIAN is true, the most significant byte has the highest address.
//////////////////////////////////////////////////////////////////////////////////////////
// @ offset        : first byte in array.
// @ nbytes        : record length in bytes (1/2/4).
// @ buffer_xp     : extended pointer on byte array.
// @ value         : 32 bits integer value.
//////////////////////////////////////////////////////////////////////////////////////////
static void fatfs_set_remote_record( uint32_t    offset,
                                     uint32_t    nbytes,
                                     xptr_t      buffer_xp,
                                     uint32_t    value )
{
    uint32_t i;

    if ( LITTLE_ENDIAN )
    {
        for( i = nbytes ; i > 0 ; i-- )
        {
            hal_remote_sb( (buffer_xp + offset + i-1 ) , (uint8_t)(value>>((i-1)<<3)) );
        }
    }
    else
    {
        for( i = 0 ; i < nbytes ; i++ )
        {
            hal_remote_sb( (buffer_xp + offset + i) , (uint8_t)(value>>((nbytes-1-i)<<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 synchronously updates the FAT on IOC device.
// It scan the FAT mapper to copy on IOC device all dirty pages in the interval
// defined by the <page_min> & <page_max> arguments.
// It can be called by a thread running in any cluster.
// WARNING : We don't take the lock protecting the FAT mapper, because the FAT lock
// (in FATFS context) must be taken by the calling function.
//////////////////////////////////////////////////////////////////////////////////////////
// @ fatfs_ctx_xp  : extended pointer on FATFS context in FAT cluster.
// @ page_min      : first page to be checked.
// @ page_max      : last page to be checked
// @ return 0 if success, return -1 if the FS_INFO sector cannot be updated.
//////////////////////////////////////////////////////////////////////////////////////////
static error_t fatfs_update_ioc_fat( xptr_t   fatfs_ctx_xp,
                                     uint32_t page_min,
                                     uint32_t page_max )
{

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

    error_t       error;
    cxy_t         fat_cxy;         // FAT cluster identifier
    fatfs_ctx_t * fatfs_ctx;       // local pointer on FATFS context in FAT cluster
    mapper_t    * fat_mapper_ptr;  // local pointer on FAT mapper
    uint32_t      page_id;         // current page index in FAT mapper
    xptr_t        rt_xp;           // extended pointer on FAT mapper radix tree
    xptr_t        page_xp;         // extended pointer on current page in FAT mapper
    page_t      * page_ptr;        // local pointer on current page
    uint32_t      flags;           // current page flags

    // get pointer and cluster on FATFS context in FAT cluster
    fat_cxy   = GET_CXY( fatfs_ctx_xp );
    fatfs_ctx = GET_PTR( fatfs_ctx_xp );
 
    // get FAT mapper pointer from FATFS context 
    fat_mapper_ptr  = hal_remote_lpt( XPTR( fat_cxy , &fatfs_ctx->fat_mapper ) );

    // build extended pointer on FAT mapper radix tree
    rt_xp   = XPTR( fat_cxy , &fat_mapper_ptr->rt );

    // scan all pages in [min,max] interval
    for( page_id = page_min ; page_id <= page_max ; page_id++ )
    {
        // get extended pointer on page descriptor from FAT mapper
        page_xp = grdxt_remote_lookup( rt_xp , page_id );

        // check only existing pages
        if ( page_xp != XPTR_NULL )
        {
            page_ptr = GET_PTR( page_xp );
            flags    = hal_remote_l32( XPTR( fat_cxy , &page_ptr->flags ) );

            // copy only dirty pages
            if ( flags & PG_DIRTY )
            {

#if (DEBUG_FATFS_UPDATE_IOC & 1)
if( DEBUG_FATFS_UPDATE_IOC < cycle )
printk("\n[%s] thread[%x,%x] copy 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_UPDATE_IOC
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_UPDATE_IOC < cycle )
printk("\n[%s] thread[%x,%x] exit / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, cycle );
#endif

    return 0;

}  // end fatfs_update_ioc_fat()

//////////////////////////////////////////////////////////////////////////////////////////
// This static function synchronously updates the FS_INFO sector on IOC device,
// from values contained in the FATFS context in FAT cluster.
// It uses and updates the FS_INFO buffer allocated in the FAT cluster.
// It can be called by a thread running in any cluster.
//////////////////////////////////////////////////////////////////////////////////////////
// @ fatfs_ctx_xp  : extended pointer on FATFS context in FAT cluster.
// @ return 0 if success, return -1 if the FS_INFO sector cannot be updated.
//////////////////////////////////////////////////////////////////////////////////////////
static error_t fatfs_update_ioc_fsinfo( xptr_t fatfs_ctx_xp )
{
    cxy_t         fat_cxy;             // FAT cluster identifier
    fatfs_ctx_t * fatfs_ctx_ptr;       // local pointer on fatfs context in FAT cluster
    uint32_t      free_clusters;       // current vale of "free_clusters" in fatfs context
    uint32_t      free_cluster_hint;   // current vale of "free_cluster_hint" in fatfs context
    uint8_t     * fs_info_buffer_ptr;  // local pointer on FS_INFO buffer in FAT cluster
    xptr_t        fs_info_buffer_xp;   // extended pointer on FS_INFO buffer in FAT cluster
    uint32_t      fs_info_lba;         // FS_INFO sector lba on IOC device

    // get cluster and local pointer on FAT cluster context
    fat_cxy       = GET_CXY( fatfs_ctx_xp ); 
    fatfs_ctx_ptr = GET_PTR( fatfs_ctx_xp ); 

    // force FATFS context update
    hal_fence();

    // get relevant info from fatfs context in FAT cluster
    fs_info_lba        = hal_remote_l32( XPTR( fat_cxy , &fatfs_ctx_ptr->fs_info_lba ) );
    free_clusters      = hal_remote_l32( XPTR( fat_cxy , &fatfs_ctx_ptr->free_clusters ) );
    free_cluster_hint  = hal_remote_l32( XPTR( fat_cxy , &fatfs_ctx_ptr->free_cluster_hint ) );
    fs_info_buffer_ptr = hal_remote_lpt( XPTR( fat_cxy , &fatfs_ctx_ptr->fs_info_buffer ) );

    // build extended pointer on FS_INFO buffer in FAT cluster
    fs_info_buffer_xp  = XPTR( fat_cxy , fs_info_buffer_ptr );
    
    // update the FS_INFO buffer in FAT cluster
    fatfs_set_remote_record( FS_FREE_CLUSTERS     , fs_info_buffer_xp , free_clusters );
    fatfs_set_remote_record( FS_FREE_CLUSTER_HINT , fs_info_buffer_xp , free_cluster_hint );
    
    // update the FS_INFO sector on IOC device
    return dev_ioc_sync_write( fs_info_buffer_xp , fs_info_lba , 1 );
 
}  // end fatfs_update_ioc_fsinfo()

//////////////////////////////////////////////////////////////////////////////////////////
// This static function decrements the  "free_clusters" variable, and updates the 
// "free_cluster_hint" variable in the FATFS context in FAT cluster,  when a new 
// <cluster_id> has been allocated from the FAT.
// It synchronously updates the FS_INFO sector on the IOC device. 
// The FATFS context in FAT cluster is identified by the <fat_ctx_xp> argument.
// It can be called by a thead running in any cluster. 
// It scan all slots in the FAT mapper seen as an array of 32 bits words, looking for the
// first free slot larger than the <cluster_id>.
// The lock protecting exclusive access to the FAT must be taken by the calling function. 
//////////////////////////////////////////////////////////////////////////////////////////
// @ fatfs_ctx_xp  : extended pointer on FATFS context in FAT cluster. 
// @ cluster_id    : recently allocated cluster index in FAT.
// @ return 0 if success, return -1 if the FS_INFO sector cannot be updated.
//////////////////////////////////////////////////////////////////////////////////////////
static error_t fatfs_free_clusters_decrement( xptr_t    fatfs_ctx_xp,
                                              uint32_t  cluster_id )
{
    error_t       error;
    cxy_t         fat_cxy;         // FAT cluster identifier
    fatfs_ctx_t * fat_ctx_ptr;     // local pointer on fatfs context in FAT cluster
    mapper_t    * fat_mapper_ptr;  // local pointer on FAT mapper
    xptr_t        fat_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      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        base_xp;         // extended pointer on current page base
    xptr_t        slot_xp;         // extended pointer on current slot in FAT mapper
    uint32_t      found;           // free slot found when non zero

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

    // get FAT cluster an local pointer on fatfs context in FAT cluster
    fat_cxy      = GET_CXY( fatfs_ctx_xp );
    fat_ctx_ptr  = GET_PTR( fatfs_ctx_xp );
    
    // build extended pointers on free_clusters, and free_cluster_hint in fatfs context
    hint_xp = XPTR( fat_cxy , &fat_ctx_ptr->free_cluster_hint );
    numb_xp = XPTR( fat_cxy , &fat_ctx_ptr->free_clusters );

    // get pointers on FAT mapper from FATFS context
    fat_mapper_ptr = hal_remote_lpt( XPTR( fat_cxy , &fat_ctx_ptr->fat_mapper ) );
    fat_mapper_xp  = XPTR( fat_cxy , fat_mapper_ptr );

    // initialise the loop variables to scan the FAT mapper
    page_id  = (cluster_id + 1) >> 10;
    slot_id  = (cluster_id + 1) & 0x3FF;
    page_max = hal_remote_l32( XPTR( fat_cxy, &fat_ctx_ptr->fat_sectors_count ) ) >> 3;
    found    = 0;

    // scan FAT mapper : first loop on pages
    while ( (page_id < page_max) && (found == 0) )            
    {
        // get current page from mapper
        page_xp = mapper_get_fat_page( fat_mapper_xp , page_id );

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

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

        // scan the FAT mapper : second loop on slots
        while ( (slot_id < 1024) && (found == 0) )
        {
            // get extended pointer on current slot
            slot_xp = base_xp + (slot_id << 2);

            // test slot value 
            if ( hal_remote_l32( slot_xp ) == FREE_CLUSTER )
            {
                // exit both loops
                found = 1;
            }
            else
            {
                // update slot_id if not found
                slot_id++;
            }
        }  // end loop on slots

        // update page_id & slot_id variables if not found
        if( found == 0 )
        {
            page_id++;
            slot_id = 0;
        }
    }  // end loop on pages

    if( found )  // free cluster found
    {
        // update "free_clusters" and "free_cluster_hint" value in FATFS context
        hal_remote_atomic_add( numb_xp , -1 );
        hal_remote_s32( hint_xp , (page_id << 10) + slot_id - 1 );

        // update FS_INFO sector on IOC device
        error = fatfs_update_ioc_fsinfo( fatfs_ctx_xp );

        if( error ) 
        {
            printk("\n[ERROR] in %s : cannot update FS_INFO on IOC\n", __FUNCTION__ );
            return -1;
        }

#if DEBUG_FATFS_FREE_CLUSTERS 
if( DEBUG_FATFS_FREE_CLUSTERS < cycle )
printk("\n[%s] thread[%x,%x] exit / hint %x / free %x\n",
__FUNCTION__, this->process->pid, this->trdid, 
hal_remote_l32(hint_xp), hal_remote_l32(numb_xp) );
#endif
        return 0;
    }
    else  // free cluster not found
    {
        printk("\n[ERROR] in %s : No free cluster_id found\n", __FUNCTION__ );
        return -1;
    }
    
}  // end fatfs_free_clusters_decrement()

//////////////////////////////////////////////////////////////////////////////////////////
// This static function increments the "free_clusters" variable, and updates the
// "free_cluster_hint" variables in the FATFS context in FAT cluster, identified 
// by the <fat_ctx_xp> argument, when a FATFS <cluster_id> has been released.
// If the released cluster index is smaller than the current (hint) value, 
// it set "free_cluster_hint" <= cluster.
// It does NOT update the  FS_INFO sector on the IOC device, as this is done by the
// calling fatfs_release_inode() function.
// It can be called by a thead running in any cluster. 
// The lock protecting exclusive access to the FAT must be taken by the calling function. 
//////////////////////////////////////////////////////////////////////////////////////////
// @ fatfs_ctx_xp  : extended pointer on FATFS context in FAT cluster. 
// @ cluster_id    : recently released cluster index in FAT.
// @ return 0 if success, return -1 if the FS_INFO sector cannot be updated.
//////////////////////////////////////////////////////////////////////////////////////////
static error_t fatfs_free_clusters_increment( xptr_t   fatfs_ctx_xp,
                                              uint32_t cluster_id )
{
    cxy_t         fat_cxy;      // FAT cluster identifier
    fatfs_ctx_t * fat_ctx_ptr;  // local pointer on fatfs context in FAT cluster
    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

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

    // get FAT cluster an local pointer on fatfs context in FAT cluster
    fat_cxy      = GET_CXY( fatfs_ctx_xp );
    fat_ctx_ptr  = GET_PTR( fatfs_ctx_xp );
    
    // build extended pointers on free_clusters, and free_cluster_hint
    hint_xp = XPTR( fat_cxy , &fat_ctx_ptr->free_cluster_hint );
    numb_xp = XPTR( fat_cxy , &fat_ctx_ptr->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 "numb" and "hint" variables as required
    numb++;
    if ( (cluster_id - 1) < hint ) hint = cluster_id - 1;

    // update free_clusters
    hal_remote_s32( numb_xp , numb );
    hal_remote_s32( hint_xp , hint );

#if DEBUG_FATFS_FREE_CLUSTERS 
if( DEBUG_FATFS_FREE_CLUSTERS < cycle )
printk("\n[%s] thread[%x,%x] exit / hint %x / free %x\n",
__FUNCTION__, this->process->pid, this->trdid,
hal_remote_l32( hint_xp ), hal_remote_l32( numb_xp ) );
#endif

    return 0;

}  // end fatfs_free_clusters_increment()

//////////////////////////////////////////////////////////////////////////////////////////
// This recursive function is called by the generic function fatfs_release_inode().
// It release all FATFS clusters allocated to a given inode to the FAT mapper.
// It can be called by a thread running in any cluster, as it use remote pointers
// to access both the FAT mapper and the FATFS context in the FAT cluster, defined
// by the <fat_mapper_xp> and <fatfs_ctx_xp> arguments.
// The removal is done in reverse order of the linked list (from last cluster to first).
// It returns in the <dirty_page_min> and <dirty_page_max> buffers the indexes of the
// modified pages in the FAT mapper.
// It updates the FAT mapper and the free_cluster info in the FATFS context, but does NOT
// update the FAT and the FS_INFO on IOC device.
//////////////////////////////////////////////////////////////////////////////////////////
// @ fat_mapper_xp  : [in]  extended pointer on FAT mapper.
// @ fatfs_ctx_xp   : [in]  extended pointer on FATFS context in FAT cluster.
// @ cluster        : [in]  index of cluster to be released from FAT mapper.
// @ dirty_page_min : [out] pointer on buffer for min dirty page index.
// @ dirty_page_max : [out] pointer on buffer for max dirty page index.
// @ return 0 if success / return -1 if error.
//////////////////////////////////////////////////////////////////////////////////////////
static error_t fatfs_recursive_release( xptr_t      fat_mapper_xp,
                                        xptr_t      fatfs_ctx_xp,
                                        uint32_t    cluster,
                                        uint32_t  * dirty_page_min,
                                        uint32_t  * dirty_page_max )
{
    uint32_t next;       // next cluster index
    uint32_t page_id;    // page index in FAT mapper
    uint32_t word_id;    // word index in page

    // get page index and word index from cluster
    page_id = cluster >> 10;
    word_id = cluster & 0x3FF;

    // get next cluster index from FAT mapper
    if ( mapper_remote_get_32( fat_mapper_xp,
                               page_id,
                               word_id,
                               &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,
                                      fatfs_ctx_xp,
                                      next,
                                      dirty_page_min,
                                      dirty_page_max ) ) return -1;
    }       

    // update FAT mapper
    if ( mapper_remote_set_32( fat_mapper_xp,
                               page_id, 
                               word_id,
                               FREE_CLUSTER ) ) return -1;

    // update dirty_page_min / dirty_page_max buffers
    if( page_id < *dirty_page_min ) *dirty_page_min = page_id;
    if( page_id > *dirty_page_max ) *dirty_page_max = page_id;

    // Update free_cluster info in FATFS context 
    return fatfs_free_clusters_increment( fatfs_ctx_xp , cluster );

}  // end fatfs_recursive_release()


//////////////////////////////////////////////////////////////////////////////////////////
// This static function access the FAT mapper to allocate a new cluster in the FATFS,
// and returns in <searched_cluster_id> the FATFS cluster index of a free cluster.
// It updates the FAT mapper (handling miss from IOC device if required) :
// - if the <last_cluster_id> is zero, the new cluster is the first allocated cluster,
//   and the <searched_cluster_id> FAT slot is set to END_OF_CHAIN_CLUSTER.
// - if the <last_cluster_id> argument is not zero, the new cluster is not the first,
//   the <last_cluster_id> FAT slot is set to <searched_cluster_id>, 
//   the <searched_cluster_id> FAT slot is set to END_OF_CHAIN_CLUSTER.
// This function also updates the  two "free_cluster_hint" and "free_clusters" variables
// stored in the FATFS context. It takes the rwlock stored in the FATFS context in the
// FAT cluster to get exclusive access to the FAT.
// This function synchronously updates the FAT region on IOC device. 
// It can be called by a thread running in any cluster as it uses remote accesses.
//////////////////////////////////////////////////////////////////////////////////////////
// @ last_cluster_id      : [in]  previous last cluster index. 
// @ searched_cluster_id  : [out] allocated cluster index.
// @ return 0 if success / return -1 if no more free clusters on IOC device.
//////////////////////////////////////////////////////////////////////////////////////////
static error_t fatfs_cluster_alloc( uint32_t   last_cluster_id,
                                    uint32_t * searched_cluster_id )
{
    error_t       error;
    uint32_t      free_clusters;   // total number of free clusters
    uint32_t      hint;            // hint + 1 is the first free cluster
    uint32_t      new_cluster_id;  // allocated cluster index in FAT
    uint32_t      new_page_id;     // allocated cluster page index in FAT mapper
    uint32_t      new_slot_id;     // allocated cluster slot index in page (1024 slots per page)
    xptr_t        new_page_xp;     // extended pointer on FAT page for allocated cluster
    xptr_t        new_slot_xp;     // extended pointer on allocated cluster slot in FAT 
    uint32_t      last_page_id;    // last cluster page index in FAT mapper
    uint32_t      last_slot_id;    // last cluster slot index in page (1024 slots per page)
    xptr_t        last_slot_xp;    // extended pointer on last cluster slot in FAT 
    xptr_t        last_page_xp;    // extended pointer on FAT page for last cluster
    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
    mapper_t    * fat_mapper_ptr;  // local pointer on FAT mapper
    xptr_t        fat_mapper_xp;   // extended pointer on FAT mapper
    cxy_t         fat_cxy;         // FAT mapper cluster identifier
    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        free_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 / lats_cluster_id %x / cycle = %d\n",
__FUNCTION__, this->process->pid, this->trdid, last_cluster_id, 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 FAT cluster
    fat_cxy = CONFIG_VFS_ROOT_CXY;

    // get pointers on FAT mapper
    fat_mapper_ptr = loc_fatfs_ctx->fat_mapper;
    fat_mapper_xp  = XPTR( fat_cxy , fat_mapper_ptr );

    // get local pointer on FATFS context in FAT cluster 
    fat_fatfs_ctx = hal_remote_lpt( XPTR( fat_cxy , &vfs_ctx->extend ) );

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

    // take the FAT lock in write mode
    remote_rwlock_wr_acquire( lock_xp );

    // get hint and free_clusters values from FATFS context in FAT cluster
    hint          = hal_remote_l32( hint_xp );
    free_clusters = hal_remote_l32( free_xp );
        
#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_rwlock_wr_release( lock_xp );
        return -1;
    }
    else if ( free_clusters < CONFIG_VFS_FREE_CLUSTERS_MIN )
    {
        printk("\n[WARNING] in %s : only %d free FATFS clusters\n", 
        __FUNCTION__, CONFIG_VFS_FREE_CLUSTERS_MIN );
    }

    // get new cluster, page & slot indexes in FAT
    new_cluster_id = hint + 1; 
    new_page_id    = new_cluster_id >> 10;
    new_slot_id    = new_cluster_id & 0x3FF;

    // get relevant FAT page descriptor from FAT mapper
    new_page_xp = mapper_get_fat_page( fat_mapper_xp , new_page_id );

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

    // build extended pointer on new cluster slot in FAT mapper
    new_slot_xp = ppm_page2base( new_page_xp ) + (new_slot_id << 2);
         
    // check selected cluster actually free
    if( hal_remote_l32( new_slot_xp ) != FREE_CLUSTER )
    { 
        printk("\n[ERROR] in %s : selected cluster_id %x not free\n",
        __FUNCTION__, new_cluster_id );
        remote_rwlock_wr_release( lock_xp );
        return -1;
    }

    // update new_cluster slot in FAT mapper
    hal_remote_s32( new_slot_xp  , END_OF_CHAIN_CLUSTER_MIN );

    // handle last_cluster_id argument if non zero
    if( last_cluster_id )
    {
        // get last cluster page & slot indexes in FAT
        last_page_id    = last_cluster_id >> 10;
        last_slot_id    = last_cluster_id & 0x3FF;

        // get relevant FAT page descriptor from FAT mapper
        last_page_xp = mapper_get_fat_page( fat_mapper_xp , last_page_id );

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

        // build extended pointer on new cluster slot in FAT mapper
        last_slot_xp = ppm_page2base( last_page_xp ) + (last_slot_id << 2);
         
        // check last cluster actually end of chain
        if( hal_remote_l32( last_slot_xp ) != END_OF_CHAIN_CLUSTER_MIN )
        { 
            printk("\n[ERROR] in %s : last_cluster_id %x not END_OF_CHAIN\n",
            __FUNCTION__, last_cluster_id );
            remote_rwlock_wr_release( lock_xp );
            return -1;
        }

        // update last_cluster slot in FAT mapper
        hal_remote_s32( last_slot_xp , new_cluster_id );
    }
    else
    {
        last_page_xp = XPTR_NULL;
    }

    // update the FAT new_page on device
    error = fatfs_move_page( new_page_xp , IOC_SYNC_WRITE );

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

    // update the FAT last_page on device when required
    if( (last_page_xp != XPTR_NULL) && (last_page_xp != new_page_xp) )
    {
        error = fatfs_move_page( last_page_xp , IOC_SYNC_WRITE );

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

    // update free cluster info in FATFS context and in FS_INFO sector
    error = fatfs_free_clusters_decrement( XPTR( fat_cxy , fat_fatfs_ctx ) , new_cluster_id );

    if( error )
    { 
        printk("\n[ERROR] in %s : cannot update free cluster info\n", __FUNCTION__ );
        remote_rwlock_wr_release( lock_xp );
        return -1;
    }

    // release FAT lock
    remote_rwlock_wr_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 / allocated cluster_id %x in FAT / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, new_cluster_id, cycle );
#endif

    *searched_cluster_id = new_cluster_id;
    return 0;

}  // end fatfs_cluster_alloc()


//////////////////////////////////////////////////////////////////////////////////////////
// This static function access the FAT (File Allocation Table), stored in the FAT mapper,
// and returns in <searched_cluster_id> the FATFS cluster_id for a given page of a given 
// inode, identified by the <searched_page_id> argument that is the page index in file
// (i.e. the page index in file mapper). The entry point in the FAT is defined by the
// <first_cluster_id> argument, that is the cluster_id of an already allocated cluster.
// It can be the cluster_id of the first page of the file (always registered in the
// fatfs_inode extension), or any page of the file whose <first_page_id> argument
// is smaller than the searched <first_page_id> argument. 
// This function can be called by a thread running in any cluster.
// The FAT mapper being a WRITE-THROUGH cache, this function updates the FAT mapper 
// from informations stored on IOC device in case of miss when scanning the FAT mapper.
// The searched inode mapper being a WRITE-BACK cache, this function allocates a new
// cluster_id when the searched page exist in the inode mapper, and there is no FATFS 
// cluster allocated yet for this page. It updates the FAT, but it does NOT copy the
// mapper page content to the File System.
//////////////////////////////////////////////////////////////////////////////////////////
// @ first_page_id       : [in]  index in file mapper for an existing page.
// @ first_cluster_id	 : [in]  cluster_id for this existing page. 
// @ searched_page_id    : [in]  index in file mapper for the searched page.
// @ searched_cluster_id : [out] cluster_id for the searched page.
// @ return 0 if success / return -1 if a FAT mapper miss cannot be solved,
//                         or if a missing cluster_id cannot be allocated.
//////////////////////////////////////////////////////////////////////////////////////////
static error_t fatfs_get_cluster( uint32_t   first_page_id,
                                  uint32_t   first_cluster_id,
                                  uint32_t   searched_page_id,
                                  uint32_t * searched_cluster_id )
{
    uint32_t   current_page_id;        // index of page in file mapper
    uint32_t   current_cluster_id;     // index of cluster in FATFS
    xptr_t     lock_xp;                // extended pointer on FAT lock
    xptr_t     fat_mapper_xp;          // extended pointer on FAT mapper
    mapper_t * fat_mapper_ptr;         // local pointer on FAT mapper
    cxy_t      fat_cxy;                // FAT cluster
    error_t    error;

assert( __FUNCTION__, (searched_page_id > first_page_id) ,
"searched_page_id must be larger than first_page_id\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 / frst_pid %x / frst_cid %x / srch_pid %d / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid,
first_page_id, first_cluster_id, searched_page_id, cycle );
#endif

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

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

    // get FAT cluster
    fat_cxy = CONFIG_VFS_ROOT_CXY;
    
    // get pointers on FAT mapper
    fat_mapper_ptr = loc_fatfs_ctx->fat_mapper;
    fat_mapper_xp  = XPTR( fat_cxy , fat_mapper_ptr );

    // get local pointer on FATFS context in FAT cluster 
    fatfs_ctx_t * fat_fatfs_ctx = hal_remote_lpt( XPTR( fat_cxy , &vfs_ctx->extend ) );

    // build extended pointer on FAT lock in FAT cluster
    lock_xp = XPTR( fat_cxy , &fat_fatfs_ctx->lock );

    // take FAT lock in read mode
    remote_rwlock_rd_acquire( lock_xp );

    // initialize loop variables
    current_page_id    = first_page_id;
    current_cluster_id = first_cluster_id;

    // scan FAT mapper (i.e. traverse FAT linked list)
    // starting from first_page_id until searched_page_id 
    // each iteration in this loop can change both
    // the FAT page index and the slot index in FAT 
    while( current_page_id < searched_page_id )
    {
        // FAT mapper page and slot indexes (1024 slots per FAT page)
        uint32_t fat_page_index   = current_cluster_id >> 10;
        uint32_t fat_slot_index   = current_cluster_id & 0x3FF;

        // get pointer on current page descriptor in FAT mapper
        xptr_t current_page_xp = mapper_get_fat_page( fat_mapper_xp , fat_page_index );

        if( current_page_xp == XPTR_NULL )
        {
            printk("\n[ERROR] in %s : cannot get page %d from FAT mapper\n",
            __FUNCTION__ , fat_page_index );
            remote_rwlock_rd_release( lock_xp );
            return -1;
        }

        // get pointer on buffer containing the FAT mapper page
        xptr_t     base_xp = ppm_page2base( current_page_xp );
        uint32_t * buffer  = (uint32_t *)GET_PTR( base_xp );

        // get next_cluster_id from FAT slot  
        uint32_t next_cluster_id = hal_remote_l32( XPTR( fat_cxy, &buffer[fat_slot_index] ) );

        // allocate a new FAT cluster when END_OF_CHAIN found
        if( next_cluster_id >= END_OF_CHAIN_CLUSTER_MIN ) 
        {
            // release the FAT lock in read mode,
            remote_rwlock_rd_release( lock_xp );

            // allocate a new cluster_id (and update both FAT mapper and FAT on device).
            error = fatfs_cluster_alloc( current_cluster_id,
                                         &next_cluster_id );
            if( error )
            {
                printk("\n[ERROR] in %s : cannot allocate cluster on FAT32 for page %d\n",
                __FUNCTION__ , current_page_id );
                remote_rwlock_wr_release( lock_xp );
                return -1;
            }

#if (DEBUG_FATFS_GET_CLUSTER & 1)
if( DEBUG_FATFS_GET_CLUSTER < cycle )
printk("\n[%s] allocated a new cluster_id %x in FATFS\n",
__FUNCTION__, next_cluster_id );
#endif
            // take the FAT lock in read mode,
            remote_rwlock_rd_acquire( lock_xp );
        }

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

        // update loop variables
        current_cluster_id = next_cluster_id;
        current_page_id++;
    }
   
    // release FAT lock
    remote_rwlock_rd_release( lock_xp );

#if DEBUG_FATFS_GET_CLUSTER
if( DEBUG_FATFS_GET_CLUSTER < cycle )
printk("\n[%s] thread[%x,%x] exit / frst_pid %d / frst_cid %x / srch_pid %d / srch_cid %x\n",
__FUNCTION__, this->process->pid, this->trdid,
first_page_id, first_cluster_id, searched_page_id, current_cluster_id );
#endif

    *searched_cluster_id = current_cluster_id;
    return 0;

}  // end fatfs_get_cluster()

//////////////////////////////////////////////////////////////////////////////////////////////
// This static function scan the pages of a directory mapper, identified by the <mapper_xp> 
// argument, to find the directory entry identified by the <name> argument, and returns
// a pointer on the directory entry, described as an array of 32 bytes, and the index of 
// this entry in the FAT32 mapper, seen as an array of 32 bytes entries. 
// It makes a local copy of each directory entry to reduce the number of remote accesses.
// It is called by the fatfs_new_dentry_from_mapper() function.
// It can be called by a thread running in any cluster.
//////////////////////////////////////////////////////////////////////////////////////////////
// @ mapper_xp : [in]  extended pointer on directory mapper.
// @ name      : [in]  searched directory entry name.
// @ entry     : [out] buffer for the pointer on the 32 bytes directory entry (when found).
// @ index     : [out] buffer for the directory entry index in mapper. 
// @ return 0 if found / return 1 if not found / return -1 if mapper access error. 
//////////////////////////////////////////////////////////////////////////////////////////////
static error_t fatfs_scan_directory( xptr_t      mapper_xp,
                                     char     *  name,
                                     uint8_t  ** entry,
                                     uint32_t *  index )
{
    uint8_t  buf[32];   // local buffer for one FAT32 directory entry

// check arguments
assert( __FUNCTION__, (mapper_xp != XPTR_NULL) , "mapper pointer is NULL\n" );
assert( __FUNCTION__, (name      != NULL     ) , "child name is undefined\n" );

#if DEBUG_FATFS_SCAN_DIRECTORY
char          parent_name[CONFIG_VFS_MAX_NAME_LENGTH];
uint32_t      cycle      = (uint32_t)hal_get_cycles();
thread_t    * this       = CURRENT_THREAD;
mapper_t    * mapper_ptr = GET_PTR( mapper_xp );
cxy_t         mapper_cxy = GET_CXY( mapper_xp );
vfs_inode_t * inode_ptr  = hal_remote_lpt( XPTR( mapper_cxy , &mapper_ptr->inode ) );
vfs_inode_get_name( XPTR( mapper_cxy , inode_ptr ) , parent_name );
if( DEBUG_FATFS_SCAN_DIRECTORY < cycle )
printk("\n[%s]  thread[%x,%x] enter to search child <%s> in parent <%s> / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, name , parent_name , cycle );
#endif

    char       cname[CONFIG_VFS_MAX_NAME_LENGTH];  // name extracted from 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 one page descriptor
    xptr_t     base_xp;          // extended pointer on one 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
    int32_t    found     = 0;    // not yet = 0 / success = 1 / not found = 2 / error = -1
    uint32_t   page_id   = 0;    // page index in mapper
    uint32_t   offset    = 0;    // byte offset in page

    // Two embedded loops to scan the directory mapper:
    // - scan the parent directory mapper pages 
    // - scan the directory entries in each 4 Kbytes page

    // scan the mapper pages
    while ( found == 0 )
    {
        // get one page
        page_xp = mapper_get_page( mapper_xp , page_id );

        if( page_xp == XPTR_NULL)
        {
            found = -1;
        }

        // get page base
        base_xp = ppm_page2base( page_xp );

#if (DEBUG_FATFS_SCAN_DIRECTORY & 1)
if( DEBUG_FATFS_SCAN_DIRECTORY < cycle )
mapper_display_page( mapper_xp , page_xp , 256 );
#endif
        // scan this page until end of directory, end of page, or name found
        while( (offset < 4096) && (found == 0) )
        {
            // makes a local copy of current directory entry  (32 bytes)
            hal_remote_memcpy( XPTR( local_cxy , buf ) , base_xp + offset , 32 );

            // get attr and ord from local buffer
            attr = fatfs_get_record( DIR_ATTR , buf );   
            ord  = fatfs_get_record( LDIR_ORD , buf );   

            if (ord == NO_MORE_ENTRY)                 // no more entry => break
            {
                found = 2;
            }
            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( buf , lfn1 );
                else if ( seq == 2 ) fatfs_get_name_from_long( buf , lfn2 );
                else if ( seq == 3 ) fatfs_get_name_from_long( buf , lfn3 );
                offset = offset + 32;
            }
            else                                 // NORMAL entry
            {
                // build the extracted name
                if      ( lfn == 0 )
                {
                    fatfs_get_name_from_short( buf , 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 )
                {
                    uint8_t * base = GET_PTR( base_xp );
                    *entry = base + offset;
                    *index = ( (page_id << 12) + offset ) >> 5; 
                    found  = 1;
                }
                offset = offset + 32;
                lfn    = 0;
            }

        }  // end loop on directory entries in page

        page_id++;
        offset = 0;

    }  // end loop on pages

    if( found == 1 )
    {

#if DEBUG_FATFS_SCAN_DIRECTORY
if( DEBUG_FATFS_SCAN_DIRECTORY < cycle )
printk("\n[%s]  thread[%x,%x] exit / found child <%s> in <%s>\n",
__FUNCTION__, this->process->pid, this->trdid, name, parent_name );
#endif
        return 0;
    }
    else if( found == 2 )
    {

#if DEBUG_FATFS_SCAN_DIRECTORY
if( DEBUG_FATFS_SCAN_DIRECTORY < cycle )
printk("\n[%s]  thread[%x,%x] exit / child <%s> in <%s> not found\n",
__FUNCTION__, this->process->pid, this->trdid, name, parent_name );
#endif
        return 1;
    }
    else
    {
        printk("\n[ERROR] in %s : cannot get page %d from mapper\n",
        __FUNCTION__, page_id );

        return -1;
    }
}  // end fatfs_scan_directory()


//////////////////////////////////////////////////////////////////////////////////////////
//              FATFS debug functions 
//////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////
void fatfs_display_ctx( cxy_t cxy )
{
    // get pointer on local FATFS context
    vfs_ctx_t   * vfs_ctx = &fs_context[FS_TYPE_FATFS];
	fatfs_ctx_t * ctx     = hal_remote_lpt( XPTR( cxy , &vfs_ctx->extend ) );

    uint32_t fat_sectors       = hal_remote_l32( XPTR( cxy , &ctx->fat_sectors_count ) );
    uint32_t sector_size       = hal_remote_l32( XPTR( cxy , &ctx->bytes_per_sector ) );
    uint32_t sec_per_clus      = hal_remote_l32( XPTR( cxy , &ctx->sectors_per_cluster ) );
    uint32_t fat_lba           = hal_remote_l32( XPTR( cxy , &ctx->fat_begin_lba ) );
    uint32_t data_lba          = hal_remote_l32( XPTR( cxy , &ctx->cluster_begin_lba ) );
    uint32_t fsinfo_lba        = hal_remote_l32( XPTR( cxy , &ctx->fs_info_lba ) );
    uint32_t root_dir_clus     = hal_remote_l32( XPTR( cxy , &ctx->root_dir_cluster ) );
    uint32_t free_clusters     = hal_remote_l32( XPTR( cxy , &ctx->free_clusters ) );
    uint32_t free_cluster_hint = hal_remote_l32( XPTR( cxy , &ctx->free_cluster_hint ) );
    void   * fat_mapper        = hal_remote_lpt( XPTR( cxy , &ctx->fat_mapper ) );
    void   * fs_info_buffer    = hal_remote_lpt( XPTR( cxy , &ctx->fs_info_buffer ) );

    printk("\n*** FAT context in cluster %x\n" 
           "- fat_sectors       = %d\n"
           "- sector size       = %d\n"
           "- cluster size      = %d\n"
           "- fat_lba           = %x\n"
           "- data_lba          = %x\n"
           "- fsinfo_lba        = %x\n"
           "- root_dir_cluster  = %x\n"
           "- free_clusters     = %x\n"
           "- free_cluster_hint = %x\n"
           "- fat_mapper        = %x\n"
           "- fs_info_buffer    = %x\n",
           cxy,
           fat_sectors,
           sector_size,
           sector_size * sec_per_clus,
           fat_lba,
           data_lba,
           fsinfo_lba,
           root_dir_clus,
           free_clusters,
           free_cluster_hint,
           fat_mapper,
           fs_info_buffer );

}  // end fatfs_display_ctx()

//////////////////////////////////////////
void fatfs_display_fat( uint32_t  min_slot,
                        uint32_t  nb_slots )
{
    // one FAT mapper page contains 1024 slots = 128 lines of 8 slots

    uint32_t   page_id;
    uint32_t   line;
    cxy_t      fat_cxy;         // FAT cluster
    mapper_t * mapper;          // local pointer on FAT mapper
    xptr_t     mapper_xp;       // extended pointer on fat_mapper
    uint32_t   min_cluster_id;  // index of min slot to be displayed
    uint32_t   min_page_id;     // index of min page to be displayed
    uint32_t   min_line_id;     // index of min line in min page ( < 128 )
    uint32_t   max_cluster_id;  // index of max slot to be displayed
    uint32_t   max_page_id;     // index of max page to be displayed
    uint32_t   max_line_id;     // index of max line in max page ( < 128 )

    // compute min values
    min_cluster_id = min_slot & 0xFFFFFFF8;
    min_line_id    = (min_cluster_id & 0x3FF) >> 3;
    min_page_id    = min_cluster_id >> 10;

    // compute max values
    max_cluster_id = min_slot + nb_slots - 1;
    max_line_id    = (max_cluster_id & 0x3FF) >> 3;
    max_page_id    = max_cluster_id >> 10;

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

    // get FAT cluster
    fat_cxy = CONFIG_VFS_ROOT_CXY;

    // get pointers on FAT mapper (in FAT cluster)
    mapper    = loc_fatfs_ctx->fat_mapper;
    mapper_xp = XPTR( fat_cxy , mapper );

    // get pointer on FATFS context in FAT cluster
    fatfs_ctx_t * fat_fatfs_ctx = hal_remote_lpt( XPTR( fat_cxy , &vfs_ctx->extend ) );
 
    // get current value of hint and free_clusters
    uint32_t hint = hal_remote_l32( XPTR( fat_cxy , &fat_fatfs_ctx->free_cluster_hint ) );
    uint32_t free = hal_remote_l32( XPTR( fat_cxy , &fat_fatfs_ctx->free_clusters ) );

    printk("\n***** FAT mapper / cxy %x / free_clusters %x / hint %x\n", fat_cxy, free, hint );

    // scan all pages as required by min_page_id and max_page_id
    for( page_id = min_page_id ; page_id <= max_page_id ; page_id++ )
    {
        // get extended pointer on requested page descriptor in FAT mapper 
        xptr_t page_xp = mapper_get_fat_page( mapper_xp , page_id );

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

        // compute min_line & max_line in current page
        uint32_t min_line = (page_id == min_page_id) ? min_line_id : 0;
        uint32_t max_line = (page_id == max_page_id) ? max_line_id : 127;

        // loop on lines in current page
        for( line = min_line ; line <= max_line ; line++ )
        {
            printk("%x : %X | %X | %X | %X | %X | %X | %X | %X\n",
            (page_id << 10) + (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_check_free_info( void )
{
    error_t       error;
    fatfs_ctx_t * fatfs_ctx_ptr;              // local pointer on fatfs context in cluster 0
    uint32_t      ctx_free_clusters;          // number of free clusters from fatfs context
    uint32_t      ctx_free_cluster_hint;      // free cluster hint from fatfs context
    uint32_t      ioc_free_clusters;          // number of free clusters from fatfs context
    uint32_t      ioc_free_cluster_hint;      // free cluster hint from fatfs context
    uint32_t      fs_info_lba;                // lba of FS_INFO sector on IOC device
    uint8_t     * fs_info_buffer;             // local pointer on FS_INFO buffer in cluster 0
    xptr_t        fs_info_buffer_xp;          // extended pointer on FS_INFO buffer in cluster 0
    uint8_t       tmp_buf[512];               // 512 bytes temporary buffer 
    xptr_t        tmp_buf_xp;                 // extended pointer on temporary buffer

#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

    // get pointer on fatfs context in cluster 0
    fatfs_ctx_ptr = hal_remote_lpt( XPTR( 0 , &fs_context[FS_TYPE_FATFS].extend ) );

    // get "free_clusters" and "free_cluster_hint" from fatfs context in cluster 0
    ctx_free_clusters     = hal_remote_l32( XPTR( 0 , &fatfs_ctx_ptr->free_clusters ) );
    ctx_free_cluster_hint = hal_remote_l32( XPTR( 0 , &fatfs_ctx_ptr->free_cluster_hint ) );

    // get fs_info_lba 
    fs_info_lba = hal_remote_l32( XPTR( 0 , &fatfs_ctx_ptr->fs_info_lba ) );

    // build extended pointer on temporary buffer
    tmp_buf_xp = XPTR( local_cxy , tmp_buf );

    // copy FS_INFO sector from IOC to local buffer
    error = dev_ioc_sync_read( tmp_buf_xp , fs_info_lba , 1 );

    if ( error )
    {
        printk("\n[ERROR] in %s : cannot access FS_INFO on IOC device\n", __FUNCTION__ );
        return -1;
    }

    // get current values of "free_clusters" and "free_cluster_hint" from FS_INFO on IOC
    ioc_free_clusters     = fatfs_get_remote_record( FS_FREE_CLUSTERS     , tmp_buf_xp );
    ioc_free_cluster_hint = fatfs_get_remote_record( FS_FREE_CLUSTER_HINT , tmp_buf_xp );

#if DEBUG_FATFS_SYNC_FSINFO
if( DEBUG_FATFS_SYNC_FSINFO < cycle )
printk("\n[%s] thread[%x,%x] / ctx_free %x / ioc_free %x / ctx_hint %x / ioc_hint %x\n",
__FUNCTION__ , this->process->pid, this->trdid, 
ctx_free_clusters, ioc_free_clusters, ctx_free_cluster_hint, ioc_free_cluster_hint );
#endif

    // check values
    if( (ioc_free_clusters     != ctx_free_clusters) || 
        (ioc_free_cluster_hint != ctx_free_cluster_hint) )
    {
        printk("\n[WARNING] in %s : unconsistent free clusters info\n"
        " ioc_free %x / ctx_free %x / ioc_hint %x / ctx_hint %x\n",
        __FUNCTION__, ioc_free_clusters, ctx_free_clusters,
        ioc_free_cluster_hint, ctx_free_cluster_hint );

        // get pointers on FS_INFO buffer in cluster 0
        fs_info_buffer    = hal_remote_lpt( XPTR( 0 , &fatfs_ctx_ptr->fs_info_buffer ) );
        fs_info_buffer_xp = XPTR( 0 , fs_info_buffer );

        // update FS_INFO buffer in cluster 0 
        fatfs_set_remote_record(FS_FREE_CLUSTERS    ,fs_info_buffer_xp,ctx_free_clusters );
        fatfs_set_remote_record(FS_FREE_CLUSTER_HINT,fs_info_buffer_xp,ctx_free_cluster_hint);

        // update the FS_INFO sector on IOC device
        error = dev_ioc_sync_write( fs_info_buffer_xp , fs_info_lba , 1 );

        if ( error )
        {
            printk("\n[ERROR] in %s : cannot update FS_INFO on IOC device\n", __FUNCTION__ );
            return -1;
        }
    }

#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_check_free_info()





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


/////////////////////////////////////
xptr_t  fatfs_ctx_alloc( cxy_t  cxy )
{
    kmem_req_t    req;

    // allocate memory from remote cluster
	req.type     = KMEM_KCM;
	req.order    = bits_log2( sizeof(fatfs_ctx_t) );
    req.flags    = AF_KERNEL | AF_ZERO;
 
    return XPTR( cxy , kmem_remote_alloc( cxy , &req ) );

}  //end faffs_ctx_alloc()

///////////////////////////////////////////////
error_t  fatfs_ctx_init( xptr_t  fatfs_ctx_xp )
{
    error_t       error;
    kmem_req_t    req;
    cxy_t         cxy;             // FATFS context cluster identifier
    fatfs_ctx_t * fatfs_ctx_ptr;   // local pointer on FATFS context
    uint8_t     * buffer;          // local pointer on 512 bytes buffer
    xptr_t        buffer_xp;       // extended pointer on 512 bytes buffer
    xptr_t        fat_mapper_xp;   // extended pointer on FAT mapper
    mapper_t    * fat_mapper;      // local pointer on FAT mapper

    // get FATFS context cluster and local pointer
    cxy           = GET_CXY( fatfs_ctx_xp );        
    fatfs_ctx_ptr = GET_PTR( fatfs_ctx_xp );
    
#if DEBUG_FATFS_CTX_INIT
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
if( DEBUG_FATFS_CTX_INIT < cycle )
printk("\n[%s] thread[%x,%x] enter for fatfs_ctx (%x,%x) / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, cxy, fatfs_ctx_ptr , cycle );
#endif

    // allocate a 512 bytes buffer in remote cluster, used to store
    // temporarily the BOOT sector, and permanently the FS_INFO sector
	req.type    = KMEM_KCM;
    req.order   = 9;                    // 512 bytes
    req.flags   = AF_KERNEL | AF_ZERO;
	buffer      = kmem_remote_alloc( cxy , &req );

    if( buffer == NULL ) 
    {
        printk("\n[PANIC] in %s : cannot allocate buffer in cluster %x\n",
        __FUNCTION__ , cxy );
        return -1;
    }
     
    // build extended pointer on buffer
    buffer_xp = XPTR( cxy , buffer );

    // load the BOOT record from device
    error = dev_ioc_sync_read( buffer_xp , 0 , 1 );

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

#if (DEBUG_FATFS_CTX_INIT & 0x1)
uint8_t bs[256];
hal_remote_memcpy( XPTR( local_cxy , bs ) , buffer_xp , 256 );
if( DEBUG_FATFS_CTX_INIT < cycle )
putb( "boot record", bs , 256 );
#endif

    // get sector size from boot record
    uint32_t sector_size = fatfs_get_remote_record( BPB_BYTSPERSEC , buffer_xp );

    if ( sector_size != 512 )
    {
        printk("\n[PANIC] in %s : sector size must be 512 bytes\n", __FUNCTION__ );
        return -1;
    }

    // get cluster size from boot record
    uint32_t nb_sectors = fatfs_get_remote_record( BPB_SECPERCLUS , buffer_xp );

    if ( nb_sectors != 8 )
    {
        printk("\n[PANIC] in %s : cluster size must be 8 sectors\n", __FUNCTION__ );
        return -1;
    }

    // get number of FAT copies from boot record
    uint32_t nb_fats = fatfs_get_remote_record( BPB_NUMFATS , buffer_xp );

    if ( nb_fats != 1 )
    {
        printk("\n[PANIC] in %s : number of FAT copies must be 1\n", __FUNCTION__ );
        return -1;
    }

    // get number of sectors in FAT from boot record
    uint32_t fat_sectors = fatfs_get_remote_record( BPB_FAT32_FATSZ32 , buffer_xp );

    if ( (fat_sectors & 0xF) != 0 )
    {
        printk("\n[PANIC] in %s : FAT size not multiple of 16 sectors\n", __FUNCTION__ );
        return -1;
    }

    // get root cluster from boot record
    uint32_t root_cluster = fatfs_get_remote_record( BPB_FAT32_ROOTCLUS , buffer_xp );

    if ( root_cluster != 2 ) 
    {
        printk("\n[PANIC] in %s : root cluster index must be 2\n", __FUNCTION__ );
        return -1;
    }

    // get FAT lba from boot record
    uint32_t fat_lba = fatfs_get_remote_record( BPB_RSVDSECCNT , buffer_xp );

    // get FS_INFO sector lba from boot record
    uint32_t fs_info_lba = fatfs_get_remote_record( BPB_FAT32_FSINFO , buffer_xp );

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

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

    // get free_clusters number from FS_INFO record
    uint32_t free_clusters = fatfs_get_remote_record( FS_FREE_CLUSTERS , buffer_xp );

    if ( free_clusters >= fat_sectors << 7 )
    {
        printk("\n[PANIC] in %s : unconsistent free_clusters\n", __FUNCTION__ );
        return -1;
    }

    // get free_cluster_hint from FS_INFO record
    uint32_t free_hint = fatfs_get_remote_record( FS_FREE_CLUSTER_HINT , buffer_xp );

    if ( free_hint >= fat_sectors << 7 )
    {
        printk("\n[PANIC] in %s : unconsistent free_cluster_hint\n", __FUNCTION__ );
        return -1;
    }

    // allocate a mapper for the FAT in remote cluster
    fat_mapper_xp  = mapper_create( cxy , FS_TYPE_FATFS );

    // get local pointer on FAT mapper
    fat_mapper = GET_PTR( fat_mapper_xp );

    if ( fat_mapper == NULL )
    {
        printk("\n[PANIC] in %s : no memory for FAT mapper in cluster %x\n",
        __FUNCTION__ , cxy );
        return -1;
    }

#if (DEBUG_FATFS_CTX_INIT & 0x1)
if( DEBUG_FATFS_CTX_INIT < cycle )
printk("\n[%s] sector_size %d / nb_sectors %d / fat_sectors %x / hint %x\n",
__FUNCTION__, sector_size, nb_sectors, fat_sectors, free_hint );
#endif

    // the inode field is NULL for the FAT mapper
    hal_remote_spt( XPTR( cxy , &fat_mapper->inode ) , NULL );

    // initialize the FATFS context
    hal_remote_s32( XPTR( cxy , &fatfs_ctx_ptr->fat_begin_lba       ), fat_lba );
    hal_remote_s32( XPTR( cxy , &fatfs_ctx_ptr->fat_sectors_count   ), fat_sectors );
    hal_remote_s32( XPTR( cxy , &fatfs_ctx_ptr->bytes_per_sector    ), sector_size );
    hal_remote_s32( XPTR( cxy , &fatfs_ctx_ptr->sectors_per_cluster ), nb_sectors );
    hal_remote_s32( XPTR( cxy , &fatfs_ctx_ptr->cluster_begin_lba   ), fat_lba + fat_sectors );
    hal_remote_s32( XPTR( cxy , &fatfs_ctx_ptr->root_dir_cluster    ), 2 );
    hal_remote_spt( XPTR( cxy , &fatfs_ctx_ptr->fat_mapper          ), fat_mapper );
    hal_remote_s32( XPTR( cxy , &fatfs_ctx_ptr->fs_info_lba         ), fs_info_lba );
    hal_remote_s32( XPTR( cxy , &fatfs_ctx_ptr->free_clusters       ), free_clusters );
    hal_remote_s32( XPTR( cxy , &fatfs_ctx_ptr->free_cluster_hint   ), free_hint );
    hal_remote_spt( XPTR( cxy , &fatfs_ctx_ptr->fs_info_buffer      ), buffer );

    // initialize FATFS lock
    remote_rwlock_init( XPTR( cxy , &fatfs_ctx_ptr->lock ) , LOCK_FATFS_FAT );

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

	return 0;

}  // end fatfs_ctx_init()

//////////////////////////////////////////////
void fatfs_ctx_destroy( xptr_t  fatfs_ctx_xp )
{
    kmem_req_t   req;
    mapper_t   * fat_mapper;
    uint8_t    * fs_info_buffer;

    // get FATFS context cluster and local pointer
    fatfs_ctx_t * fatfs_ctx_ptr = GET_PTR( fatfs_ctx_xp );
    cxy_t         fatfs_ctx_cxy = GET_CXY( fatfs_ctx_xp );

    // get pointer on FAT mapper
    fat_mapper = hal_remote_lpt( XPTR( fatfs_ctx_cxy , &fatfs_ctx_ptr->fat_mapper ) );

    // release FAT mapper
    mapper_destroy( XPTR( fatfs_ctx_cxy , fat_mapper ) );

    // get pointer on FS_INFO buffer
    fs_info_buffer = hal_remote_lpt( XPTR( fatfs_ctx_cxy , &fatfs_ctx_ptr->fs_info_buffer ) );

    // release FS_INFO buffer
    req.type = KMEM_KCM;
    req.ptr  = fs_info_buffer;
    kmem_remote_free( fatfs_ctx_cxy , &req );

    // release FATFS context descriptor
    req.type = KMEM_KCM;
    req.ptr  = fatfs_ctx_ptr;
    kmem_remote_free( fatfs_ctx_cxy , &req );

}  // end fatfs_ctx_destroy()

/////////////////////////////////////////////////////////
error_t fatfs_add_dentry( xptr_t         parent_inode_xp,
                          vfs_dentry_t * dentry_ptr )
{
    error_t       error;
    vfs_inode_t * parent_inode_ptr;   // parent inode local pointer
    cxy_t         parent_cxy;         // pparent inode cluster
    xptr_t        child_inode_xp;     // extended pointer on child inode 
    cxy_t         child_cxy;          // child inode cluster
    vfs_inode_t * child_inode_ptr;    // child inode local pointer
    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_ptr;         // local pointer on parent directory mapper
    xptr_t        mapper_xp;          // extended pointer on parent directory mapper
    uint32_t      size;               // child inode size
    uint32_t      type;               // child inode type
    void        * extend;             // child inode extension
    uint32_t      cluster_id;         // child inode first cluster_id in FATFS 

    char          child_name[CONFIG_VFS_MAX_NAME_LENGTH];

    uint8_t       buf[32];            // local buffer for one FAT32 directory entry
 
// check arguments
assert( __FUNCTION__, (parent_inode_xp != XPTR_NULL) , "parent_inode_xp argument is NULL\n" );
assert( __FUNCTION__, (dentry_ptr      != NULL)      , "dentry_ptr argument is NULL\n" );

    // get directory inode cluster and local pointer 
    parent_cxy       = GET_CXY( parent_inode_xp );
    parent_inode_ptr = GET_PTR( parent_inode_xp );

    // get extended pointers on child inode
    child_inode_xp  = hal_remote_l64( XPTR( parent_cxy , &dentry_ptr->child_xp ) );
    child_cxy       = GET_CXY( child_inode_xp );
    child_inode_ptr = GET_PTR( child_inode_xp );

    // get a local copy of the child name
    vfs_inode_get_name( child_inode_xp  , child_name );

#if DEBUG_FATFS_ADD_DENTRY
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
char       parent_name[CONFIG_VFS_MAX_NAME_LENGTH];
vfs_inode_get_name( parent_inode_xp , parent_name );
if( DEBUG_FATFS_ADD_DENTRY < cycle )
printk("\n[%s]  thread[%x,%x] enter for <%s> in <%s> directory / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, child_name, parent_name, cycle );
#endif

    // get pointers on parent directory mapper
    mapper_ptr = hal_remote_lpt( XPTR( parent_cxy , &parent_inode_ptr->mapper ) );
    mapper_xp  = XPTR( parent_cxy , mapper_ptr );

#if (DEBUG_FATFS_ADD_DENTRY & 1)
mapper_display_page( mapper_xp , 0 , 512 );
#endif

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

    // analyse child name
    error = fatfs_name_format( child_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

    // initialise loop variables
    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_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 );

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

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

#if (DEBUG_FATFS_ADD_DENTRY & 1)
if( DEBUG_FATFS_ADD_DENTRY < cycle )
printk("\n[%s]  thread[%x,%x] found NO_MORE_ENTRY : page_id %d / offset %d\n",
__FUNCTION__, this->process->pid, this->trdid, page_id, offset );
#endif

    // Modify the directory mapper: depending on the name length,
    // the new child requires to write (3, 4, or 5) directory entries.
    // We build one complete directory entry in a local buffer
    // before copying it the remote mapper. We use 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 two pages.

    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;
    
    uint32_t   i;           // byte index in one 32 bytes directory
    uint32_t   c;           // character index in name

    while ( step )   
    {
        // this block is only executed when the new name spread 
        // on two pages, and we need to access a new page in mapper
        if ( offset >= 4096 )
        {
            // copy the modified page to IOC device
            error = fatfs_move_page( page_xp , IOC_SYNC_WRITE );   

            if ( error )
            {
                printk("\n[ERROR] in %s : cannot update directory on device\n",
                __FUNCTION__ );
                return -1;
            }

            // get the next page in directory mapper
            page_xp  = mapper_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 );
            
            // update offset
            offset = 0;
        }

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

        // write one FATFS directory entry (32 bytes) per iteration
        switch ( step )
        {
            case 5:   // write LFN3 entry
            {
                c = 26;

                // write 32 bytes in local buf
                for ( i = 0 ; i < 32 ; i++ )
                {
                    if (i == 0)
                    {
                        if ( nb_lfn == 3) buf[i] = 0x43;
                        else              buf[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 ) )
                    {
                                          buf[i] = child_name[c];
                                          c++;
                    }
                    else if (i == 11)     buf[i] = 0x0F;
                    else if (i == 13)     buf[i] = checksum;
                    else                  buf[i] = 0x00;
                }

                // copy 32 bytes from local buffer to remote mapper
                hal_remote_memcpy( base_xp + offset , XPTR( local_cxy  , buf ) , 32 );

                step--;
                break;
            }
            case 4:   // write LFN2 entry  
            {
                c = 13;

                // write 32 bytes in local buf
                for ( i = 0 ; i < 32 ; i++ )
                {
                    if (i == 0)
                    {
                        if ( nb_lfn == 2) buf[i] = 0x42;
                        else              buf[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 ) )
                    {
                                          buf[i] = child_name[c];
                                          c++;
                    }
                    else if (i == 11)     buf[i] = 0x0F;
                    else if (i == 13)     buf[i] = checksum;
                    else                  buf[i] = 0x00;
                }

                // copy 32 bytes from local buffer to remote mapper
                hal_remote_memcpy( base_xp + offset , XPTR( local_cxy  , buf ) , 32 );

                step--;
                break;
            }
            case 3:   // Write LFN1 entry   
            {
                c = 0;

                // write 32 bytes in local buf
                for ( i = 0 ; i < 32 ; i++ )
                {
                    if (i == 0)
                    {
                        if ( nb_lfn == 1) buf[i] = 0x41;
                        else              buf[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 ) )
                    {
                                          buf[i] = child_name[c];
                                          c++;
                    }
                    else if (i == 11)     buf[i] = 0x0F;
                    else if (i == 13)     buf[i] = checksum;
                    else                  buf[i] = 0x00;
                }

                // copy 32 bytes from local buffer to remote mapper
                hal_remote_memcpy( base_xp + offset , XPTR( local_cxy  , buf ) , 32 );

                step--;
                break;
            }
            case 2:   // write NORMAL entry     
            {
                // write 32 bytes in local buf
                for ( i = 0 ; i < 32 ; i++ )
                {
                    if      ( i < 11 )                               // 8.3 SFN
                    {
                                          buf[i] = sfn[i];
                    }
                    else if (i == 11)                                // ATTR
                    {
                        if (type == FILE_TYPE_DIR)  buf[i] = 0x10;
                        else                         buf[i] = 0x20;
                    }
                    else if (i == 20)     buf[i] = cluster_id>>16;   // cluster.B2
                    else if (i == 21)     buf[i] = cluster_id>>24;   // cluster.B3
                    else if (i == 26)     buf[i] = cluster_id>>0;    // cluster.B0
                    else if (i == 27)     buf[i] = cluster_id>>8;    // cluster.B1
                    else if (i == 28)     buf[i] = size>>0;          // size.B0
                    else if (i == 29)     buf[i] = size>>8;          // size.B1
                    else if (i == 30)     buf[i] = size>>16;         // size.B2
                    else if (i == 31)     buf[i] = size>>24;         // size.B3
                    else                  buf[i] = 0x00;
                }

                // copy 32 bytes from local buffer to remote mapper
                hal_remote_memcpy( base_xp + offset , XPTR( local_cxy  , buf ) , 32 );

                // set the dentry "extend" field 
                hal_remote_spt( XPTR( parent_cxy , &dentry_ptr->extend ),
                                (void *)(intptr_t)(((page_id << 12) + offset) >> 5 ) );
                step--;
                break;
            }
            case 1:   // write NOMORE entry  
            {
                hal_remote_s32( base_xp + offset , 0 );

                step--;
                break;
            }

        } // end switch step

        offset += 32;

    } // end while     

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

    if ( error )
    {
        printk("\n[ERROR] in %s : cannot update directory on device\n",
        __FUNCTION__ );
        return -1;
    }

#if DEBUG_FATFS_ADD_DENTRY
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_ADD_DENTRY < cycle )
printk("\n[%s]  thread[%x,%x] exit for <%s> in <%s> directory\n",
__FUNCTION__, this->process->pid, this->trdid, child_name, parent_name );
#endif

#if (DEBUG_FATFS_ADD_DENTRY & 1)
mapper_display_page( mapper_xp , 0 , 512 );
#endif

    return 0;

}  // end fatfs_add_dentry()

////////////////////////////////////////////////////////////
error_t fatfs_remove_dentry( xptr_t         parent_inode_xp,
                             vfs_dentry_t * dentry_ptr )
{
    error_t       error;
    vfs_inode_t * parent_inode_ptr;   // parent inode local pointer
    cxy_t         parent_cxy;         // pparent inode cluster
    xptr_t        child_inode_xp;     // extended pointer on child inode 
    cxy_t         child_cxy;          // child inode cluster
    vfs_inode_t * child_inode_ptr;    // child inode local pointer
    xptr_t        mapper_xp;          // extended pointer on mapper
    mapper_t    * mapper_ptr;         // local pointer on mapper
    xptr_t        page_xp;            // extended pointer on mapper page descriptor
    xptr_t        base_xp;            // extended pointer on mapper page base
    uint32_t      dentry_id;          // FAT32 directory entry index
    uint32_t      page_id;            // page index in directory mapper
    uint32_t      offset;             // offset in this mapper page

    char          child_name[CONFIG_VFS_MAX_NAME_LENGTH];

// check arguments
assert( __FUNCTION__, (parent_inode_xp != XPTR_NULL) , "parent_inode_xp argument is NULL\n" );
assert( __FUNCTION__, (dentry_ptr      != NULL)      , "dentry_ptr argument is NULL\n" );

    // get directory inode cluster and local pointer 
    parent_cxy       = GET_CXY( parent_inode_xp );
    parent_inode_ptr = GET_PTR( parent_inode_xp );

    // get extended pointers on child inode
    child_inode_xp  = hal_remote_l64( XPTR( parent_cxy , &dentry_ptr->child_xp ) );
    child_cxy       = GET_CXY( child_inode_xp );
    child_inode_ptr = GET_PTR( child_inode_xp );

    // get a local copy of the child name
    vfs_inode_get_name( child_inode_xp  , child_name );

#if DEBUG_FATFS_REMOVE_DENTRY
uint32_t   cycle = (uint32_t)hal_get_cycles();
thread_t * this  = CURRENT_THREAD;
char       parent_name[CONFIG_VFS_MAX_NAME_LENGTH];
vfs_inode_get_name( parent_inode_xp , parent_name );
if( DEBUG_FATFS_REMOVE_DENTRY < cycle )
printk("\n[%s]  thread[%x,%x] enter for <%s> in <%s> directory / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, child_name, parent_name, cycle );
#endif

    // get pointers on directory mapper
    mapper_ptr = hal_remote_lpt( XPTR( parent_cxy , &parent_inode_ptr->mapper ) );
    mapper_xp  = XPTR( parent_cxy , mapper_ptr );

    // compute number of LFN entries
    uint32_t nb_lfn;
    uint32_t name_length = strlen( child_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,4) 32 bytes entries:
    // - the NORMAL entry, registered in dentry->extend.
    // - the (1,2,3) 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 NORMAL entry index from dentry extension 
    dentry_id  = (uint32_t)(intptr_t)hal_remote_lpt( XPTR( parent_cxy , &dentry_ptr->extend ) ); 

    // get page index and offset in parent directory mapper
    page_id  = dentry_id >> 7;
    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 
    page_xp  = mapper_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 extended pointer on page base
    base_xp = ppm_page2base( page_xp );

    // invalidate NORMAL entry in directory cache
    hal_remote_sb( base_xp + offset , 0xE5 );

    // invalidate LFN entries
    while ( nb_lfn )
    {
        // this block is only executed when the removed name
        // spread on two mapper pages
        if (offset == 0)  // we must load page (page_id - 1)
        {
            // copy the modified page to the IOC device
            error = fatfs_move_page( page_xp , IOC_SYNC_WRITE );   

            if ( error )
            {
                printk("\n[ERROR] in %s : cannot update directory on device\n",
                __FUNCTION__ );
                return -1;
            }

            // get extended pointer on page descriptor 
            page_xp  = mapper_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 extended pointer on page base
            base_xp = ppm_page2base( page_xp );

            // update offset
            offset = 4096;
        }

        offset = offset - 32;

// check for LFN entry
assert( __FUNCTION__, (fatfs_get_remote_record( DIR_ATTR, base_xp + offset ) == ATTR_LONG_NAME_MASK ),
"this directory entry must be a LFN\n");

        // invalidate LFN entry
        hal_remote_sb( base_xp + offset , 0xE5 );

        nb_lfn--;
    }     

    // copy the modified page to the IOC device
    error = fatfs_move_page( page_xp , IOC_SYNC_WRITE );   
    
    if ( error )
    {
        printk("\n[ERROR] in %s : cannot update directory on device\n",
        __FUNCTION__ );
        return -1;
    }

#if DEBUG_FATFS_REMOVE_DENTRY
cycle = (uint32_t)hal_get_cycles();
if( DEBUG_FATFS_REMOVE_DENTRY < cycle )
printk("\n[%s] thread[%x,%x] exit for <%s> in <%s> directory\n",
__FUNCTION__, this->process->pid, this->trdid, child_name, parent_name );
#endif

    return 0;

}  // end fatfs_remove_dentry

/////////////////////////////////////////////////////////////////////
error_t fatfs_new_dentry_from_mapper( xptr_t         parent_inode_xp,
                                      vfs_dentry_t * dentry_ptr )
{
    uint32_t       cluster_id;        // directory entry first FATFS cluster
    uint32_t       size;              // directory entry size
    bool_t         is_dir;            // directory entry type (file/dir)
    cxy_t          parent_cxy;        // parent inode cluster  
    vfs_inode_t  * parent_inode_ptr;  // parent inode local pointer
    mapper_t     * parent_mapper;     // pointer on parent directory mapper
    xptr_t         child_inode_xp;    // extended pointer on child inode
    cxy_t          child_cxy;         // child inode cluster  
    vfs_inode_t  * child_inode_ptr;   // child inode local pointer
    error_t        error;

    uint8_t        buf[32];           // FAT32 directory entry local copy

    char           parent_name[CONFIG_VFS_MAX_NAME_LENGTH];     // local parent name copy
    char           child_name[CONFIG_VFS_MAX_NAME_LENGTH];      // local child name copy

// check arguments
assert( __FUNCTION__, (parent_inode_xp != XPTR_NULL)  , "parent_inode_xp is NULL\n" );
assert( __FUNCTION__, (dentry_ptr      != NULL ) , "dentry_ptr is NULL\n" );

    // get parent inode cluster and local pointer
    parent_cxy       = GET_CXY( parent_inode_xp );
    parent_inode_ptr = GET_PTR( parent_inode_xp );

    // get child inode cluster and pointers
    child_inode_xp  = hal_remote_l64( XPTR( parent_cxy , &dentry_ptr->child_xp ) );
    child_cxy       = GET_CXY( child_inode_xp );
    child_inode_ptr = GET_PTR( child_inode_xp );

    // get child and parent names
    vfs_inode_get_name( parent_inode_xp , parent_name );
    vfs_inode_get_name( child_inode_xp  , child_name );

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

    // get local pointer on parent mapper
    parent_mapper = hal_remote_lpt( XPTR( parent_cxy , &parent_inode_ptr->mapper ) );

    // try to get pointer and index of directory entry in mapper 
    uint8_t      * entry = NULL;
    uint32_t       index = 0;

    error  = fatfs_scan_directory( XPTR( parent_cxy , parent_mapper ),
                                   child_name, 
                                   &entry,
                                   &index );
    
    // an error can be non fatal, for a new (created) entry
    if( error )  return -1;

    // get local copy of found directory entry
    hal_remote_memcpy( XPTR( local_cxy  , buf ),
                       XPTR( parent_cxy , entry ), 32 );

    // get relevant infos from directory entry
    cluster_id = (fatfs_get_record( DIR_FST_CLUS_HI , buf ) << 16) |
                 (fatfs_get_record( DIR_FST_CLUS_LO , buf )      ) ;
    is_dir     = (fatfs_get_record( DIR_ATTR        , buf ) & ATTR_DIRECTORY );
    size       =  fatfs_get_record( DIR_FILE_SIZE   , buf );

    // update the child inode "type", "size", and "extend" fields
    vfs_file_type_t type = (is_dir) ? FILE_TYPE_DIR : FILE_TYPE_REG;

    hal_remote_s32( XPTR( child_cxy , &child_inode_ptr->type   ) , type );
    hal_remote_s32( XPTR( child_cxy , &child_inode_ptr->size   ) , size );
    hal_remote_s32( XPTR( child_cxy , &child_inode_ptr->extend ) , cluster_id );

    // update the dentry "extend" field
    hal_remote_spt( XPTR( parent_cxy , &dentry_ptr->extend ) , (void *)(intptr_t)index );

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

#if (DEBUG_FATFS_NEW_DENTRY_FROM & 1)
if( DEBUG_FATFS_NEW_DENTRY_FROM < cycle )
fatfs_display_fat( cluster_id , 32 );
#endif

    return 0;

}  // end fatfs_new_dentry_from_mapper()

///////////////////////////////////////////////////////////////////
error_t fatfs_new_dentry_to_mapper( xptr_t         parent_inode_xp,
                                    vfs_dentry_t * dentry_ptr )
{
    uint32_t       cluster_id;        // directory entry cluster
    cxy_t          parent_cxy;        // parent inode cluster identifier  
    vfs_inode_t  * parent_inode_ptr;  // child inode local pointer
    xptr_t         child_inode_xp;    // extended pointer on child inode
    cxy_t          child_cxy;         // child inode cluster identifier  
    vfs_inode_t  * child_inode_ptr;   // child inode local pointer
    error_t        error;

    char           parent_name[CONFIG_VFS_MAX_NAME_LENGTH];
    char           child_name[CONFIG_VFS_MAX_NAME_LENGTH];

// check arguments
assert( __FUNCTION__, (parent_inode_xp != XPTR_NULL) , "parent_inode_xp argument is NULL\n" );
assert( __FUNCTION__, (dentry_ptr      != NULL)      , "dentry_ptr argument NULL\n" );

    // get child inode cluster and local pointer
    parent_cxy       = GET_CXY( parent_inode_xp );
    parent_inode_ptr = GET_PTR( parent_inode_xp );

    // get child inode cluster and pointers
    child_inode_xp  = hal_remote_l64( XPTR( parent_cxy , &dentry_ptr->child_xp ) );
    child_cxy       = GET_CXY( child_inode_xp );
    child_inode_ptr = GET_PTR( child_inode_xp );

    // get child and parent names
    vfs_inode_get_name( parent_inode_xp , parent_name );
    vfs_inode_get_name( child_inode_xp  , child_name );

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

    // 1. allocate one FATFS cluster (update FAT and FSINFO) 
    error = fatfs_cluster_alloc( 0 , &cluster_id );

    if( error )
    {
        printk("\n[ERROR] in %s : cannot find a free cluster_id\n",
        __FUNCTION__ );
        return -1;
    }
       
    // 2. register cluster_id in inode descriptor
    hal_remote_spt( XPTR( child_cxy , &child_inode_ptr->extend ),
                    (void*)(intptr_t)cluster_id );
    
    // 3. introduce dentry in the directory mapper
    error = fatfs_add_dentry( parent_inode_xp , dentry_ptr );
        
    if( error )
    {
        printk("\n[ERROR] in %s : cannot update parent directory mapper\n",
        __FUNCTION__ );
        // TODO release cluster_id [AG]
        return -1;
    }

#if DEBUG_FATFS_NEW_DENTRY_TO_MAP
if( DEBUG_FATFS_NEW_DENTRY_TO_MAP < cycle )
printk("\n[%s]  thread[%x,%x] exit for <%s> in <%s> / cluster_id %x\n",
__FUNCTION__, this->process->pid, this->trdid, child_name, parent_name, cluster_id );
#endif

    return 0;

}  // end fatfs_new_dentry_to mapper()


////////////////////////////////////////////////////////////
error_t fatfs_update_dentry( xptr_t         parent_inode_xp,
                             vfs_dentry_t * dentry_ptr )
{
    cxy_t         parent_cxy;        // parent directory cluster identifier
    vfs_inode_t * parent_inode_ptr;  // extended pointer on parent directory inode
    mapper_t    * parent_mapper_ptr; // local pointer on parent directory mapper
    xptr_t        parent_mapper_xp;  // extended pointer on parent directory mapper
    xptr_t        child_inode_xp;    // extended pointer on child inode
    cxy_t         child_cxy;         // child inode cluster identifier
    vfs_inode_t * child_inode_ptr;   // extended pointer on child inode

    uint32_t      current_size;      // current size in directory mapper 
    uint32_t      new_size;          // new size (from child inode) 
    
    uint32_t      entry_id;          // directory entry index in parent directory mapper
    uint32_t      page_id;           // page_index in parent directory mapper
    uint32_t      offset;            // directory entry offset in page
    xptr_t        page_xp;           // extended pointer on page descriptor
    xptr_t        base_xp;           // extended pointer on page base 
    xptr_t        entry_xp;          // extended pointer on directory entry

    error_t       error;

    char       parent_name[CONFIG_VFS_MAX_NAME_LENGTH];
    char       child_name[CONFIG_VFS_MAX_NAME_LENGTH];

// check arguments
assert( __FUNCTION__, (parent_inode_xp  != XPTR_NULL) , "parent_inode_xp argument is NULL\n" );
assert( __FUNCTION__, (dentry_ptr       != NULL)      , "dentry_ptr argument is NULL\n" );

    // get parent directory cluster ans local pointer
    parent_inode_ptr = GET_PTR( parent_inode_xp );
    parent_cxy       = GET_CXY( parent_inode_xp );

    // get extended pointer on child inode
    child_inode_xp  = hal_remote_l64( XPTR( parent_cxy , &dentry_ptr->child_xp ) );

    // get child and parent names
    vfs_inode_get_name( parent_inode_xp , parent_name );
    vfs_inode_get_name( child_inode_xp  , child_name );

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

    // get child inode cluster and local pointer
    child_cxy       = GET_CXY( child_inode_xp );
    child_inode_ptr = GET_PTR( child_inode_xp );
 
    // get size from child inode
    new_size = hal_remote_l32( XPTR( child_cxy , &child_inode_ptr->size ) );
 
    // get local and extended pointers on parent directory mapper
    parent_mapper_ptr = hal_remote_lpt( XPTR( parent_cxy , &parent_inode_ptr->mapper ) );
    parent_mapper_xp  = XPTR( parent_cxy , parent_mapper_ptr );

    // get directory entry index from dentry extension
    entry_id = (intptr_t)hal_remote_lpt( XPTR( parent_cxy , &dentry_ptr->extend ) );

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

    // get extended pointers on page descriptor and page base
    page_xp = mapper_get_page( parent_mapper_xp , page_id );     
    base_xp = ppm_page2base( page_xp ); 

    // build extended pointer on directory entry
    entry_xp = base_xp + offset;

    // get current size from directory mapper
    current_size = fatfs_get_remote_record( DIR_FILE_SIZE , entry_xp );

    // update dentry in mapper & device only if required
    if( new_size != current_size )
    {
        // set size field in FAT32 directory entry
        fatfs_set_remote_record( DIR_FILE_SIZE , entry_xp , new_size );

        // synchronously update the modified mapper page on device
        error = fatfs_move_page( page_xp , IOC_SYNC_WRITE );

        if( error )
        { 
            printk("\n[ERROR] in %s : cannot update parent directory <%s> on device\n",
            __FUNCTION__, parent_name );
            return -1;
        }
    }

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

    return 0;

}  // end fatfs_update_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( __FUNCTION__, (mapper != NULL) , "mapper is NULL\n");
    
// TODO handle the detailed flag
assert( __FUNCTION__, (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_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 );   
            ord  = fatfs_get_record( LDIR_ORD , base + offset );   

            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( xptr_t inode_xp )
{
    cxy_t         inode_cxy;      // remote inode cluster
    vfs_inode_t * inode_ptr;      // remote inode local pointer
    mapper_t    * mapper;         // remote inode mapper local pointer
    uint32_t      size;           // remote inode size in bytes
    uint32_t      type;           // remote inode type
    xptr_t        rt_xp;          // extended pointer on mapper radix tree
    uint32_t      npages;         // number of pages in mapper
    uint32_t      page_id;        // current page index in mapper
    xptr_t        page_xp;        // extended pointer on current page
    page_t      * page_ptr;       // local pointer on current page
    uint32_t      flags;          // current page flags
    error_t       error;

// check inode pointer and cluster index
assert( __FUNCTION__, (inode_xp != XPTR_NULL)          , "inode pointer undefined\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( inode_xp , 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

    // get inode cluster and local pointer
    inode_cxy = GET_CXY( inode_xp );
    inode_ptr = GET_PTR( inode_xp );

    //get inode mapper pointer
    mapper    = hal_remote_lpt( XPTR( inode_cxy , &inode_ptr->mapper ) );

assert( __FUNCTION__, (mapper != NULL) , "mapper pointer is NULL\n" );     

    // get inode type and size
    size = hal_remote_l32( XPTR( inode_cxy , &inode_ptr->size ) );
    type = hal_remote_l32( XPTR( inode_cxy , &inode_ptr->type ) );

assert( __FUNCTION__, (type == FILE_TYPE_REG) , "inode is not a file\n" );     

    // compute number of pages
    npages = size >> CONFIG_PPM_PAGE_SHIFT;
    if( size & CONFIG_PPM_PAGE_MASK ) npages++; 
         
    // build pointers on mapper radix tree
    rt_xp = XPTR( inode_cxy , &mapper->rt );

    // scan all pages
    for( page_id = 0 ; page_id < npages ; page_id++ )
    {
        // get page descriptor from mapper
        page_xp = grdxt_remote_lookup( rt_xp , page_id );

        // check all existing pages
        if ( page_xp != XPTR_NULL )
        {
            // get page cluster and local pointer
            page_ptr = GET_PTR( page_xp );

            // get page flags
            flags = hal_remote_l32( XPTR( inode_cxy , &page_ptr->flags ) );
   
            if ( flags & PG_DIRTY )
            {

#if (DEBUG_FATFS_SYNC_INODE & 1)
if( DEBUG_FATFS_SYNC_INODE < cycle )
printk("\n[%s] thread[%x,%x] synchronizes page %d of <%s> mapper to IOC device\n",
__FUNCTION__, page_id, name );
#endif
                // 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>\n",
__FUNCTION__ , this->process->pid, this->trdid, name );
#endif

    return 0;

}  // end fatfs_sync_inode()

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

    fatfs_ctx_t * fatfs_ctx; 
    cxy_t         fat_cxy;
    mapper_t    * mapper_ptr;
    xptr_t        mapper_xp;
    uint32_t      start_page_id;
    uint32_t      found_page_id;
    page_t      * page_ptr;
    xptr_t        page_xp;
    uint32_t      flags;
    error_t       error;

#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
    
    // get FAT cluster
    fat_cxy = CONFIG_VFS_ROOT_CXY;
    
    // get FAT mapper pointers  
    fatfs_ctx  = fs_context[FS_TYPE_FATFS].extend;
    mapper_ptr = fatfs_ctx->fat_mapper;
    mapper_xp  = XPTR( fat_cxy , mapper_ptr );

    // get pointers on remote FAT mapper radix tree
    grdxt_t  * rt_ptr = &mapper_ptr->rt;
    xptr_t     rt_xp  = XPTR( fat_cxy , rt_ptr );

    // initialise page_id
    start_page_id = 0;

    // scan FAT mapper
    while( 1 )
    {
        // get one page
        page_xp = grdxt_remote_get_first( rt_xp , start_page_id , &found_page_id );

        // exit loop when no more page found 
        if ( page_xp != XPTR_NULL ) break;

        // get page flags
        page_ptr = GET_PTR( page_xp );
        flags    = hal_remote_l32( XPTR( fat_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 );
        }

        // update loop variable
        start_page_id = found_page_id + 1;

    }  // 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\n",
__FUNCTION__ , this->process->pid, this->trdid );
#endif

    return 0;

}  // end fatfs_sync_fat()

//////////////////////////////////////////////
error_t fatfs_release_inode( xptr_t inode_xp )
{
    vfs_ctx_t   * vfs_ctx;           // local pointer on VFS context (same in all clusters)
    cxy_t         fat_cxy;           // FAT cluster identifier
    fatfs_ctx_t * fatfs_ctx_ptr;     // local pointer on FATFS context in FAT cluster
    xptr_t        fatfs_ctx_xp;      // extended pointer on FATFS-context in FAT cluster
    mapper_t    * fat_mapper_ptr;    // local pointer on FAT mapper
    xptr_t        fat_mapper_xp;     // extended pointer on FAT mapper
    xptr_t        lock_xp;           // extended pointer on lock protecting FAT.
    xptr_t        first_xp;          // extended pointer on inode extension
    uint32_t      first_cluster_id;  // first cluster index for released inode
    vfs_inode_t * inode_ptr;         // local pointer on target inode
    cxy_t         inode_cxy;         // target inode cluster identifier
    error_t       error;

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

    // get inode cluster and local pointer
    inode_ptr     = GET_PTR( inode_xp );
    inode_cxy     = GET_CXY( inode_xp );

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

// check first cluster index
assert( __FUNCTION__, (first_cluster_id != 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_id %x / cycle %d\n",
__FUNCTION__ , this->process->pid, this->trdid, name, first_cluster_id, cycle );
#endif

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

    // get FAT cluster
    fat_cxy = CONFIG_VFS_ROOT_CXY;

    // get pointers on FATFS context in FAT cluster 
    fatfs_ctx_ptr = hal_remote_lpt( XPTR( fat_cxy , &vfs_ctx->extend ) );
    fatfs_ctx_xp  = XPTR( fat_cxy , fatfs_ctx_ptr ); 

    // get FAT mapper pointers
    fat_mapper_ptr = hal_remote_lpt( XPTR( fat_cxy , &fatfs_ctx_ptr->fat_mapper ) );
	fat_mapper_xp  = XPTR( fat_cxy , fat_mapper_ptr );
    
    // build extended pointer on FAT lock in FAT cluster
    lock_xp = XPTR( fat_cxy , &fatfs_ctx_ptr->lock );

    // take FAT lock in write mode
    remote_rwlock_wr_acquire( lock_xp );

#if (DEBUG_FATFS_RELEASE_INODE & 0x11 == 0x11)
mapper_display_page( fat_mapper_xp , 0 , 4096 );
#endif

    // call the recursive function to release all clusters from FAT mapper
    uint32_t dirty_page_min = 0xFFFFFFFF;
    uint32_t dirty_page_max = 0;

    if ( fatfs_recursive_release( fat_mapper_xp,
                                  fatfs_ctx_xp,
                                  first_cluster_id,
                                  &dirty_page_min,
                                  &dirty_page_max ) )
    {
        printk("\n[ERROR] in %s : cannot update FAT mapper\n", __FUNCTION__ );
        remote_rwlock_wr_release( lock_xp );
        return -1;
    }

#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

#if (DEBUG_FATFS_RELEASE_INODE & 0x11 == 0x11)
mapper_display_page( fat_mapper_xp , 0 , 4096 );
#endif

    // update FAT on IOC device (from FAT mapper)
    error = fatfs_update_ioc_fat( fatfs_ctx_xp,
                                  dirty_page_min,
                                  dirty_page_max );

    if( error )
    {
        printk("\n[ERROR] in %s : cannot update FAT on IOC device\n", __FUNCTION__ );
        remote_rwlock_wr_release( lock_xp );
        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 on IOC device (from FATFS context)
    error = fatfs_update_ioc_fsinfo( fatfs_ctx_xp );

    if( error )
    {
        printk("\n[ERROR] in %s: cannot update FSINFO on IOC device\n", __FUNCTION__ );
        remote_rwlock_wr_release( lock_xp );
        return -1;
    }

#if (DEBUG_FATFS_RELEASE_INODE & 1)
if( DEBUG_FATFS_RELEASE_INODE < cycle )
printk("\n[%s] updated FS_INFO on IOC device for >%s>\n", __FUNCTION__, name );
#endif

    // release FAT lock
    remote_rwlock_wr_release( lock_xp );

#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,
                         ioc_cmd_type_t  cmd_type )
{
    error_t       error = 0;

    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 and 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    buffer_xp  = ppm_page2base( page_xp );
 
    // get inode pointer from mapper
    inode_ptr  = hal_remote_lpt( XPTR( page_cxy , &mapper_ptr->inode ) );

    //////////////////////////////  FAT mapper  /////////////////////////////////////////
    if( inode_ptr == NULL )
    {

#if DEBUG_FATFS_MOVE_PAGE
if( DEBUG_FATFS_MOVE_PAGE < cycle )
printk("\n[%s] thread[%x,%x] enters %s for  page %d in FAT mapper / cycle %d\n",
__FUNCTION__, this->process->pid, this->trdid, dev_ioc_cmd_str(cmd_type), page_id, cycle );
#endif
        // get lba from FATFS context and page_id 
        uint32_t      lba = fatfs_ctx->fat_begin_lba + (page_id << 3);
 
        // access IOC device
        if     (cmd_type == IOC_SYNC_WRITE) error = dev_ioc_sync_write( buffer_xp, lba, 8 );
        else if(cmd_type == IOC_SYNC_READ ) error = dev_ioc_sync_read( buffer_xp, lba, 8 );
        else
        {
            printk("\n[ERROR] in %s : illegal asynchronous FAT access\n", __FUNCTION__ );
        }

        if( error )
        {
            printk("\n[ERROR] in %s : cannot access IOC device\n", __FUNCTION__ );
            return -1;
        }

#if DEBUG_FATFS_MOVE_PAGE
if( DEBUG_FATFS_MOVE_PAGE < cycle )
printk("\n[%s] thread[%x,%x] exit %s for page %d in FAT mapper\n",
__FUNCTION__, this->process->pid, this->trdid, dev_ioc_cmd_str(cmd_type), page_id );
#endif

    }
    /////////////////////////  inode mapper  ////////////////////////////////////////////
    else                       
    {

#if DEBUG_FATFS_MOVE_PAGE
if( DEBUG_FATFS_MOVE_PAGE < cycle )
{
    vfs_inode_get_name( XPTR( page_cxy , inode_ptr ) , name );
    printk("\n[%s] thread[%x,%x] enters %s for page %d in <%s> mapper / cycle %d\n",
    __FUNCTION__, this->process->pid, this->trdid, 
    dev_ioc_cmd_str( cmd_type ), page_id, name, cycle );
}
#endif

        uint32_t  searched_cluster_id;
        uint32_t  first_cluster_id;

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

        // compute searched_cluster_id
        if( page_id == 0 )            // no need to access FAT mapper
        {
            // searched cluster is first cluster
            searched_cluster_id = first_cluster_id;
        }
        else                        // FAT mapper access required
        {
            // scan FAT mapper to get searched_cluster_id
            error = fatfs_get_cluster( 0,                    // first page in mapper
                                       first_cluster_id,
                                       page_id,
                                       &searched_cluster_id );
            if( error )
            {
                printk("\n[ERROR] in %s : cannot get cluster_id\n", __FUNCTION__ );
                return -1;
            }
        }

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

        // access IOC device
        if     (cmd_type == IOC_WRITE     ) error = dev_ioc_write( buffer_xp, lba, 8 );
        else if(cmd_type == IOC_READ      ) error = dev_ioc_read( buffer_xp, lba, 8 );
        else if(cmd_type == IOC_SYNC_READ ) error = dev_ioc_sync_read( buffer_xp, lba, 8 );
        else if(cmd_type == IOC_SYNC_WRITE) error = dev_ioc_sync_write( buffer_xp, lba, 8 );
        else
        {
            printk("\n[ERROR] in %s : illegal cmd_type\n", __FUNCTION__ );
        }

        if( error )
        {
            printk("\n[ERROR] in %s : cannot access device\n", __FUNCTION__ );
            return -1;
        }

#if DEBUG_FATFS_MOVE_PAGE
if( DEBUG_FATFS_MOVE_PAGE < cycle )
{
    printk("\n[%s] thread[%x,%x] exit %s for page %d in <%s> mapper / cluster_id %x\n",
    __FUNCTION__, this->process->pid, this->trdid, 
    dev_ioc_cmd_str( cmd_type ), page_id, name, searched_cluster_id );
}
#endif

#if (DEBUG_FATFS_MOVE_PAGE & 1)
if( DEBUG_FATFS_MOVE_PAGE < cycle )
fatfs_display_fat( searched_cluster_id , 64 );
#endif

    }

    return 0;

}  // end fatfs_move_page()


