/*
 *  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    : core_exfat.c                                              */
/*  PURPOSE : exFAT-fs core code for sdFAT                              */
/*                                                                      */
/*----------------------------------------------------------------------*/
/*  NOTES                                                               */
/*                                                                      */
/*                                                                      */
/************************************************************************/

#include <linux/version.h>
#include <linux/blkdev.h>
#include <linux/workqueue.h>
#include <linux/kernel.h>
#include <linux/log2.h>

#include "sdfat.h"
#include "core.h"
#include <asm/byteorder.h>
#include <asm/unaligned.h>

/*----------------------------------------------------------------------*/
/*  Constant & Macro Definitions                                        */
/*----------------------------------------------------------------------*/

/*----------------------------------------------------------------------*/
/*  Global Variable Definitions                                         */
/*----------------------------------------------------------------------*/

/*----------------------------------------------------------------------*/
/*  Local Variable Definitions                                          */
/*----------------------------------------------------------------------*/
static u8 free_bit[] = {
	0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,/*  0 ~  19*/
	0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3,/* 20 ~  39*/
	0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/* 40 ~  59*/
	0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,/* 60 ~  79*/
	0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2,/* 80 ~  99*/
	0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3,/*100 ~ 119*/
	0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/*120 ~ 139*/
	0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5,/*140 ~ 159*/
	0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2,/*160 ~ 179*/
	0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3,/*180 ~ 199*/
	0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2,/*200 ~ 219*/
	0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4,/*220 ~ 239*/
	0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0                /*240 ~ 254*/
};

static u8 used_bit[] = {
	0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3,/*  0 ~  19*/
	2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4,/* 20 ~  39*/
	2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5,/* 40 ~  59*/
	4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,/* 60 ~  79*/
	2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4,/* 80 ~  99*/
	3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6,/*100 ~ 119*/
	4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4,/*120 ~ 139*/
	3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,/*140 ~ 159*/
	2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5,/*160 ~ 179*/
	4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5,/*180 ~ 199*/
	3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6,/*200 ~ 219*/
	5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,/*220 ~ 239*/
	4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8             /*240 ~ 255*/
};


/*======================================================================*/
/*  Local Function Definitions                                          */
/*======================================================================*/
/*
 *  Directory Entry Management Functions
 */
static u32 exfat_get_entry_type(DENTRY_T *p_entry)
{
	FILE_DENTRY_T *ep = (FILE_DENTRY_T *) p_entry;

	if (ep->type == EXFAT_UNUSED)
		return TYPE_UNUSED;
	if (ep->type < 0x80)
		return TYPE_DELETED;
	if (ep->type == 0x80)
		return TYPE_INVALID;
	if (ep->type < 0xA0) {
		if (ep->type == 0x81)
			return TYPE_BITMAP;
		if (ep->type == 0x82)
			return TYPE_UPCASE;
		if (ep->type == 0x83)
			return TYPE_VOLUME;
		if (ep->type == 0x85) {
			if (le16_to_cpu(ep->attr) & ATTR_SUBDIR)
				return TYPE_DIR;
			return TYPE_FILE;
		}
		return TYPE_CRITICAL_PRI;
	}
	if (ep->type < 0xC0) {
		if (ep->type == 0xA0)
			return TYPE_GUID;
		if (ep->type == 0xA1)
			return TYPE_PADDING;
		if (ep->type == 0xA2)
			return TYPE_ACLTAB;
		return TYPE_BENIGN_PRI;
	}
	if (ep->type < 0xE0) {
		if (ep->type == 0xC0)
			return TYPE_STREAM;
		if (ep->type == 0xC1)
			return TYPE_EXTEND;
		if (ep->type == 0xC2)
			return TYPE_ACL;
		return TYPE_CRITICAL_SEC;
	}
	return TYPE_BENIGN_SEC;
} /* end of exfat_get_entry_type */

static void exfat_set_entry_type(DENTRY_T *p_entry, u32 type)
{
	FILE_DENTRY_T *ep = (FILE_DENTRY_T *) p_entry;

	if (type == TYPE_UNUSED) {
		ep->type = 0x0;
	} else if (type == TYPE_DELETED) {
		ep->type &= ~0x80;
	} else if (type == TYPE_STREAM) {
		ep->type = 0xC0;
	} else if (type == TYPE_EXTEND) {
		ep->type = 0xC1;
	} else if (type == TYPE_BITMAP) {
		ep->type = 0x81;
	} else if (type == TYPE_UPCASE) {
		ep->type = 0x82;
	} else if (type == TYPE_VOLUME) {
		ep->type = 0x83;
	} else if (type == TYPE_DIR) {
		ep->type = 0x85;
		ep->attr = cpu_to_le16(ATTR_SUBDIR);
	} else if (type == TYPE_FILE) {
		ep->type = 0x85;
		ep->attr = cpu_to_le16(ATTR_ARCHIVE);
	} else if (type == TYPE_SYMLINK) {
		ep->type = 0x85;
		ep->attr = cpu_to_le16(ATTR_ARCHIVE | ATTR_SYMLINK);
	}
} /* end of exfat_set_entry_type */

static u32 exfat_get_entry_attr(DENTRY_T *p_entry)
{
	FILE_DENTRY_T *ep = (FILE_DENTRY_T *)p_entry;

	return (u32)le16_to_cpu(ep->attr);
} /* end of exfat_get_entry_attr */

static void exfat_set_entry_attr(DENTRY_T *p_entry, u32 attr)
{
	FILE_DENTRY_T *ep = (FILE_DENTRY_T *)p_entry;

	ep->attr = cpu_to_le16((u16) attr);
} /* end of exfat_set_entry_attr */

static u8 exfat_get_entry_flag(DENTRY_T *p_entry)
{
	STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry;

	return ep->flags;
} /* end of exfat_get_entry_flag */

static void exfat_set_entry_flag(DENTRY_T *p_entry, u8 flags)
{
	STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry;

	ep->flags = flags;
} /* end of exfat_set_entry_flag */

static u32 exfat_get_entry_clu0(DENTRY_T *p_entry)
{
	STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry;

	return (u32)le32_to_cpu(ep->start_clu);
} /* end of exfat_get_entry_clu0 */

static void exfat_set_entry_clu0(DENTRY_T *p_entry, u32 start_clu)
{
	STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry;

	ep->start_clu = cpu_to_le32(start_clu);
} /* end of exfat_set_entry_clu0 */

static u64 exfat_get_entry_size(DENTRY_T *p_entry)
{
	STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry;

	return le64_to_cpu(ep->valid_size);
} /* end of exfat_get_entry_size */

static void exfat_set_entry_size(DENTRY_T *p_entry, u64 size)
{
	STRM_DENTRY_T *ep = (STRM_DENTRY_T *)p_entry;

	ep->valid_size = cpu_to_le64(size);
	ep->size = cpu_to_le64(size);
} /* end of exfat_set_entry_size */


#define TENS_MS_PER_SEC	(100)
#define SEC_TO_TENS_MS(sec)	(((sec) & 0x01) ? TENS_MS_PER_SEC : 0)
#define TENS_MS_TO_SEC(tens_ms)	(((tens_ms) / TENS_MS_PER_SEC) ? 1 : 0)

static void exfat_get_entry_time(DENTRY_T *p_entry, TIMESTAMP_T *tp, u8 mode)
{
	u16 t = 0x00, d = 0x21, tz = 0x00, s = 0x00;
	FILE_DENTRY_T *ep = (FILE_DENTRY_T *)p_entry;

	switch (mode) {
	case TM_CREATE:
		t = le16_to_cpu(ep->create_time);
		d = le16_to_cpu(ep->create_date);
		s = TENS_MS_TO_SEC(ep->create_time_ms);
		tz = ep->create_tz;
		break;
	case TM_MODIFY:
		t = le16_to_cpu(ep->modify_time);
		d = le16_to_cpu(ep->modify_date);
		s = TENS_MS_TO_SEC(ep->modify_time_ms);
		tz = ep->modify_tz;
		break;
	case TM_ACCESS:
		t = le16_to_cpu(ep->access_time);
		d = le16_to_cpu(ep->access_date);
		tz = ep->access_tz;
		break;
	}

	tp->tz.value = tz;
	tp->sec  = ((t & 0x001F) << 1) + s;
	tp->min  = (t >> 5) & 0x003F;
	tp->hour = (t >> 11);
	tp->day  = (d & 0x001F);
	tp->mon  = (d >> 5) & 0x000F;
	tp->year = (d >> 9);
} /* end of exfat_get_entry_time */

static void exfat_set_entry_time(DENTRY_T *p_entry, TIMESTAMP_T *tp, u8 mode)
{
	u16 t, d;
	FILE_DENTRY_T *ep = (FILE_DENTRY_T *)p_entry;

	t = (tp->hour << 11) | (tp->min << 5) | (tp->sec >> 1);
	d = (tp->year <<  9) | (tp->mon << 5) |  tp->day;

	switch (mode) {
	case TM_CREATE:
		ep->create_time = cpu_to_le16(t);
		ep->create_time_ms = SEC_TO_TENS_MS(tp->sec);
		ep->create_date = cpu_to_le16(d);
		ep->create_tz = tp->tz.value;
		break;
	case TM_MODIFY:
		ep->modify_time = cpu_to_le16(t);
		ep->modify_date = cpu_to_le16(d);
		ep->modify_time_ms = (tp->sec & 0x1) ? TENS_MS_PER_SEC : 0;
		ep->modify_tz = tp->tz.value;
		break;
	case TM_ACCESS:
		ep->access_time = cpu_to_le16(t);
		ep->access_date = cpu_to_le16(d);
		ep->access_tz = tp->tz.value;
		break;
	}
} /* end of exfat_set_entry_time */


static void __init_file_entry(struct super_block *sb, FILE_DENTRY_T *ep, u32 type)
{
	TIMESTAMP_T tm, *tp;

	exfat_set_entry_type((DENTRY_T *) ep, type);

	tp = tm_now_sb(sb, &tm);
	exfat_set_entry_time((DENTRY_T *) ep, tp, TM_CREATE);
	exfat_set_entry_time((DENTRY_T *) ep, tp, TM_MODIFY);
	exfat_set_entry_time((DENTRY_T *) ep, tp, TM_ACCESS);
} /* end of __init_file_entry */

static void __init_strm_entry(STRM_DENTRY_T *ep, u8 flags, u32 start_clu, u64 size)
{
	exfat_set_entry_type((DENTRY_T *) ep, TYPE_STREAM);
	ep->flags = flags;
	ep->start_clu = cpu_to_le32(start_clu);
	ep->valid_size = cpu_to_le64(size);
	ep->size = cpu_to_le64(size);
} /* end of __init_strm_entry */

static void __init_name_entry(NAME_DENTRY_T *ep, u16 *uniname)
{
	s32 i;

	exfat_set_entry_type((DENTRY_T *) ep, TYPE_EXTEND);
	ep->flags = 0x0;

	for (i = 0; i < 15; i++) {
		ep->unicode_0_14[i] = cpu_to_le16(*uniname);
		if (*uniname == 0x0)
			break;
		uniname++;
	}
} /* end of __init_name_entry */

static s32 exfat_init_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u32 type, u32 start_clu, u64 size)
{
	u64 sector;
	u8 flags;
	FILE_DENTRY_T *file_ep;
	STRM_DENTRY_T *strm_ep;

	flags = (type == TYPE_FILE) ? 0x01 : 0x03;

	/* we cannot use get_dentry_set_in_dir here because file ep is not initialized yet */
	file_ep = (FILE_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry, &sector);
	if (!file_ep)
		return -EIO;

	strm_ep = (STRM_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry+1, &sector);
	if (!strm_ep)
		return -EIO;

	__init_file_entry(sb, file_ep, type);
	if (dcache_modify(sb, sector))
		return -EIO;

	__init_strm_entry(strm_ep, flags, start_clu, size);
	if (dcache_modify(sb, sector))
		return -EIO;

	return 0;
} /* end of exfat_init_dir_entry */

s32 update_dir_chksum(struct super_block *sb, CHAIN_T *p_dir, s32 entry)
{
	s32 ret = -EIO;
	s32 i, num_entries;
	u64 sector;
	u16 chksum;
	FILE_DENTRY_T *file_ep;
	DENTRY_T *ep;

	file_ep = (FILE_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry, &sector);
	if (!file_ep)
		return -EIO;

	dcache_lock(sb, sector);

	num_entries = (s32) file_ep->num_ext + 1;
	chksum = calc_chksum_2byte((void *) file_ep, DENTRY_SIZE, 0, CS_DIR_ENTRY);

	for (i = 1; i < num_entries; i++) {
		ep = get_dentry_in_dir(sb, p_dir, entry+i, NULL);
		if (!ep)
			goto out_unlock;

		chksum = calc_chksum_2byte((void *) ep, DENTRY_SIZE, chksum, CS_DEFAULT);
	}

	file_ep->checksum = cpu_to_le16(chksum);
	ret = dcache_modify(sb, sector);
out_unlock:
	dcache_unlock(sb, sector);
	return ret;

} /* end of update_dir_chksum */


static s32 exfat_init_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, s32 num_entries,
						   UNI_NAME_T *p_uniname, DOS_NAME_T *p_dosname)
{
	s32 i;
	u64 sector;
	u16 *uniname = p_uniname->name;
	FILE_DENTRY_T *file_ep;
	STRM_DENTRY_T *strm_ep;
	NAME_DENTRY_T *name_ep;

	file_ep = (FILE_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry, &sector);
	if (!file_ep)
		return -EIO;

	file_ep->num_ext = (u8)(num_entries - 1);
	dcache_modify(sb, sector);

	strm_ep = (STRM_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry+1, &sector);
	if (!strm_ep)
		return -EIO;

	strm_ep->name_len = p_uniname->name_len;
	strm_ep->name_hash = cpu_to_le16(p_uniname->name_hash);
	dcache_modify(sb, sector);

	for (i = 2; i < num_entries; i++) {
		name_ep = (NAME_DENTRY_T *)get_dentry_in_dir(sb, p_dir, entry+i, &sector);
		if (!name_ep)
			return -EIO;

		__init_name_entry(name_ep, uniname);
		dcache_modify(sb, sector);
		uniname += 15;
	}

	update_dir_chksum(sb, p_dir, entry);

	return 0;
} /* end of exfat_init_ext_entry */


static s32 exfat_delete_dir_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, s32 order, s32 num_entries)
{
	s32 i;
	u64 sector;
	DENTRY_T *ep;

	for (i = order; i < num_entries; i++) {
		ep = get_dentry_in_dir(sb, p_dir, entry+i, &sector);
		if (!ep)
			return -EIO;

		exfat_set_entry_type(ep, TYPE_DELETED);
		if (dcache_modify(sb, sector))
			return -EIO;
	}

	return 0;
}

static s32 __write_partial_entries_in_entry_set(struct super_block *sb,
		ENTRY_SET_CACHE_T *es, u64 sec, u32 off, u32 count)
{
	s32 num_entries;
	u32 buf_off = (off - es->offset);
	u32 remaining_byte_in_sector, copy_entries;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
	u32 clu;
	u8 *buf, *esbuf = (u8 *)&(es->__buf);

	TMSG("%s entered\n", __func__);
	MMSG("%s: es %p sec %llu off %u cnt %d\n", __func__, es, sec, off, count);
	num_entries = count;

	while (num_entries) {
		/* write per sector base */
		remaining_byte_in_sector = (1 << sb->s_blocksize_bits) - off;
		copy_entries = min((s32)(remaining_byte_in_sector >> DENTRY_SIZE_BITS), num_entries);
		buf = dcache_getblk(sb, sec);
		if (!buf)
			goto err_out;
		MMSG("es->buf %p buf_off %u\n", esbuf, buf_off);
		MMSG("copying %d entries from %p to sector %llu\n", copy_entries, (esbuf + buf_off), sec);
		memcpy(buf + off, esbuf + buf_off, copy_entries << DENTRY_SIZE_BITS);
		dcache_modify(sb, sec);
		num_entries -= copy_entries;

		if (num_entries) {
			// get next sector
			if (IS_LAST_SECT_IN_CLUS(fsi, sec)) {
				clu = SECT_TO_CLUS(fsi, sec);
				if (es->alloc_flag == 0x03)
					clu++;
				else if (get_next_clus_safe(sb, &clu))
					goto err_out;
				sec = CLUS_TO_SECT(fsi, clu);
			} else {
				sec++;
			}
			off = 0;
			buf_off += copy_entries << DENTRY_SIZE_BITS;
		}
	}

	TMSG("%s exited successfully\n", __func__);
	return 0;
err_out:
	TMSG("%s failed\n", __func__);
	return -EIO;
}

/* write back all entries in entry set */
static s32 __write_whole_entry_set(struct super_block *sb, ENTRY_SET_CACHE_T *es)
{
	return __write_partial_entries_in_entry_set(sb, es, es->sector, es->offset, es->num_entries);
}

s32 update_dir_chksum_with_entry_set(struct super_block *sb, ENTRY_SET_CACHE_T *es)
{
	DENTRY_T *ep;
	u16 chksum = 0;
	s32 chksum_type = CS_DIR_ENTRY, i;

	ep = (DENTRY_T *)&(es->__buf);
	for (i = 0; i < es->num_entries; i++) {
		MMSG("%s %p\n", __func__, ep);
		chksum = calc_chksum_2byte((void *) ep, DENTRY_SIZE, chksum, chksum_type);
		ep++;
		chksum_type = CS_DEFAULT;
	}

	ep = (DENTRY_T *)&(es->__buf);
	((FILE_DENTRY_T *)ep)->checksum = cpu_to_le16(chksum);
	return __write_whole_entry_set(sb, es);
}

/* returns a set of dentries for a file or dir.
 * Note that this is a copy (dump) of dentries so that user should call write_entry_set()
 * to apply changes made in this entry set to the real device.
 * in:
 *   sb+p_dir+entry: indicates a file/dir
 *   type:  specifies how many dentries should be included.
 * out:
 *   file_ep: will point the first dentry(= file dentry) on success
 * return:
 *   pointer of entry set on success,
 *   NULL on failure.
 */

#define ES_MODE_STARTED				0
#define ES_MODE_GET_FILE_ENTRY			1
#define ES_MODE_GET_STRM_ENTRY			2
#define ES_MODE_GET_NAME_ENTRY			3
#define ES_MODE_GET_CRITICAL_SEC_ENTRY		4
ENTRY_SET_CACHE_T *get_dentry_set_in_dir(struct super_block *sb,
		CHAIN_T *p_dir, s32 entry, u32 type, DENTRY_T **file_ep)
{
	s32 ret;
	u32 off, byte_offset, clu = 0;
	u32 entry_type;
	u64 sec;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
	ENTRY_SET_CACHE_T *es = NULL;
	DENTRY_T *ep, *pos;
	u8 *buf;
	u8 num_entries;
	s32 mode = ES_MODE_STARTED;

	/* FIXME : is available in error case? */
	if (p_dir->dir == DIR_DELETED) {
		EMSG("%s : access to deleted dentry\n", __func__);
		BUG_ON(!fsi->prev_eio);
		return NULL;
	}

	TMSG("%s entered\n", __func__);
	MMSG("p_dir dir %u flags %x size %d\n", p_dir->dir, p_dir->flags, p_dir->size);
	MMSG("entry %d type %d\n", entry, type);

	byte_offset = entry << DENTRY_SIZE_BITS;
	ret = walk_fat_chain(sb, p_dir, byte_offset, &clu);
	if (ret)
		return NULL;

	/* byte offset in cluster */
	byte_offset &= fsi->cluster_size - 1;

	/* byte offset in sector */
	off = byte_offset & (u32)(sb->s_blocksize - 1);

	/* sector offset in cluster */
	sec = byte_offset >> (sb->s_blocksize_bits);
	sec += CLUS_TO_SECT(fsi, clu);

	buf = dcache_getblk(sb, sec);
	if (!buf)
		goto err_out;

	ep = (DENTRY_T *)(buf + off);
	entry_type = exfat_get_entry_type(ep);

	if ((entry_type != TYPE_FILE)
		&& (entry_type != TYPE_DIR))
		goto err_out;

	if (type == ES_ALL_ENTRIES)
		num_entries = ((FILE_DENTRY_T *)ep)->num_ext+1;
	else
		num_entries = type;

	MMSG("trying to malloc %lx bytes for %d entries\n",
		(unsigned long)(offsetof(ENTRY_SET_CACHE_T, __buf) + (num_entries)  * sizeof(DENTRY_T)), num_entries);
	es = kmalloc((offsetof(ENTRY_SET_CACHE_T, __buf) + (num_entries)  * sizeof(DENTRY_T)), GFP_KERNEL);
	if (!es) {
		EMSG("%s: failed to alloc entryset\n", __func__);
		goto err_out;
	}

	es->num_entries = num_entries;
	es->sector = sec;
	es->offset = off;
	es->alloc_flag = p_dir->flags;

	pos = (DENTRY_T *) &(es->__buf);

	while (num_entries) {
		// instead of copying whole sector, we will check every entry.
		// this will provide minimum stablity and consistency.
		entry_type = exfat_get_entry_type(ep);

		if ((entry_type == TYPE_UNUSED) || (entry_type == TYPE_DELETED))
			goto err_out;

		switch (mode) {
		case ES_MODE_STARTED:
			if  ((entry_type == TYPE_FILE) || (entry_type == TYPE_DIR))
				mode = ES_MODE_GET_FILE_ENTRY;
			else
				goto err_out;
			break;
		case ES_MODE_GET_FILE_ENTRY:
			if (entry_type == TYPE_STREAM)
				mode = ES_MODE_GET_STRM_ENTRY;
			else
				goto err_out;
			break;
		case ES_MODE_GET_STRM_ENTRY:
			if (entry_type == TYPE_EXTEND)
				mode = ES_MODE_GET_NAME_ENTRY;
			else
				goto err_out;
			break;
		case ES_MODE_GET_NAME_ENTRY:
			if (entry_type == TYPE_EXTEND)
				break;
			else if (entry_type == TYPE_STREAM)
				goto err_out;
			else if (entry_type & TYPE_CRITICAL_SEC)
				mode = ES_MODE_GET_CRITICAL_SEC_ENTRY;
			else
				goto err_out;
			break;
		case ES_MODE_GET_CRITICAL_SEC_ENTRY:
			if ((entry_type == TYPE_EXTEND) || (entry_type == TYPE_STREAM))
				goto err_out;
			else if ((entry_type & TYPE_CRITICAL_SEC) != TYPE_CRITICAL_SEC)
				goto err_out;
			break;
		}

		/* copy dentry */
		memcpy(pos, ep, sizeof(DENTRY_T));

		if (--num_entries == 0)
			break;

		if (((off + DENTRY_SIZE) & (u32)(sb->s_blocksize - 1)) <
					(off & (u32)(sb->s_blocksize - 1))) {
			// get the next sector
			if (IS_LAST_SECT_IN_CLUS(fsi, sec)) {
				if (es->alloc_flag == 0x03)
					clu++;
				else if (get_next_clus_safe(sb, &clu))
					goto err_out;
				sec = CLUS_TO_SECT(fsi, clu);
			} else {
				sec++;
			}
			buf = dcache_getblk(sb, sec);
			if (!buf)
				goto err_out;
			off = 0;
			ep = (DENTRY_T *)(buf);
		} else {
			ep++;
			off += DENTRY_SIZE;
		}
		pos++;
	}

	if (file_ep)
		*file_ep = (DENTRY_T *)&(es->__buf);

	MMSG("es sec %llu offset %u flags %d, num_entries %u buf ptr %p\n",
	 es->sector, es->offset, es->alloc_flag, es->num_entries, &(es->__buf));
	TMSG("%s exited %p\n", __func__, es);
	return es;
err_out:
	TMSG("%s exited (return NULL) (es %p)\n", __func__, es);

	/* kfree(NULL) is safe */
	kfree(es);
	es = NULL;
	return NULL;
}

void release_dentry_set(ENTRY_SET_CACHE_T *es)
{
	TMSG("%s %p\n", __func__, es);

	/* kfree(NULL) is safe */
	kfree(es);
	es = NULL;
}

static s32 __extract_uni_name_from_name_entry(NAME_DENTRY_T *ep, u16 *uniname, s32 order)
{
	s32 i, len = 0;

	for (i = 0; i < 15; i++) {
		/* FIXME : unaligned? */
		*uniname = le16_to_cpu(ep->unicode_0_14[i]);
		if (*uniname == 0x0)
			return len;
		uniname++;
		len++;
	}

	*uniname = 0x0;
	return len;

} /* end of __extract_uni_name_from_name_entry */

#define DIRENT_STEP_FILE	(0)
#define DIRENT_STEP_STRM	(1)
#define DIRENT_STEP_NAME	(2)
#define DIRENT_STEP_SECD	(3)

/* return values of exfat_find_dir_entry()
 * >= 0 : return dir entiry position with the name in dir
 * -EEXIST : (root dir, ".") it is the root dir itself
 * -ENOENT : entry with the name does not exist
 * -EIO    : I/O error
 */
static s32 exfat_find_dir_entry(struct super_block *sb, FILE_ID_T *fid,
		CHAIN_T *p_dir, UNI_NAME_T *p_uniname, s32 num_entries, DOS_NAME_T *unused, u32 type)
{
	s32 i, rewind = 0, dentry = 0, end_eidx = 0, num_ext = 0, len;
	s32 order, step, name_len;
	s32 dentries_per_clu, num_empty = 0;
	u32 entry_type;
	u16 entry_uniname[16], *uniname = NULL, unichar;
	CHAIN_T clu;
	DENTRY_T *ep;
	HINT_T *hint_stat = &fid->hint_stat;
	HINT_FEMP_T candi_empty;
	FILE_DENTRY_T *file_ep;
	STRM_DENTRY_T *strm_ep;
	NAME_DENTRY_T *name_ep;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	/*
	 * REMARK:
	 * DOT and DOTDOT are handled by VFS layer
	 */

	if (IS_CLUS_FREE(p_dir->dir))
		return -EIO;

	dentries_per_clu = fsi->dentries_per_clu;

	clu.dir = p_dir->dir;
	clu.size = p_dir->size;
	clu.flags = p_dir->flags;

	if (hint_stat->eidx) {
		clu.dir = hint_stat->clu;
		dentry = hint_stat->eidx;
		end_eidx = dentry;
	}

	candi_empty.eidx = -1;
rewind:
	order = 0;
	step = DIRENT_STEP_FILE;
	while (!IS_CLUS_EOF(clu.dir)) {
		i = dentry & (dentries_per_clu - 1);
		for (; i < dentries_per_clu; i++, dentry++) {
			if (rewind && (dentry == end_eidx))
				goto not_found;

			ep = get_dentry_in_dir(sb, &clu, i, NULL);
			if (!ep)
				return -EIO;

			entry_type = exfat_get_entry_type(ep);

			if ((entry_type == TYPE_UNUSED) || (entry_type == TYPE_DELETED)) {
				step = DIRENT_STEP_FILE;

				num_empty++;
				if (candi_empty.eidx == -1) {
					if (num_empty == 1) {
						candi_empty.cur.dir = clu.dir;
						candi_empty.cur.size = clu.size;
						candi_empty.cur.flags = clu.flags;
					}

					if (num_empty >= num_entries) {
						candi_empty.eidx = dentry - (num_empty - 1);
						ASSERT(0 <= candi_empty.eidx);
						candi_empty.count = num_empty;

						if ((fid->hint_femp.eidx == -1) ||
							(candi_empty.eidx <= fid->hint_femp.eidx)) {
							memcpy(&fid->hint_femp,
								&candi_empty,
								sizeof(HINT_FEMP_T));
						}
					}
				}

				if (entry_type == TYPE_UNUSED)
					goto not_found;
				continue;
			}

			num_empty = 0;
			candi_empty.eidx = -1;

			if ((entry_type == TYPE_FILE) || (entry_type == TYPE_DIR)) {
				step = DIRENT_STEP_FILE;
				if ((type == TYPE_ALL) || (type == entry_type)) {
					file_ep = (FILE_DENTRY_T *) ep;
					num_ext = file_ep->num_ext;
					step = DIRENT_STEP_STRM;
				}
				continue;
			}

			if (entry_type == TYPE_STREAM) {
				if (step != DIRENT_STEP_STRM) {
					step = DIRENT_STEP_FILE;
					continue;
				}
				step = DIRENT_STEP_FILE;
				strm_ep = (STRM_DENTRY_T *) ep;
				if ((p_uniname->name_hash == le16_to_cpu(strm_ep->name_hash)) &&
						(p_uniname->name_len == strm_ep->name_len)) {
					step = DIRENT_STEP_NAME;
					order = 1;
					name_len = 0;
				}
				continue;
			}

			if (entry_type == TYPE_EXTEND) {
				if (step != DIRENT_STEP_NAME) {
					step = DIRENT_STEP_FILE;
					continue;
				}
				name_ep = (NAME_DENTRY_T *) ep;

				if ((++order) == 2)
					uniname = p_uniname->name;
				else
					uniname += 15;

				len = __extract_uni_name_from_name_entry(name_ep, entry_uniname, order);
				name_len += len;

				unichar = *(uniname+len);
				*(uniname+len) = 0x0;

				if (nls_cmp_uniname(sb, uniname, entry_uniname)) {
					step = DIRENT_STEP_FILE;
				} else if (name_len == p_uniname->name_len) {
					if (order == num_ext) {
						//fid->hint_femp.eidx = -1;
						goto found;
					}
					step = DIRENT_STEP_SECD;
				}

				*(uniname+len) = unichar;
				continue;
			}

			if (entry_type & (TYPE_CRITICAL_SEC | TYPE_BENIGN_SEC)) {
				if (step == DIRENT_STEP_SECD) {
					if (++order == num_ext)
						goto found;
					continue;
				}
			}
			step = DIRENT_STEP_FILE;
		}

		if (clu.flags == 0x03) {
			if ((--clu.size) > 0)
				clu.dir++;
			else
				clu.dir = CLUS_EOF;
		} else {
			if (get_next_clus_safe(sb, &clu.dir))
				return -EIO;
		}
	}

not_found:
	/* we started at not 0 index,so we should try to find target
	 * from 0 index to the index we started at.
	 */
	if (!rewind && end_eidx) {
		rewind = 1;
		dentry = 0;
		clu.dir = p_dir->dir;
		/* reset empty hint */
		num_empty = 0;
		candi_empty.eidx = -1;
		goto rewind;
	}

	/* initialized hint_stat */
	hint_stat->clu = p_dir->dir;
	hint_stat->eidx = 0;
	return -ENOENT;

found:
	/* next dentry we'll find is out of this cluster */
	if (!((dentry + 1) & (dentries_per_clu-1))) {
		int ret = 0;

		if (clu.flags == 0x03) {
			if ((--clu.size) > 0)
				clu.dir++;
			else
				clu.dir = CLUS_EOF;
		} else {
			ret = get_next_clus_safe(sb, &clu.dir);
		}

		if (ret || IS_CLUS_EOF(clu.dir)) {
			/* just initialized hint_stat */
			hint_stat->clu = p_dir->dir;
			hint_stat->eidx = 0;
			return (dentry - num_ext);
		}
	}

	hint_stat->clu = clu.dir;
	hint_stat->eidx = dentry + 1;
	return (dentry - num_ext);
} /* end of exfat_find_dir_entry */

/* returns -EIO on error */
static s32 exfat_count_ext_entries(struct super_block *sb, CHAIN_T *p_dir, s32 entry, DENTRY_T *p_entry)
{
	s32 i, count = 0;
	u32 type;
	FILE_DENTRY_T *file_ep = (FILE_DENTRY_T *) p_entry;
	DENTRY_T *ext_ep;

	for (i = 0, entry++; i < file_ep->num_ext; i++, entry++) {
		ext_ep = get_dentry_in_dir(sb, p_dir, entry, NULL);
		if (!ext_ep)
			return -EIO;

		type = exfat_get_entry_type(ext_ep);
		if ((type == TYPE_EXTEND) || (type == TYPE_STREAM))
			count++;
		else
			return count;
	}

	return count;
} /* end of exfat_count_ext_entries */


/*
 *  Name Conversion Functions
 */
static void exfat_get_uniname_from_ext_entry(struct super_block *sb, CHAIN_T *p_dir, s32 entry, u16 *uniname)
{
	s32 i;
	DENTRY_T *ep;
	ENTRY_SET_CACHE_T *es;

	es = get_dentry_set_in_dir(sb, p_dir, entry, ES_ALL_ENTRIES, &ep);
	if (!es)
		return;

	if (es->num_entries < 3)
		goto out;

	ep += 2;

	/*
	 * First entry  : file entry
	 * Second entry : stream-extension entry
	 * Third entry  : first file-name entry
	 * So, the index of first file-name dentry should start from 2.
	 */
	for (i = 2; i < es->num_entries; i++, ep++) {
		/* end of name entry */
		if (exfat_get_entry_type(ep) != TYPE_EXTEND)
			goto out;

		__extract_uni_name_from_name_entry((NAME_DENTRY_T *)ep, uniname, i);
		uniname += 15;
	}

out:
	release_dentry_set(es);
} /* end of exfat_get_uniname_from_ext_entry */

static s32 exfat_calc_num_entries(UNI_NAME_T *p_uniname)
{
	s32 len;

	len = p_uniname->name_len;
	if (len == 0)
		return 0;

	/* 1 file entry + 1 stream entry + name entries */
	return((len-1) / 15 + 3);

} /* end of exfat_calc_num_entries */

static s32 exfat_check_max_dentries(FILE_ID_T *fid)
{
	if ((fid->size >> DENTRY_SIZE_BITS) >= MAX_EXFAT_DENTRIES) {
		/* exFAT spec allows a dir to grow upto 8388608(256MB) dentries */
		return -ENOSPC;
	}
	return 0;
} /* end of check_max_dentries */

/*
 *  Allocation Bitmap Management Functions
 */
s32 load_alloc_bmp(struct super_block *sb)
{
	s32 ret;
	u32 i, j, map_size, type, need_map_size;
	u64 sector;
	CHAIN_T clu;
	BMAP_DENTRY_T *ep;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	clu.dir = fsi->root_dir;
	clu.flags = 0x01;

	while (!IS_CLUS_EOF(clu.dir)) {
		for (i = 0; i < fsi->dentries_per_clu; i++) {
			ep = (BMAP_DENTRY_T *) get_dentry_in_dir(sb, &clu, i, NULL);
			if (!ep)
				return -EIO;

			type = exfat_get_entry_type((DENTRY_T *) ep);

			if (type == TYPE_UNUSED)
				break;
			if (type != TYPE_BITMAP)
				continue;

			if (ep->flags == 0x0) {
				fsi->map_clu  = le32_to_cpu(ep->start_clu);
				map_size = (u32) le64_to_cpu(ep->size);

				need_map_size = (((fsi->num_clusters - CLUS_BASE) - 1) >> 3) + 1;
				if (need_map_size != map_size) {
					sdfat_log_msg(sb, KERN_ERR,
						"bogus allocation bitmap size(need : %u, cur : %u)",
						need_map_size, map_size);
					/* Only allowed when bogus allocation bitmap size is large */
					if (need_map_size > map_size)
						return -EIO;
				}
				fsi->map_sectors = ((need_map_size - 1) >> (sb->s_blocksize_bits)) + 1;
				fsi->vol_amap =
					kmalloc((sizeof(struct buffer_head *) * fsi->map_sectors), GFP_KERNEL);
				if (!fsi->vol_amap)
					return -ENOMEM;

				sector = CLUS_TO_SECT(fsi, fsi->map_clu);

				/* trigger read amap ahead */
				bdev_readahead(sb, sector, fsi->map_sectors);

				for (j = 0; j < fsi->map_sectors; j++) {
					fsi->vol_amap[j] = NULL;
					ret = read_sect(sb, sector+j, &(fsi->vol_amap[j]), 1);
					if (ret) {
						/*  release all buffers and free vol_amap */
						i = 0;
						while (i < j)
							brelse(fsi->vol_amap[i++]);

						/* kfree(NULL) is safe */
						kfree(fsi->vol_amap);
						fsi->vol_amap = NULL;
						return ret;
					}
				}

				fsi->pbr_bh = NULL;
				return 0;
			}
		}

		if (get_next_clus_safe(sb, &clu.dir))
			return -EIO;
	}

	return -EINVAL;
} /* end of load_alloc_bmp */

void free_alloc_bmp(struct super_block *sb)
{
	s32 i;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	brelse(fsi->pbr_bh);

	for (i = 0; i < fsi->map_sectors; i++)
		__brelse(fsi->vol_amap[i]);

	/* kfree(NULL) is safe */
	kfree(fsi->vol_amap);
	fsi->vol_amap = NULL;
}

/* WARN :
 * If the value of "clu" is 0, it means cluster 2 which is
 * the first cluster of cluster heap.
 */
static s32 set_alloc_bitmap(struct super_block *sb, u32 clu)
{
	s32 i, b;
	u64 sector;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	i = clu >> (sb->s_blocksize_bits + 3);
	b = clu & (u32)((sb->s_blocksize << 3) - 1);

	sector = CLUS_TO_SECT(fsi, fsi->map_clu) + i;
	bitmap_set((unsigned long *)(fsi->vol_amap[i]->b_data), b, 1);

	return write_sect(sb, sector, fsi->vol_amap[i], 0);
} /* end of set_alloc_bitmap */

/* WARN :
 * If the value of "clu" is 0, it means cluster 2 which is
 * the first cluster of cluster heap.
 */
static s32 clr_alloc_bitmap(struct super_block *sb, u32 clu)
{
	s32 ret;
	s32 i, b;
	u64 sector;
	struct sdfat_sb_info *sbi = SDFAT_SB(sb);
	struct sdfat_mount_options *opts = &sbi->options;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	i = clu >> (sb->s_blocksize_bits + 3);
	b = clu & (u32)((sb->s_blocksize << 3) - 1);

	sector = CLUS_TO_SECT(fsi, fsi->map_clu) + i;

	bitmap_clear((unsigned long *)(fsi->vol_amap[i]->b_data), b, 1);

	ret = write_sect(sb, sector, fsi->vol_amap[i], 0);

	if (opts->discard) {
		s32 ret_discard;

		TMSG("discard cluster(%08x)\n", clu+2);
		ret_discard = sb_issue_discard(sb, CLUS_TO_SECT(fsi, clu+2),
				(1 << fsi->sect_per_clus_bits), GFP_NOFS, 0);

		if (ret_discard == -EOPNOTSUPP) {
			sdfat_msg(sb, KERN_ERR,
				"discard not supported by device, disabling");
			opts->discard = 0;
		}
	}

	return ret;
} /* end of clr_alloc_bitmap */

/* WARN :
 * If the value of "clu" is 0, it means cluster 2 which is
 * the first cluster of cluster heap.
 */
static u32 test_alloc_bitmap(struct super_block *sb, u32 clu)
{
	u32 i, map_i, map_b;
	u32 clu_base, clu_free;
	u8 k, clu_mask;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	clu_base = (clu & ~(0x7)) + 2;
	clu_mask = (1 << (clu - clu_base + 2)) - 1;

	map_i = clu >> (sb->s_blocksize_bits + 3);
	map_b = (clu >> 3) & (u32)(sb->s_blocksize - 1);

	for (i = 2; i < fsi->num_clusters; i += 8) {
		k = *(((u8 *) fsi->vol_amap[map_i]->b_data) + map_b);
		if (clu_mask > 0) {
			k |= clu_mask;
			clu_mask = 0;
		}
		if (k < 0xFF) {
			clu_free = clu_base + free_bit[k];
			if (clu_free < fsi->num_clusters)
				return clu_free;
		}
		clu_base += 8;

		if (((++map_b) >= (u32)sb->s_blocksize) ||
			(clu_base >= fsi->num_clusters)) {
			if ((++map_i) >= fsi->map_sectors) {
				clu_base = 2;
				map_i = 0;
			}
			map_b = 0;
		}
	}

	return CLUS_EOF;
} /* end of test_alloc_bitmap */

void sync_alloc_bmp(struct super_block *sb)
{
	s32 i;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	if (fsi->vol_amap == NULL)
		return;

	for (i = 0; i < fsi->map_sectors; i++)
		sync_dirty_buffer(fsi->vol_amap[i]);
}

static s32 exfat_chain_cont_cluster(struct super_block *sb, u32 chain, u32 len)
{
	if (!len)
		return 0;

	while (len > 1) {
		if (fat_ent_set(sb, chain, chain+1))
			return -EIO;
		chain++;
		len--;
	}

	if (fat_ent_set(sb, chain, CLUS_EOF))
		return -EIO;
	return 0;
}

s32 chain_cont_cluster(struct super_block *sb, u32 chain, u32 len)
{
	return exfat_chain_cont_cluster(sb, chain, len);
}


static s32 exfat_free_cluster(struct super_block *sb, CHAIN_T *p_chain, s32 do_relse)
{
	s32 ret = -EIO;
	u32 num_clusters = 0;
	u32 clu;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
	s32 i;
	u64 sector;

	/* invalid cluster number */
	if (IS_CLUS_FREE(p_chain->dir) || IS_CLUS_EOF(p_chain->dir))
		return 0;

	/* no cluster to truncate */
	if (p_chain->size == 0) {
		DMSG("%s: cluster(%u) truncation is not required.",
			 __func__, p_chain->dir);
		return 0;
	}

	/* check cluster validation */
	if (!is_valid_clus(fsi, p_chain->dir)) {
		EMSG("%s: invalid start cluster (%u)\n", __func__, p_chain->dir);
		sdfat_debug_bug_on(1);
		return -EIO;
	}

	set_sb_dirty(sb);
	clu = p_chain->dir;

	if (p_chain->flags == 0x03) {
		do {
			if (do_relse) {
				sector = CLUS_TO_SECT(fsi, clu);
				for (i = 0; i < fsi->sect_per_clus; i++) {
					if (dcache_release(sb, sector+i) == -EIO)
						goto out;
				}
			}

			if (clr_alloc_bitmap(sb, clu-2))
				goto out;
			clu++;

			num_clusters++;
		} while (num_clusters < p_chain->size);
	} else {
		do {
			if (do_relse) {
				sector = CLUS_TO_SECT(fsi, clu);
				for (i = 0; i < fsi->sect_per_clus; i++) {
					if (dcache_release(sb, sector+i) == -EIO)
						goto out;
				}
			}

			if (clr_alloc_bitmap(sb, (clu - CLUS_BASE)))
				goto out;

			if (get_next_clus_safe(sb, &clu))
				goto out;

			num_clusters++;
		} while (!IS_CLUS_EOF(clu));
	}

	/* success */
	ret = 0;
out:

	fsi->used_clusters -= num_clusters;
	return ret;
} /* end of exfat_free_cluster */

static s32 exfat_alloc_cluster(struct super_block *sb, u32 num_alloc, CHAIN_T *p_chain, s32 dest)
{
	s32 ret = -ENOSPC;
	u32 num_clusters = 0, total_cnt;
	u32 hint_clu, new_clu, last_clu = CLUS_EOF;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	total_cnt = fsi->num_clusters - CLUS_BASE;

	if (unlikely(total_cnt < fsi->used_clusters)) {
		sdfat_fs_error_ratelimit(sb,
				"%s: invalid used clusters(t:%u,u:%u)\n",
				__func__, total_cnt, fsi->used_clusters);
		return -EIO;
	}

	if (num_alloc > total_cnt - fsi->used_clusters)
		return -ENOSPC;

	hint_clu = p_chain->dir;
	/* find new cluster */
	if (IS_CLUS_EOF(hint_clu)) {
		if (fsi->clu_srch_ptr < CLUS_BASE) {
			EMSG("%s: fsi->clu_srch_ptr is invalid (%u)\n",
				__func__, fsi->clu_srch_ptr);
			ASSERT(0);
			fsi->clu_srch_ptr = CLUS_BASE;
		}

		hint_clu = test_alloc_bitmap(sb, fsi->clu_srch_ptr - CLUS_BASE);
		if (IS_CLUS_EOF(hint_clu))
			return -ENOSPC;
	}

	/* check cluster validation */
	if (!is_valid_clus(fsi, hint_clu)) {
		/* "last + 1" can be passed as hint_clu. Otherwise, bug_on */
		if (hint_clu != fsi->num_clusters) {
			EMSG("%s: hint_cluster is invalid (%u)\n",
			     __func__, hint_clu);
			sdfat_debug_bug_on(1);
		}
		hint_clu = CLUS_BASE;
		if (p_chain->flags == 0x03) {
			if (exfat_chain_cont_cluster(sb, p_chain->dir, num_clusters))
				return -EIO;
			p_chain->flags = 0x01;
		}
	}

	set_sb_dirty(sb);

	p_chain->dir = CLUS_EOF;

	while ((new_clu = test_alloc_bitmap(sb, hint_clu - CLUS_BASE)) != CLUS_EOF) {
		if ((new_clu != hint_clu) && (p_chain->flags == 0x03)) {
			if (exfat_chain_cont_cluster(sb, p_chain->dir, num_clusters)) {
				ret = -EIO;
				goto error;
			}
			p_chain->flags = 0x01;
		}

		/* update allocation bitmap */
		if (set_alloc_bitmap(sb, new_clu - CLUS_BASE)) {
			ret = -EIO;
			goto error;
		}

		num_clusters++;

		/* update FAT table */
		if (p_chain->flags == 0x01) {
			if (fat_ent_set(sb, new_clu, CLUS_EOF)) {
				ret = -EIO;
				goto error;
			}
		}

		if (IS_CLUS_EOF(p_chain->dir)) {
			p_chain->dir = new_clu;
		} else if (p_chain->flags == 0x01) {
			if (fat_ent_set(sb, last_clu, new_clu)) {
				ret = -EIO;
				goto error;
			}
		}
		last_clu = new_clu;

		if ((--num_alloc) == 0) {
			fsi->clu_srch_ptr = hint_clu;
			fsi->used_clusters += num_clusters;

			p_chain->size += num_clusters;
			return 0;
		}

		hint_clu = new_clu + 1;
		if (hint_clu >= fsi->num_clusters) {
			hint_clu = CLUS_BASE;

			if (p_chain->flags == 0x03) {
				if (exfat_chain_cont_cluster(sb, p_chain->dir, num_clusters)) {
					ret = -EIO;
					goto error;
				}
				p_chain->flags = 0x01;
			}
		}
	}
error:
	if (num_clusters)
		exfat_free_cluster(sb, p_chain, 0);
	return ret;
} /* end of exfat_alloc_cluster */

static s32 exfat_count_used_clusters(struct super_block *sb, u32 *ret_count)
{
	u32 count = 0;
	u32 i, map_i, map_b;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
	u32 total_clus = fsi->num_clusters - 2;

	map_i = map_b = 0;

	for (i = 0; i < total_clus; i += 8) {
		u8 k = *(((u8 *) fsi->vol_amap[map_i]->b_data) + map_b);

		count += used_bit[k];
		if ((++map_b) >= (u32)sb->s_blocksize) {
			map_i++;
			map_b = 0;
		}
	}

	/* FIXME : abnormal bitmap count should be handled as more smart */
	if (total_clus < count)
		count = total_clus;

	*ret_count = count;
	return 0;
} /* end of exfat_count_used_clusters */


/*
 *  File Operation Functions
 */
static FS_FUNC_T exfat_fs_func = {
	.alloc_cluster = exfat_alloc_cluster,
	.free_cluster = exfat_free_cluster,
	.count_used_clusters = exfat_count_used_clusters,

	.init_dir_entry = exfat_init_dir_entry,
	.init_ext_entry = exfat_init_ext_entry,
	.find_dir_entry = exfat_find_dir_entry,
	.delete_dir_entry = exfat_delete_dir_entry,
	.get_uniname_from_ext_entry = exfat_get_uniname_from_ext_entry,
	.count_ext_entries = exfat_count_ext_entries,
	.calc_num_entries = exfat_calc_num_entries,
	.check_max_dentries = exfat_check_max_dentries,

	.get_entry_type = exfat_get_entry_type,
	.set_entry_type = exfat_set_entry_type,
	.get_entry_attr = exfat_get_entry_attr,
	.set_entry_attr = exfat_set_entry_attr,
	.get_entry_flag = exfat_get_entry_flag,
	.set_entry_flag = exfat_set_entry_flag,
	.get_entry_clu0 = exfat_get_entry_clu0,
	.set_entry_clu0 = exfat_set_entry_clu0,
	.get_entry_size = exfat_get_entry_size,
	.set_entry_size = exfat_set_entry_size,
	.get_entry_time = exfat_get_entry_time,
	.set_entry_time = exfat_set_entry_time,
};

s32 mount_exfat(struct super_block *sb, pbr_t *p_pbr)
{
	pbr64_t *p_bpb = (pbr64_t *)p_pbr;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	fsi->sect_per_clus = 1 << p_bpb->bsx.sect_per_clus_bits;
	fsi->sect_per_clus_bits = p_bpb->bsx.sect_per_clus_bits;
	fsi->cluster_size_bits = fsi->sect_per_clus_bits + sb->s_blocksize_bits;
	fsi->cluster_size = 1 << fsi->cluster_size_bits;

	if (!p_bpb->bsx.num_fats) {
		sdfat_msg(sb, KERN_ERR, "bogus number of FAT structure");
		return -EINVAL;
	}

	if (p_bpb->bsx.num_fats >= 2) {
		sdfat_msg(sb, KERN_WARNING,
			"unsupported number of FAT structure :%u, try with 1",
			p_bpb->bsx.num_fats);
	}

	fsi->num_FAT_sectors = le32_to_cpu(p_bpb->bsx.fat_length);
	if (!fsi->num_FAT_sectors) {
		sdfat_msg(sb, KERN_ERR, "bogus fat size");
		return -EINVAL;
	}

	fsi->FAT1_start_sector = le32_to_cpu(p_bpb->bsx.fat_offset);
	fsi->FAT2_start_sector = fsi->FAT1_start_sector;

	fsi->root_start_sector = le32_to_cpu(p_bpb->bsx.clu_offset);
	fsi->data_start_sector = fsi->root_start_sector;

	fsi->num_sectors = le64_to_cpu(p_bpb->bsx.vol_length);
	if (!fsi->num_sectors) {
		sdfat_msg(sb, KERN_ERR, "bogus number of total sector count");
		return -EINVAL;
	}

	/* because the cluster index starts with 2 */
	fsi->num_clusters = le32_to_cpu(p_bpb->bsx.clu_count) + CLUS_BASE;

	fsi->vol_id = le32_to_cpu(p_bpb->bsx.vol_serial);
	fsi->root_dir = le32_to_cpu(p_bpb->bsx.root_cluster);
	fsi->dentries_in_root = 0;
	fsi->dentries_per_clu = 1 << (fsi->cluster_size_bits - DENTRY_SIZE_BITS);
	fsi->vol_flag = (u32) le16_to_cpu(p_bpb->bsx.vol_flags);
	fsi->clu_srch_ptr = CLUS_BASE;
	fsi->used_clusters = (u32) ~0;

	fsi->fs_func = &exfat_fs_func;
	fsi->vol_type = EXFAT;
	fat_ent_ops_init(sb);

	if (p_bpb->bsx.vol_flags & VOL_DIRTY) {
		fsi->vol_flag |= VOL_DIRTY;
		sdfat_log_msg(sb, KERN_WARNING, "Volume was not properly "
				"unmounted. Some data may be corrupt. "
				"Please run fsck.");
	}

	return 0;
} /* end of mount_exfat */

/* end of core_exfat.c */