//==========================================================================
//
//      fatfs_blib.c
//
//      Block cache and access library  
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
// Copyright (C) 2004, 2005, 2006, 2007 eCosCentric Limited                 
//
// eCos 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; either version 2 or (at your option) any later      
// version.                                                                 
//
// eCos 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 eCos; if not, write to the Free Software Foundation, Inc.,    
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.            
//
// As a special exception, if other files instantiate templates or use      
// macros or inline functions from this file, or you compile this file      
// and link it with other works to produce a work based on this file,       
// this file does not by itself cause the resulting work to be covered by   
// the GNU General Public License. However the source code for this file    
// must still be made available in accordance with section (3) of the GNU   
// General Public License v2.                                               
//
// This exception does not invalidate any other reasons why a work based    
// on this file might be covered by the GNU General Public License.         
// -------------------------------------------                              
// ####ECOSGPLCOPYRIGHTEND####                                              
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):     savin 
// Contributor(s):      nickg,jlarmour
// Date:          2003-08-29
// Description: 
//
//####DESCRIPTIONEND####
//
//==========================================================================

#include <pkgconf/system.h>         // CYGPKG_KERNEL
#include <pkgconf/io_disk.h>        // CYGINT_IO_DISK_ALIGN_BUFS_TO_CACHELINE
#include <cyg/io/io.h>
#include <cyg/infra/cyg_ass.h>      // assertion support
#include <cyg/infra/diag.h>         // diagnostic output

//#include <cyg/kernel/instrmnt.h>

#include <string.h>

//#include <linux/rbtree.h>
//#include <linux/list.h>

#include "fatfs.h"

// --------------------------------------------------------------------

//#define DEBUG 1

#ifdef DEBUG
# define D(_args_)                              \
{                                               \
    cyg_drv_isr_lock();                         \
    diag_printf("%08x: ",cyg_thread_self());    \
    diag_printf _args_;                         \
    cyg_drv_isr_unlock();                       \
                                                \
}
#else
# define D(_args_) CYG_EMPTY_STATEMENT
#endif

#if 0
# define X(_args_)                              \
{                                               \
    cyg_drv_isr_lock();                         \
    diag_printf("%08x: ",cyg_thread_self());    \
    diag_printf _args_;                         \
    cyg_drv_isr_unlock();                       \
                                                \
}
#else
# define X(_args_) CYG_EMPTY_STATEMENT
#endif

#define ALIGN(_x_) (((_x_) + (CYGARC_ALIGNMENT-1)) & ~(CYGARC_ALIGNMENT-1))

#if CYGINT_IO_DISK_ALIGN_BUFS_TO_CACHELINE
# include <cyg/hal/hal_cache.h>
# if defined(HAL_DCACHE_LINE_SIZE) && (HAL_DCACHE_LINE_SIZE > 0)
#  define BLOCK_ALIGNMENT HAL_DCACHE_LINE_SIZE
#  define BLOCK_ALIGN(_x_) (((_x_) + (BLOCK_ALIGNMENT-1)) & ~(BLOCK_ALIGNMENT-1))
#  define BLOCK_ALIGN_ADDR(_x_) BLOCK_ALIGN((CYG_ADDRESS)(_x_))
# endif
#endif

#ifdef CYGIMP_BLOCK_LIB_STATISTICS

#define STAT_INIT(_bl_) do {    \
        bl->stat.n_gets   = 0;  \
        bl->stat.n_reads  = 0;  \
        bl->stat.n_writes = 0;  \
    } while (0)    

#define STAT(_bl_, _group_) \
    ((_bl_)->stat._group_++)

#else // CYGIMP_BLOCK_LIB_STATISTICS

#define STAT_INIT(_bl_)     CYG_EMPTY_STATEMENT
#define STAT(_bl_, _group_) CYG_EMPTY_STATEMENT
    
#endif // not CYGIMP_BLOCK_LIB_STATISTICS
 
// --------------------------------------------------------------------

typedef struct {
    struct list_head  list_node; // list node
    struct rb_node    rb_node;   // red-black tree node
    cyg_uint32        num;       // block number
    cyg_uint32        flags;     // Flag word
    cyg_bool          modified;  // is this block data modified (needs write)
    cyg_uint8         data[0];   // block data
} blib_block_t;

#define BLIB_FLAG_READ          0x01
#define BLIB_FLAG_WRITE         0x02
#define BLIB_FLAG_PEND          0x04

// --------------------------------------------------------------------

static int
io_bread(fatfs_disk_t *disk, void *buf, cyg_uint32 *len, cyg_uint32 pos)
{
    int ret;

    D(("io_bread %d\n",pos));

#ifdef CYGPKG_KERNEL
    cyg_drv_mutex_unlock( disk->lock );
#endif
    
    ret = cyg_io_bread(disk->dev_h, buf, len, pos);

#ifdef CYGPKG_KERNEL
    cyg_drv_mutex_lock( disk->lock );
#endif

    D(("io_bread done %d\n",pos));

    // disk device drivers return -ve numbers to indicate an error, with
    // the absolute value corresponding to errno. The fileio layer expects
    // file systems to return +ve numbers. Higher levels of code within
    // fatfs consistently use the +ve number convention, and it is the block
    // layer that performs the translation.
    return (ret < 0) ? -ret : ret;
}

static int
io_bwrite(fatfs_disk_t *disk, const void *buf, cyg_uint32 *len, cyg_uint32 pos)
{
    int ret;

    D(("io_bwrite %d\n",pos));

#ifdef CYGPKG_KERNEL
    cyg_drv_mutex_unlock( disk->lock );
#endif
    
    ret = cyg_io_bwrite(disk->dev_h, buf, len, pos);

#ifdef CYGPKG_KERNEL
    cyg_drv_mutex_lock( disk->lock );
#endif

    D(("io_bwrite done %d\n",pos));
    
    return (ret < 0) ? -ret : ret;
}

static void io_pend( fatfs_disk_t *disk, int pos )
{
    D(("io_pend %d\n",pos));

    // I believe this should never even be called in a non-kernel context anyway,
    // so perhaps we should assert? -Jifl
#ifdef CYGPKG_KERNEL
    cyg_drv_cond_wait( &disk->pending );
#endif
}

static void io_unpend( fatfs_disk_t *disk, int pos )
{
    D(("io_unpend %d\n",pos));
    
    // I believe this should never even be called in a non-kernel context anyway,
    // so perhaps we should assert? -Jifl
#ifdef CYGPKG_KERNEL
    cyg_drv_cond_broadcast( &disk->pending );
#endif
}

// --------------------------------------------------------------------

static blib_block_t *
rb_find_block(fatfs_blib_t *bl, cyg_uint32 num)
{
    struct rb_node *node  = bl->rb_root.rb_node;
    blib_block_t   *block = NULL;

//    X(("find %d\n",num));
    while (NULL != node)
    {
        block = rb_entry(node, blib_block_t, rb_node);

        if (block->num == num)
        {
//            X(("found %08x %d\n",block,block->num));            
            return block;
        }

        node = (block->num > num) ? node->rb_left : node->rb_right;
    }
    return NULL;
}

static void
rb_add_block(fatfs_blib_t *bl, blib_block_t *block)
{
    struct rb_node *node = bl->rb_root.rb_node;    

//    X(("add %08x %d\n",block,block->num));
    if (NULL == node)
    {
        rb_link_node(&block->rb_node, NULL, &bl->rb_root.rb_node);
    }
    else
    {
        struct rb_node **link;
        blib_block_t    *b = NULL;
        
        while (NULL != node)
        {
            b = rb_entry(node, blib_block_t, rb_node);

if( b->num == block->num )
{
    X(("rb_add_block %08x[%d] %08x[%d]\n",b,b->num,block,block->num));
    for(;;);
}
                
            CYG_ASSERTC(b->num != block->num);

            link = (b->num > block->num) ? &node->rb_left : &node->rb_right;
            node = *link;
        }
        rb_link_node(&block->rb_node, &b->rb_node, link);
    }
    rb_insert_color(&block->rb_node, &bl->rb_root);
}

static __inline__ void
rb_del_block(fatfs_blib_t *bl, blib_block_t *block)
{
    rb_erase(&block->rb_node, &bl->rb_root);
}

// --------------------------------------------------------------------

static __inline__ void
list_add_block(fatfs_blib_t *bl, blib_block_t *block)
{
    list_add(&block->list_node, &bl->list_head);
}

static __inline__ void
list_del_block(fatfs_blib_t *bl, blib_block_t *block)
{
    list_del(&block->list_node);
}

static __inline__ void
list_add_block_end(fatfs_blib_t *bl, blib_block_t *block)
{
    list_add_tail(&block->list_node, &bl->list_head);
}

static __inline__ blib_block_t *
list_get_first_block(fatfs_blib_t *bl)
{
    return(list_entry(bl->list_head.next, blib_block_t, list_node));
}

static __inline__ blib_block_t *
list_get_last_block(fatfs_blib_t *bl)
{
    return(list_entry(bl->list_head.prev, blib_block_t, list_node));
}

static void
list_move_block_to_head(fatfs_blib_t *bl, blib_block_t *block)
{
    list_del(&block->list_node);
    list_add(&block->list_node, &bl->list_head);
}

// --------------------------------------------------------------------

static __inline__ void
free_block(fatfs_blib_t *bl, blib_block_t *block)
{
    list_add(&block->list_node, &bl->free_list_head);
}

static __inline__ blib_block_t *
alloc_block(fatfs_blib_t *bl)
{
    if ( !list_empty(&bl->free_list_head) )
    {
        blib_block_t *new;

        new = list_entry(bl->free_list_head.next, blib_block_t, list_node);
        list_del(bl->free_list_head.next);

        new->num = 0;
        new->flags = 0;
        new->modified = false;
        
        return new; 
    }
    else
        return NULL;
}

static void
init_block_mem_pool(fatfs_blib_t *bl)
{
    cyg_uint8  *block_mem;
    cyg_uint32  avail_mem_size, block_mem_size;
#ifdef BLOCK_ALIGN_ADDR
# define ALIGNED_BLIB_BLOCK_SIZE    BLOCK_ALIGN(sizeof(blib_block_t))
# define ALIGN_ADJUST               (ALIGNED_BLIB_BLOCK_SIZE - sizeof(blib_block_t))
#else
# define ALIGN_ADJUST               0
#endif
    
    INIT_LIST_HEAD(&bl->free_list_head);    

    block_mem      = bl->mem_base;
    avail_mem_size = bl->mem_size;
#ifndef BLOCK_ALIGN_ADDR
    block_mem_size = ALIGN(sizeof(blib_block_t) + bl->block_size);
#else
    // This means that block data has stricter alignment constraints
    // than just the ALIGN macro supplies.
    // Aligning block_mem to the stricter alignment does not work by itself,
    // as it's the data field inside the blib_block_t that matters.

    // The layout is such that the block data must start and end at an
    // aligned boundary (with these stricter block alignment constraints).
    // So there may be a gap - padding for alignment - between the end of
    // one block data, and the start of the next blib structure.
    // For this to work, the block size must be a multiple of the alignment,
    // so we enforce this.
    CYG_ASSERT((bl->block_size % BLOCK_ALIGNMENT) == 0,
               "block size not a multiple of needed block alignment");
    // The start has to be the same as with subsequent blib+blocks, i.e.
    // aligned to the block alignment.
    block_mem = (cyg_uint8*)BLOCK_ALIGN_ADDR(block_mem);
    // Consequently:
    avail_mem_size -= block_mem - bl->mem_base;

    // We determined align_adjust above to be the amount of padding that
    // must precede a blib structure to ensure it will end aligned on a
    // BLOCK_ALIGNMENT boundary. We calculate block_mem_size
    // in line with that wholee allocation size - the padding, structure
    // and block data.
    block_mem_size = ALIGN_ADJUST + sizeof(blib_block_t) + bl->block_size;
#endif    

    while (avail_mem_size >= block_mem_size)
    {
        blib_block_t *block = (blib_block_t *)(block_mem+ALIGN_ADJUST);
        
        list_add(&block->list_node, &bl->free_list_head);
        
        block_mem      += block_mem_size;
        avail_mem_size -= block_mem_size;
    }
}

// --------------------------------------------------------------------

static cyg_uint32
get_val_log2(cyg_uint32 val)
{
    cyg_uint32 i, log2;

    i = val;
    log2 = 0;
    while (0 == (i & 1))
    {
        i >>= 1;
        log2++;
    }
    if (i != 1)
        return 0;
    else
        return log2;
}

static int 
blib_sync_block(fatfs_blib_t *bl, blib_block_t *block)
{
    int ret = ENOERR;

    // We only need to sync this block if it has been modified and is
    // not already being written to disk by some other thread.
    if (block->modified && ((block->flags & BLIB_FLAG_WRITE)==0) )
    {
        cyg_uint32 len = bl->block_size/bl->sector_size;

        X(("blib writing block=%d\n", block->num));
 
        STAT(bl, n_writes);

        block->flags |= BLIB_FLAG_WRITE;
        bl->io_requests++;
        ret = io_bwrite(bl->disk, (void *)block->data, &len, block->num*len);
        bl->io_requests--;
        block->flags &= ~BLIB_FLAG_WRITE;        
        if( block->flags & BLIB_FLAG_PEND )
        {
            block->flags &= ~BLIB_FLAG_PEND;
//            CYG_INSTRUMENT_USER(0x11,block,block->num);
            io_unpend( bl->disk, block->num );
        }
        
        block->modified = false;
        bl->modified_blocks--;
        CYG_ASSERT( bl->modified_blocks >= 0, "Negative modified_blocks" );
        if( bl->modified_blocks == 0 )
        {
            // Make callback when filesystem becomes safe.
            bl->disk->callback.callback( CYG_FS_CALLBACK_SAFE, bl->disk->callback.data );
        }
    }
    return ret;
}

static int 
blib_sync(fatfs_blib_t *bl)
{
    struct list_head *node = bl->list_head.next;
    blib_block_t     *block;
    int ret = ENOERR;
    int result = ENOERR;
 
    D(("blib cache sync\n")); 

    while (node != &bl->list_head)
    {
        block = list_entry(node, blib_block_t, list_node);

        ret = blib_sync_block(bl, block);
        if (ENOERR != ret)
            result = ret;
        
        node = node->next;
    }
    return result;
}

static int 
blib_get_block(fatfs_blib_t    *bl, 
               cyg_uint32      num, 
               cyg_bool        read_data,
               blib_block_t  **dblock)
{
    blib_block_t *block = NULL;
    int ret = ENOERR;
    cyg_uint32 len;
 
    D(("blib get block=%d\n", num)); 
    
    STAT(bl, n_gets);

    do
    {
        
        // first check if the most recently used block is the requested block,
        // this can improve performance when using byte access functions
        if (!list_empty(&bl->list_head))
        {
            blib_block_t *first_block = list_get_first_block(bl);
            if (first_block->num == num)
                block = first_block;
            else 
                block = rb_find_block(bl, num);
        }

//        if( block )
//            X(("block %08x %d\n",block,block->num));
        
        if( NULL != block && block->flags != 0 )
        {
            X(("pend %08x %d\n",block,block->num));
            block->flags |= BLIB_FLAG_PEND;
//            CYG_INSTRUMENT_USER(0x12,block,block->num);            
            io_pend( bl->disk, block->num );
//            CYG_INSTRUMENT_USER(0x14,block,block->num);
            
            // Now we must go around and look again for the block. The
            // block may have been discarded while we slept.

            block = NULL;

            continue;
        }

        if (NULL != block)
        {
            D(("blib block=%d found in cache\n", num)); 

            list_move_block_to_head(bl, block);
            *dblock = block;
            return ret;
        }

        D(("blib block=%d NOT found in cache\n", num)); 

        block = alloc_block(bl);

        if (NULL == block)
        {
            CYG_ASSERTC(!list_empty(&bl->list_head));

            block = list_get_last_block(bl);

            // Remove the block from the LRU list so it is not found
            // and reused by a different thread while we are syncing
            // it. Leave it in the RB tree, however, so that explicit
            // lookups still succeed.

            list_del_block(bl, block);

            // If the block is in mid-transfer, rotate it to the head
            // of the list and look for another one.
            
            if( block->flags != 0 )
            {
                list_add_block(bl, block);
                block = NULL;
                continue;
            }

            X(("blib reusing block %08x[%d] for %d\n", block, block->num,num)); 

            if( block->modified )
            {
                // Evict the block to the device medium.
                
                ret = blib_sync_block(bl, block);
                if (ENOERR != ret)
                    return ret;

                // Add the block back at the tail of the list, so it
                // will be the first to be chosen for reuse. It is now
                // unmodified so we should not come back here a second
                // time.
            
                list_add_block_end( bl, block );

                // Loop back to the top, if we were away for a while
                // during the blib_sync_block() call then some other
                // thread may have already fetched the block we want, so
                // go look again.

                block = NULL;
                
                continue;
            }

            // If we come here we have a suitable candidate for reuse
            // in our hand. Remove it from the RB tree and drop
            // through to reuse it.
            
            rb_del_block(bl, block);
            
        }

        break;
        
    } while(1);

    CYG_ASSERT(!block->modified, "Reusing modified block" );
    
    block->num      = num;
    block->flags    = 0;
    block->modified = false;

    rb_add_block(bl, block);
    list_add_block(bl, block);
    
    if (read_data)
    {
        X(("blib reading block=%d\n", block->num)); 

        STAT(bl, n_reads);

        len = bl->block_size/bl->sector_size;
        block->flags |= BLIB_FLAG_READ;
        bl->io_requests++;
        ret = io_bread(bl->disk, (void *)block->data, &len, block->num*len);
//        CYG_INSTRUMENT_USER(0x15,block,block->num);
        bl->io_requests--;
        block->flags &= ~BLIB_FLAG_READ;
        if( block->flags & BLIB_FLAG_PEND )
        {
            block->flags &= ~BLIB_FLAG_PEND;
//            CYG_INSTRUMENT_USER(0x13,block,block->num);
            io_unpend( bl->disk, block->num );
            X(("unpend %08x %d\n",block,block->num));            
        }
        
        if (ENOERR != ret)
        {
            list_del_block(bl, block);
            rb_del_block(bl, block);
            free_block(bl, block);
            return ret;
        }
    }
    
    *dblock = block;
    return ret;
}

static int 
blib_free_cache(fatfs_blib_t *bl, cyg_bool force)
{
    int ret = ENOERR;

    if( !force )
    {
        ret = blib_sync(bl);
        CYG_ASSERT( bl->modified_blocks == 0, "Modified blocks still exist" );
    }
    return ret;
}

static int
blib_init_cache(fatfs_blib_t *bl,
                fatfs_disk_t *disk,                
                void         *mem_base,
                cyg_uint32    mem_size,
                cyg_uint32    block_size,
                cyg_uint32    sector_size,
                cyg_uint32    flags,                
                cyg_bool      reinit)
{
    int ret = ENOERR;
    
    if (reinit)
    {
        ret = blib_free_cache(bl, false); 
        if (ENOERR != ret)
            return ret;
    }
    
    bl->rb_root = RB_ROOT;
    INIT_LIST_HEAD(&bl->list_head);

    bl->disk       = disk;
    
    bl->mem_base   = mem_base;
    bl->mem_size   = mem_size;    
    bl->block_size = block_size;
    bl->sector_size = sector_size;
    bl->flags      = flags;
    bl->io_requests = 0;
    bl->modified_blocks = 0;
    
    bl->block_size_log2 = get_val_log2(block_size);
    if (0 == bl->block_size_log2)
        return EINVAL;

    init_block_mem_pool(bl);

    STAT_INIT(bl);

    return ret;
}


// --------------------------------------------------------------------

int
fatfs_blib_create(fatfs_disk_t       *disk,                
                  void               *mem_base,
                  cyg_uint32          mem_size,
                  cyg_uint32          block_size,
                  cyg_uint32          sector_size,
                  cyg_uint32          flags,                
                  fatfs_blib_t       *bl)
{
    int ret;
    
    ret = blib_init_cache(bl, disk, mem_base, mem_size, block_size, sector_size, flags, false); 
    return ret; 
}

int 
fatfs_blib_io_create(fatfs_disk_t       *disk,
                     void               *mem_base,
                     cyg_uint32          mem_size,
                     cyg_uint32          block_size,
                     cyg_uint32          sector_size,
                     cyg_uint32          flags,
                     fatfs_blib_t       *bl)
{
    return fatfs_blib_create(disk, mem_base, mem_size, block_size, sector_size, flags, bl);
}

int
fatfs_blib_delete(fatfs_blib_t *bl, cyg_bool force)
{
    while( bl->io_requests != 0 )
    {
#ifdef CYGPKG_KERNEL
        cyg_drv_mutex_unlock( bl->disk->lock );
    
        cyg_thread_delay(10);

        cyg_drv_mutex_lock( bl->disk->lock );
#endif
    }
    
    return blib_free_cache(bl, force);
}

int
fatfs_blib_read(fatfs_blib_t *bl,
                void         *buf,
                cyg_uint32   *len,
                cyg_uint32    bnum,
                cyg_uint32    pos)
{
    blib_block_t *block;
    cyg_uint8 *bbuf = buf;
    cyg_int32  size = *len;
    Cyg_ErrNo  ret  = ENOERR;

    size = *len;

    if (pos >= bl->block_size)
    {
        bnum += pos >> bl->block_size_log2;
        pos   = pos & (bl->block_size - 1);
    }
        
    D(("blib read len=%d pos=%d bnum=%d\n", *len, pos, bnum));

    while (size > 0)
    {
        int csize;

        if ((size + pos) > bl->block_size)
            csize = bl->block_size - pos;
        else
            csize = size;

        ret = blib_get_block(bl, bnum, true, &block);
        if (ENOERR != ret)
            break;

        X(("blread  %08x %4d[%3d](%3d) -> %08x\n",block,block->num,pos,csize,bbuf));
        memcpy((void *)bbuf, (void *)(block->data+pos), csize);

        bbuf += csize;
        size -= csize;
        pos   = 0;
        bnum++;
    }
    *len -= size;
    return ret;
}

int
fatfs_blib_write(fatfs_blib_t *bl,
                 const void   *buf,
                 cyg_uint32   *len,
                 cyg_uint32    bnum,
                 cyg_uint32    pos)
{
    blib_block_t *block;
    cyg_uint8 *bbuf = (cyg_uint8 * const) buf;
    cyg_int32  size = *len;
    Cyg_ErrNo  ret  = ENOERR;

    size = *len;

    if (pos >= bl->block_size)
    {
        bnum += pos >> bl->block_size_log2;
        pos   = pos & (bl->block_size - 1);
    }
        
    D(("blib write len=%d pos=%d bnum=%d\n", *len, pos, bnum));

    while (size > 0)
    {
        int csize;

        if ((size + pos) > bl->block_size)
            csize = bl->block_size - pos;
        else
            csize = size;

        if (0 == pos && csize == bl->block_size)
            ret = blib_get_block(bl, bnum, false, &block);
        else
            ret = blib_get_block(bl, bnum, true, &block);
        if (ENOERR != ret)
            break;

        X(("blwrite %08x %4d[%3d](%3d) <- %08x\n",block,block->num,pos,csize,bbuf));        
        memcpy((void *)(block->data+pos), (void *)bbuf, csize);

        if( !(bl->flags & FATFS_FLAGS_READONLY) )
        {
            if( !block->modified )
            {
                bl->modified_blocks++;
                CYG_ASSERT( bl->modified_blocks > 0, "Negative modified_blocks" );
                if( bl->modified_blocks == 1 )
                {
                    // Make callback when the filesystem first becomes unsafe.
                    bl->disk->callback.callback( CYG_FS_CALLBACK_UNSAFE, bl->disk->callback.data );
                }
            }
            block->modified = true;

            if( (bl->flags & FATFS_FLAGS_WRITEDATA) && (block->num >= bl->disk->fat_data_pos) )
            {
                // If the filesystem has been mounted with writedata
                // mode then data blocks are written directly to
                // memory but FAT blocks wait for eviction or for the
                // file to be closed.
                
                blib_sync_block( bl, block );                
            }
            else if( bl->flags & FATFS_FLAGS_WRITETHROUGH )
            {
                // If the filesystem has been mounted with
                // write-through mode then write this block out here.
                
                blib_sync_block( bl, block );
                CYG_ASSERT( bl->modified_blocks == 0, "Modified blocks still exist" );
            }
        }
        
        bbuf += csize;
        size -= csize;
        pos   = 0;
        bnum++;
    }
    *len -= size;
    return ret;
}

int
fatfs_blib_sync(fatfs_blib_t *bl)
{
    return blib_sync(bl);
}

int
fatfs_blib_sync_block(fatfs_blib_t *bl, cyg_uint32 num)
{
    blib_block_t *block = rb_find_block(bl, num);
    
    if (NULL != block)
        return blib_sync_block(bl, block);
    else
        return EINVAL;
}

int
fatfs_blib_flush(fatfs_blib_t *bl)
{
    return blib_init_cache(bl, bl->disk, bl->mem_base,  bl->mem_size,
                           bl->block_size, bl->sector_size, bl->flags, true);
}

int
fatfs_blib_set_block_size(fatfs_blib_t *bl, cyg_uint32 block_size)
{
    return blib_init_cache(bl, bl->disk, bl->mem_base, bl->mem_size, 
                           block_size, bl->sector_size, bl->flags, true); 
}    

int
fatfs_blib_get_stat(fatfs_blib_t *bl, fatfs_blib_stat_t *stat)
{
#ifdef CYGIMP_BLOCK_LIB_STATISTICS
    *stat = bl->stat;
    return ENOERR;
#else
    stat->n_gets   = 0;
    stat->n_reads  = 0;
    stat->n_writes = 0;
    return EINVAL;
#endif
}

// --------------------------------------------------------------------
// EOF fatfs_blib.c
