/*
 *  Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
 *
 *  This program 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
 *  of the License, or (at your option) any later version.
 *
 *  This program 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 this program; if not, see <http://www.gnu.org/licenses/>.
 */

/************************************************************************/
/*                                                                      */
/*  PROJECT : exFAT & FAT12/16/32 File System                           */
/*  FILE    : cache.c                                                   */
/*  PURPOSE : sdFAT Cache Manager                                       */
/*            (FAT Cache & Buffer Cache)                                */
/*                                                                      */
/*----------------------------------------------------------------------*/
/*  NOTES                                                               */
/*                                                                      */
/*                                                                      */
/************************************************************************/

#include <linux/swap.h> /* for mark_page_accessed() */
#include <asm/unaligned.h>

#include "sdfat.h"
#include "core.h"

#define DEBUG_HASH_LIST
#define DEBUG_HASH_PREV	(0xAAAA5555)
#define DEBUG_HASH_NEXT	(0x5555AAAA)

/*----------------------------------------------------------------------*/
/*  Global Variable Definitions                                         */
/*----------------------------------------------------------------------*/
/* All buffer structures are protected w/ fsi->v_sem */

/*----------------------------------------------------------------------*/
/*  Local Variable Definitions                                          */
/*----------------------------------------------------------------------*/
#define LOCKBIT         (0x01)
#define DIRTYBIT        (0x02)
#define KEEPBIT         (0x04)

/*----------------------------------------------------------------------*/
/*  Cache handling function declarations                                */
/*----------------------------------------------------------------------*/
static cache_ent_t *__fcache_find(struct super_block *sb, u64 sec);
static cache_ent_t *__fcache_get(struct super_block *sb);
static void __fcache_insert_hash(struct super_block *sb, cache_ent_t *bp);
static void __fcache_remove_hash(cache_ent_t *bp);

static cache_ent_t *__dcache_find(struct super_block *sb, u64 sec);
static cache_ent_t *__dcache_get(struct super_block *sb);
static void __dcache_insert_hash(struct super_block *sb, cache_ent_t *bp);
static void __dcache_remove_hash(cache_ent_t *bp);

/*----------------------------------------------------------------------*/
/*  Static functions                                                    */
/*----------------------------------------------------------------------*/
static void push_to_mru(cache_ent_t *bp, cache_ent_t *list)
{
	bp->next = list->next;
	bp->prev = list;
	list->next->prev = bp;
	list->next = bp;
}

static void push_to_lru(cache_ent_t *bp, cache_ent_t *list)
{
	bp->prev = list->prev;
	bp->next = list;
	list->prev->next = bp;
	list->prev = bp;
}

static void move_to_mru(cache_ent_t *bp, cache_ent_t *list)
{
	bp->prev->next = bp->next;
	bp->next->prev = bp->prev;
	push_to_mru(bp, list);
}

static void move_to_lru(cache_ent_t *bp, cache_ent_t *list)
{
	bp->prev->next = bp->next;
	bp->next->prev = bp->prev;
	push_to_lru(bp, list);
}

static inline s32 __check_hash_valid(cache_ent_t *bp)
{
#ifdef DEBUG_HASH_LIST
	if ((bp->hash.next == (cache_ent_t *)DEBUG_HASH_NEXT) ||
		(bp->hash.prev == (cache_ent_t *)DEBUG_HASH_PREV)) {
		return -EINVAL;
	}
#endif
	if ((bp->hash.next == bp) || (bp->hash.prev == bp))
		return -EINVAL;

	return 0;
}

static inline void __remove_from_hash(cache_ent_t *bp)
{
	(bp->hash.prev)->hash.next = bp->hash.next;
	(bp->hash.next)->hash.prev = bp->hash.prev;
	bp->hash.next = bp;
	bp->hash.prev = bp;
#ifdef DEBUG_HASH_LIST
	bp->hash.next = (cache_ent_t *)DEBUG_HASH_NEXT;
	bp->hash.prev = (cache_ent_t *)DEBUG_HASH_PREV;
#endif
}

/* Do FAT mirroring (don't sync)
 * sec: sector No. in FAT1
 * bh:  bh of sec.
 */
static inline s32 __fat_copy(struct super_block *sb, u64 sec, struct buffer_head *bh, int sync)
{
#ifdef CONFIG_SDFAT_FAT_MIRRORING
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
	u64 sec2;

	if (fsi->FAT2_start_sector != fsi->FAT1_start_sector) {
		sec2 = sec - fsi->FAT1_start_sector + fsi->FAT2_start_sector;
		BUG_ON(sec2 != (sec + (u64)fsi->num_FAT_sectors));

		MMSG("BD: fat mirroring (%llu in FAT1, %llu in FAT2)\n", sec, sec2);
		if (write_sect(sb, sec2, bh, sync))
			return -EIO;
	}
#else
	/* DO NOTHING */
#endif
	return 0;
} /* end of __fat_copy */

/*
 * returns 1, if bp is flushed
 * returns 0, if bp is not dirty
 * returns -1, if error occurs
 */
static s32 __fcache_ent_flush(struct super_block *sb, cache_ent_t *bp, u32 sync)
{
	if (!(bp->flag & DIRTYBIT))
		return 0;
#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY
	// Make buffer dirty (XXX: Naive impl.)
	if (write_sect(sb, bp->sec, bp->bh, 0))
		return -EIO;

	if (__fat_copy(sb, bp->sec, bp->bh, 0))
		return -EIO;
#endif
	bp->flag &= ~(DIRTYBIT);

	if (sync)
		sync_dirty_buffer(bp->bh);

	return 1;
}

static s32 __fcache_ent_discard(struct super_block *sb, cache_ent_t *bp)
{
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	__fcache_remove_hash(bp);
	bp->sec = ~0;
	bp->flag = 0;

	if (bp->bh) {
		__brelse(bp->bh);
		bp->bh = NULL;
	}
	move_to_lru(bp, &fsi->fcache.lru_list);
	return 0;
}

u8 *fcache_getblk(struct super_block *sb, u64 sec)
{
	cache_ent_t *bp;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
	u32 page_ra_count = FCACHE_MAX_RA_SIZE >> sb->s_blocksize_bits;

	bp = __fcache_find(sb, sec);
	if (bp) {
		if (bdev_check_bdi_valid(sb)) {
			__fcache_ent_flush(sb, bp, 0);
			__fcache_ent_discard(sb, bp);
			return NULL;
		}
		move_to_mru(bp, &fsi->fcache.lru_list);

		if (likely(bp->bh->b_data))
			return bp->bh->b_data;

		sdfat_msg(sb, KERN_ERR,
			"%s: no b_data (flag:0x%02x, sect:%llu), %s",
			__func__, bp->flag, sec, bp->flag ? "EIO" : ",retry");

		if (bp->flag)
			return NULL;

		__fcache_ent_discard(sb, bp);
	}

	bp = __fcache_get(sb);
	if (!__check_hash_valid(bp))
		__fcache_remove_hash(bp);

	bp->sec = sec;
	bp->flag = 0;
	__fcache_insert_hash(sb, bp);

	/* Naive FAT read-ahead (increase I/O unit to page_ra_count) */
	if ((sec & (page_ra_count - 1)) == 0)
		bdev_readahead(sb, sec, (u64)page_ra_count);

	/*
	 * patch 1.2.4 : buffer_head null pointer exception problem.
	 *
	 * When read_sect is failed, fcache should be moved to
	 * EMPTY hash_list and the first of lru_list.
	 */
	if (read_sect(sb, sec, &(bp->bh), 1)) {
		__fcache_ent_discard(sb, bp);
		return NULL;
	}

	if (likely(bp->bh->b_data))
		return bp->bh->b_data;

	sdfat_msg(sb, KERN_ERR,
		"%s: no b_data after read (flag:0x%02x, sect:%llu)",
		__func__, bp->flag, sec);

	return NULL;
}

static inline int __mark_delayed_dirty(struct super_block *sb, cache_ent_t *bp)
{
#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	if (fsi->vol_type == EXFAT)
		return -ENOTSUPP;

	bp->flag |= DIRTYBIT;
	return 0;
#else
	return -ENOTSUPP;
#endif
}



s32 fcache_modify(struct super_block *sb, u64 sec)
{
	cache_ent_t *bp;

	bp = __fcache_find(sb, sec);
	if (!bp) {
		sdfat_fs_error(sb, "Can`t find fcache (sec 0x%016llx)", sec);
		return -EIO;
	}

	if (!__mark_delayed_dirty(sb, bp))
		return 0;

	if (write_sect(sb, sec, bp->bh, 0))
		return -EIO;

	if (__fat_copy(sb, sec, bp->bh, 0))
		return -EIO;

	return 0;
}

/*======================================================================*/
/*  Cache Initialization Functions                                      */
/*======================================================================*/
s32 meta_cache_init(struct super_block *sb)
{
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
	s32 i;

	/* LRU list */
	fsi->fcache.lru_list.next = &fsi->fcache.lru_list;
	fsi->fcache.lru_list.prev = fsi->fcache.lru_list.next;

	for (i = 0; i < FAT_CACHE_SIZE; i++) {
		fsi->fcache.pool[i].sec = ~0;
		fsi->fcache.pool[i].flag = 0;
		fsi->fcache.pool[i].bh = NULL;
		fsi->fcache.pool[i].prev = NULL;
		fsi->fcache.pool[i].next = NULL;
		push_to_mru(&(fsi->fcache.pool[i]), &fsi->fcache.lru_list);
	}

	fsi->dcache.lru_list.next = &fsi->dcache.lru_list;
	fsi->dcache.lru_list.prev = fsi->dcache.lru_list.next;
	fsi->dcache.keep_list.next = &fsi->dcache.keep_list;
	fsi->dcache.keep_list.prev = fsi->dcache.keep_list.next;

	// Initially, all the BUF_CACHEs are in the LRU list
	for (i = 0; i < BUF_CACHE_SIZE; i++) {
		fsi->dcache.pool[i].sec = ~0;
		fsi->dcache.pool[i].flag = 0;
		fsi->dcache.pool[i].bh = NULL;
		fsi->dcache.pool[i].prev = NULL;
		fsi->dcache.pool[i].next = NULL;
		push_to_mru(&(fsi->dcache.pool[i]), &fsi->dcache.lru_list);
	}

	/* HASH list */
	for (i = 0; i < FAT_CACHE_HASH_SIZE; i++) {
		fsi->fcache.hash_list[i].sec = ~0;
		fsi->fcache.hash_list[i].hash.next = &(fsi->fcache.hash_list[i]);
;
		fsi->fcache.hash_list[i].hash.prev = fsi->fcache.hash_list[i].hash.next;
	}

	for (i = 0; i < FAT_CACHE_SIZE; i++)
		__fcache_insert_hash(sb, &(fsi->fcache.pool[i]));

	for (i = 0; i < BUF_CACHE_HASH_SIZE; i++) {
		fsi->dcache.hash_list[i].sec = ~0;
		fsi->dcache.hash_list[i].hash.next = &(fsi->dcache.hash_list[i]);

		fsi->dcache.hash_list[i].hash.prev = fsi->dcache.hash_list[i].hash.next;
	}

	for (i = 0; i < BUF_CACHE_SIZE; i++)
		__dcache_insert_hash(sb, &(fsi->dcache.pool[i]));

	return 0;
}

s32 meta_cache_shutdown(struct super_block *sb)
{
	return 0;
}

/*======================================================================*/
/*  FAT Read/Write Functions                                            */
/*======================================================================*/
s32 fcache_release_all(struct super_block *sb)
{
	s32 ret = 0;
	cache_ent_t *bp;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
	s32 dirtycnt = 0;

	bp = fsi->fcache.lru_list.next;
	while (bp != &fsi->fcache.lru_list) {
		s32 ret_tmp = __fcache_ent_flush(sb, bp, 0);

		if (ret_tmp < 0)
			ret = ret_tmp;
		else
			dirtycnt += ret_tmp;

		bp->sec = ~0;
		bp->flag = 0;

		if (bp->bh) {
			__brelse(bp->bh);
			bp->bh = NULL;
		}
		bp = bp->next;
	}

	DMSG("BD:Release / dirty fat cache: %d (err:%d)\n", dirtycnt, ret);
	return ret;
}


/* internal DIRTYBIT marked => bh dirty */
s32 fcache_flush(struct super_block *sb, u32 sync)
{
	s32 ret = 0;
	cache_ent_t *bp;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
	s32 dirtycnt = 0;

	bp = fsi->fcache.lru_list.next;
	while (bp != &fsi->fcache.lru_list) {
		ret = __fcache_ent_flush(sb, bp, sync);
		if (ret < 0)
			break;

		dirtycnt += ret;
		bp = bp->next;
	}

	MMSG("BD: flush / dirty fat cache: %d (err:%d)\n", dirtycnt, ret);
	return ret;
}

static cache_ent_t *__fcache_find(struct super_block *sb, u64 sec)
{
	s32 off;
	cache_ent_t *bp, *hp;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	off = (sec + (sec >> fsi->sect_per_clus_bits)) & (FAT_CACHE_HASH_SIZE - 1);
	hp = &(fsi->fcache.hash_list[off]);
	for (bp = hp->hash.next; bp != hp; bp = bp->hash.next) {
		if (bp->sec == sec) {
			/*
			 * patch 1.2.4 : for debugging
			 */
			WARN(!bp->bh, "[SDFAT] fcache has no bh. "
					  "It will make system panic.\n");

			touch_buffer(bp->bh);
			return bp;
		}
	}
	return NULL;
}

static cache_ent_t *__fcache_get(struct super_block *sb)
{
	cache_ent_t *bp;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	bp = fsi->fcache.lru_list.prev;
#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY
	while (bp->flag & DIRTYBIT) {
		cache_ent_t *bp_prev = bp->prev;

		bp = bp_prev;
		if (bp == &fsi->fcache.lru_list) {
			DMSG("BD: fat cache flooding\n");
			fcache_flush(sb, 0);	// flush all dirty FAT caches
			bp = fsi->fcache.lru_list.prev;
		}
	}
#endif
//	if (bp->flag & DIRTYBIT)
//       sync_dirty_buffer(bp->bh);

	move_to_mru(bp, &fsi->fcache.lru_list);
	return bp;
}

static void __fcache_insert_hash(struct super_block *sb, cache_ent_t *bp)
{
	s32 off;
	cache_ent_t *hp;
	FS_INFO_T *fsi;

	fsi = &(SDFAT_SB(sb)->fsi);
	off = (bp->sec + (bp->sec >> fsi->sect_per_clus_bits)) & (FAT_CACHE_HASH_SIZE-1);

	hp = &(fsi->fcache.hash_list[off]);
	bp->hash.next = hp->hash.next;
	bp->hash.prev = hp;
	hp->hash.next->hash.prev = bp;
	hp->hash.next = bp;
}


static void __fcache_remove_hash(cache_ent_t *bp)
{
#ifdef DEBUG_HASH_LIST
	if ((bp->hash.next == (cache_ent_t *)DEBUG_HASH_NEXT) ||
		(bp->hash.prev == (cache_ent_t *)DEBUG_HASH_PREV)) {
		EMSG("%s: FATAL: tried to remove already-removed-cache-entry"
			"(bp:%p)\n", __func__, bp);
		return;
	}
#endif
	WARN_ON(bp->flag & DIRTYBIT);
	__remove_from_hash(bp);
}

/*======================================================================*/
/*  Buffer Read/Write Functions                                         */
/*======================================================================*/
/* Read-ahead a cluster */
s32 dcache_readahead(struct super_block *sb, u64 sec)
{
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
	struct buffer_head *bh;
	u32 max_ra_count = DCACHE_MAX_RA_SIZE >> sb->s_blocksize_bits;
	u32 page_ra_count = PAGE_SIZE >> sb->s_blocksize_bits;
	u32 adj_ra_count = max(fsi->sect_per_clus, page_ra_count);
	u32 ra_count = min(adj_ra_count, max_ra_count);

	/* Read-ahead is not required */
	if (fsi->sect_per_clus == 1)
		return 0;

	if (sec < fsi->data_start_sector) {
		EMSG("BD: %s: requested sector is invalid(sect:%llu, root:%llu)\n",
				__func__, sec, fsi->data_start_sector);
		return -EIO;
	}

	/* Not sector aligned with ra_count, resize ra_count to page size */
	if ((sec - fsi->data_start_sector) & (ra_count - 1))
		ra_count = page_ra_count;

	bh = sb_find_get_block(sb, sec);
	if (!bh || !buffer_uptodate(bh))
		bdev_readahead(sb, sec, (u64)ra_count);

	brelse(bh);

	return 0;
}

/*
 * returns 1, if bp is flushed
 * returns 0, if bp is not dirty
 * returns -1, if error occurs
 */
static s32 __dcache_ent_flush(struct super_block *sb, cache_ent_t *bp, u32 sync)
{
	if (!(bp->flag & DIRTYBIT))
		return 0;
#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY
	// Make buffer dirty (XXX: Naive impl.)
	if (write_sect(sb, bp->sec, bp->bh, 0))
		return -EIO;
#endif
	bp->flag &= ~(DIRTYBIT);

	if (sync)
		sync_dirty_buffer(bp->bh);

	return 1;
}

static s32 __dcache_ent_discard(struct super_block *sb, cache_ent_t *bp)
{
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	MMSG("%s : bp[%p] (sec:%016llx flag:%08x bh:%p) list(prev:%p next:%p) "
		"hash(prev:%p next:%p)\n", __func__,
		bp, bp->sec, bp->flag, bp->bh, bp->prev, bp->next,
		bp->hash.prev, bp->hash.next);

	__dcache_remove_hash(bp);
	bp->sec = ~0;
	bp->flag = 0;

	if (bp->bh) {
		__brelse(bp->bh);
		bp->bh = NULL;
	}

	move_to_lru(bp, &fsi->dcache.lru_list);
	return 0;
}

u8 *dcache_getblk(struct super_block *sb, u64 sec)
{
	cache_ent_t *bp;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	bp = __dcache_find(sb, sec);
	if (bp) {
		if (bdev_check_bdi_valid(sb)) {
			MMSG("%s: found cache(%p, sect:%llu). But invalid BDI\n"
				, __func__, bp, sec);
			__dcache_ent_flush(sb, bp, 0);
			__dcache_ent_discard(sb, bp);
			return NULL;
		}

		if (!(bp->flag & KEEPBIT))	// already in keep list
			move_to_mru(bp, &fsi->dcache.lru_list);

		if (likely(bp->bh->b_data))
			return bp->bh->b_data;

		sdfat_msg(sb, KERN_ERR,
			"%s: no b_data (flag:0x%02x, sect:%llu), %s",
			__func__, bp->flag, sec, bp->flag ? "EIO" : ",retry");

		if (bp->flag)
			return NULL;

		__dcache_ent_discard(sb, bp);
	}

	bp = __dcache_get(sb);

	if (!__check_hash_valid(bp))
		__dcache_remove_hash(bp);

	bp->sec = sec;
	bp->flag = 0;
	__dcache_insert_hash(sb, bp);

	if (read_sect(sb, sec, &(bp->bh), 1)) {
		__dcache_ent_discard(sb, bp);
		return NULL;
	}

	if (likely(bp->bh->b_data))
		return bp->bh->b_data;

	sdfat_msg(sb, KERN_ERR,
		"%s: no b_data after read (flag:0x%02x, sect:%llu)",
		__func__, bp->flag, sec);

	return NULL;
}

s32 dcache_modify(struct super_block *sb, u64 sec)
{
	s32 ret = -EIO;
	cache_ent_t *bp;

	set_sb_dirty(sb);

	bp = __dcache_find(sb, sec);
	if (unlikely(!bp)) {
		sdfat_fs_error(sb, "Can`t find dcache (sec 0x%016llx)", sec);
		return -EIO;
	}
#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY
	if (SDFAT_SB(sb)->fsi.vol_type != EXFAT) {
		bp->flag |= DIRTYBIT;
		return 0;
	}
#endif
	ret = write_sect(sb, sec, bp->bh, 0);

	if (ret) {
		DMSG("%s : failed to modify buffer(err:%d, sec:%llu, bp:0x%p)\n",
			__func__, ret, sec, bp);
	}

	return ret;
}

s32 dcache_lock(struct super_block *sb, u64 sec)
{
	cache_ent_t *bp;

	bp = __dcache_find(sb, sec);
	if (likely(bp)) {
		bp->flag |= LOCKBIT;
		return 0;
	}

	EMSG("%s : failed to lock buffer(sec:%llu, bp:0x%p)\n", __func__, sec, bp);
	return -EIO;
}

s32 dcache_unlock(struct super_block *sb, u64 sec)
{
	cache_ent_t *bp;

	bp = __dcache_find(sb, sec);
	if (likely(bp))  {
		bp->flag &= ~(LOCKBIT);
		return 0;
	}

	EMSG("%s : failed to unlock buffer (sec:%llu, bp:0x%p)\n", __func__, sec, bp);
	return -EIO;
}

s32 dcache_release(struct super_block *sb, u64 sec)
{
	cache_ent_t *bp;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	bp = __dcache_find(sb, sec);
	if (unlikely(!bp))
		return -ENOENT;

#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY
	if (bp->flag & DIRTYBIT) {
		if (write_sect(sb, bp->sec, bp->bh, 0))
			return -EIO;
	}
#endif
	bp->sec = ~0;
	bp->flag = 0;

	if (bp->bh) {
		__brelse(bp->bh);
		bp->bh = NULL;
	}

	move_to_lru(bp, &fsi->dcache.lru_list);
	return 0;
}

s32 dcache_release_all(struct super_block *sb)
{
	s32 ret = 0;
	cache_ent_t *bp;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
	s32 dirtycnt = 0;

	/* Connect list elements:
	 * LRU list : (A - B - ... - bp_front) + (bp_first + ... + bp_last)
	 */
	while (fsi->dcache.keep_list.prev != &fsi->dcache.keep_list) {
		cache_ent_t *bp_keep = fsi->dcache.keep_list.prev;
		// bp_keep->flag &= ~(KEEPBIT);		// Will be 0-ed later
		move_to_mru(bp_keep, &fsi->dcache.lru_list);
	}

	bp = fsi->dcache.lru_list.next;
	while (bp != &fsi->dcache.lru_list) {
#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY
		if (bp->flag & DIRTYBIT) {
			dirtycnt++;
			if (write_sect(sb, bp->sec, bp->bh, 0))
				ret = -EIO;
		}
#endif
		bp->sec = ~0;
		bp->flag = 0;

		if (bp->bh) {
			__brelse(bp->bh);
			bp->bh = NULL;
		}
		bp = bp->next;
	}

	DMSG("BD:Release / dirty buf cache: %d (err:%d)", dirtycnt, ret);
	return ret;
}


s32 dcache_flush(struct super_block *sb, u32 sync)
{
	s32 ret = 0;
	cache_ent_t *bp;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
	s32 dirtycnt = 0;
	s32 keepcnt = 0;

	/* Connect list elements:
	 * LRU list : (A - B - ... - bp_front) + (bp_first + ... + bp_last)
	 */
	while (fsi->dcache.keep_list.prev != &fsi->dcache.keep_list) {
		cache_ent_t *bp_keep = fsi->dcache.keep_list.prev;

		bp_keep->flag &= ~(KEEPBIT);		// Will be 0-ed later
		move_to_mru(bp_keep, &fsi->dcache.lru_list);
		keepcnt++;
	}

	bp = fsi->dcache.lru_list.next;
	while (bp != &fsi->dcache.lru_list) {
		if (bp->flag & DIRTYBIT) {
#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY
			// Make buffer dirty (XXX: Naive impl.)
			if (write_sect(sb, bp->sec, bp->bh, 0)) {
				ret = -EIO;
				break;
			}

#endif
			bp->flag &= ~(DIRTYBIT);
			dirtycnt++;

			if (sync != 0)
				sync_dirty_buffer(bp->bh);
		}
		bp = bp->next;
	}

	MMSG("BD: flush / dirty dentry cache: %d (%d from keeplist, err:%d)\n",
						dirtycnt, keepcnt, ret);
	return ret;
}

static cache_ent_t *__dcache_find(struct super_block *sb, u64 sec)
{
	s32 off;
	cache_ent_t *bp, *hp;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	off = (sec + (sec >> fsi->sect_per_clus_bits)) & (BUF_CACHE_HASH_SIZE - 1);

	hp = &(fsi->dcache.hash_list[off]);
	for (bp = hp->hash.next; bp != hp; bp = bp->hash.next) {
		if (bp->sec == sec) {
			touch_buffer(bp->bh);
			return bp;
		}
	}
	return NULL;
}

static cache_ent_t *__dcache_get(struct super_block *sb)
{
	cache_ent_t *bp;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	bp = fsi->dcache.lru_list.prev;
#ifdef CONFIG_SDFAT_DELAYED_META_DIRTY
	while (bp->flag & (DIRTYBIT | LOCKBIT)) {
		cache_ent_t *bp_prev = bp->prev; // hold prev

		if (bp->flag & DIRTYBIT) {
			MMSG("BD: Buf cache => Keep list\n");
			bp->flag |= KEEPBIT;
			move_to_mru(bp, &fsi->dcache.keep_list);
		}
		bp = bp_prev;

		/* If all dcaches are dirty */
		if (bp == &fsi->dcache.lru_list) {
			DMSG("BD: buf cache flooding\n");
			dcache_flush(sb, 0);
			bp = fsi->dcache.lru_list.prev;
		}
	}
#else
	while (bp->flag & LOCKBIT)
		bp = bp->prev;
#endif
//	if (bp->flag & DIRTYBIT)
//       sync_dirty_buffer(bp->bh);

	move_to_mru(bp, &fsi->dcache.lru_list);
	return bp;
}

static void __dcache_insert_hash(struct super_block *sb, cache_ent_t *bp)
{
	s32 off;
	cache_ent_t *hp;
	FS_INFO_T *fsi;

	fsi = &(SDFAT_SB(sb)->fsi);
	off = (bp->sec + (bp->sec >> fsi->sect_per_clus_bits)) & (BUF_CACHE_HASH_SIZE-1);

	hp = &(fsi->dcache.hash_list[off]);
	bp->hash.next = hp->hash.next;
	bp->hash.prev = hp;
	hp->hash.next->hash.prev = bp;
	hp->hash.next = bp;
}

static void __dcache_remove_hash(cache_ent_t *bp)
{
#ifdef DEBUG_HASH_LIST
	if ((bp->hash.next == (cache_ent_t *)DEBUG_HASH_NEXT) ||
		(bp->hash.prev == (cache_ent_t *)DEBUG_HASH_PREV)) {
		EMSG("%s: FATAL: tried to remove already-removed-cache-entry"
			"(bp:%p)\n", __func__, bp);
		return;
	}
#endif
	WARN_ON(bp->flag & DIRTYBIT);
	__remove_from_hash(bp);
}


/* end of cache.c */