/*
 *  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    : amap_smart.c                                              */
/*  PURPOSE : FAT32 Smart allocation code for sdFAT                     */
/*                                                                      */
/*----------------------------------------------------------------------*/
/*  NOTES                                                               */
/*                                                                      */
/*                                                                      */
/************************************************************************/

#include <linux/slab.h>
#include <linux/vmalloc.h>

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

/* AU list related functions */
static inline void amap_list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);

	/* Will be used to check if the entry is a single entry(selected) */
	entry->prev = NULL;
	entry->next = NULL;
}

static inline int amap_insert_to_list(AU_INFO_T *au, struct slist_head *shead)
{
	struct slist_head *entry = &au->shead;

	ASSERT(!entry->head);

	entry->next = shead->next;
	entry->head = shead;

	shead->next = entry;

	return 0;
}

static inline int amap_remove_from_list(AU_INFO_T *au, struct slist_head *shead)
{
	struct slist_head *entry = &au->shead;
	struct slist_head *iter;

	BUG_ON(entry->head != shead);

	iter = shead;

	while (iter->next) {
		if (iter->next == entry) {
			// iter->next = iter->next->next
			iter->next = entry->next;

			entry->next = NULL;
			entry->head = NULL;
			return 0;
		}
		iter = iter->next;
	}

	BUG_ON("Not reachable");
}

/* Full-linear serach => Find AU with max. number of fclu */
static inline AU_INFO_T *amap_find_hot_au_largest(struct slist_head *shead)
{
	struct slist_head *iter;
	uint16_t max_fclu = 0;
	AU_INFO_T *entry, *ret = NULL;

	ASSERT(shead->head == shead);	/* Singly-list condition */
	ASSERT(shead->next != shead);

	iter = shead->next;

	while (iter) {
		entry = list_entry(iter, AU_INFO_T, shead);

		if (entry->free_clusters > max_fclu) {
			max_fclu = entry->free_clusters;
			ret = entry;
		}

		iter = iter->next;
	}

	return ret;
}

/* Find partially used AU with max. number of fclu.
 * If there is no partial AU available, pick a clean one
 */
static inline AU_INFO_T *amap_find_hot_au_partial(AMAP_T *amap)
{
	struct slist_head *iter;
	uint16_t max_fclu = 0;
	AU_INFO_T *entry, *ret = NULL;

	iter = &amap->slist_hot;
	ASSERT(iter->head == iter);	/* Singly-list condition */
	ASSERT(iter->next != iter);

	iter = iter->next;

	while (iter) {
		entry = list_entry(iter, AU_INFO_T, shead);

		if (entry->free_clusters > max_fclu) {
			if (entry->free_clusters < amap->clusters_per_au) {
				max_fclu = entry->free_clusters;
				ret = entry;
			} else {
				if (!ret)
					ret = entry;
			}
		}

		iter = iter->next;
	}

	return ret;
}




/*
 * Size-base AU management functions
 */

/*
 * Add au into cold AU MAP
 * au: an isolated (not in a list) AU data structure
 */
int amap_add_cold_au(AMAP_T *amap, AU_INFO_T *au)
{
	FCLU_NODE_T *fclu_node = NULL;

	/* Check if a single entry */
	BUG_ON(au->head.prev);

	/* Ignore if the au is full */
	if (!au->free_clusters)
		return 0;

	/* Find entry */
	fclu_node = NODE(au->free_clusters, amap);

	/* Insert to the list */
	list_add_tail(&(au->head), &(fclu_node->head));

	/* Update fclu_hint (Increase) */
	if (au->free_clusters > amap->fclu_hint)
		amap->fclu_hint = au->free_clusters;

	return 0;
}

/*
 * Remove an AU from AU MAP
 */
int amap_remove_cold_au(AMAP_T *amap, AU_INFO_T *au)
{
	struct list_head *prev = au->head.prev;

	/* Single entries are not managed in lists */
	if (!prev) {
		BUG_ON(au->free_clusters > 0);
		return 0;
	}

	/* remove from list */
	amap_list_del(&(au->head));

	return 0;
}


/* "Find" best fit AU
 * returns NULL if there is no AU w/ enough free space.
 *
 * This function doesn't change AU status.
 * The caller should call amap_remove_cold_au() if needed.
 */
AU_INFO_T *amap_find_cold_au_bestfit(AMAP_T *amap, uint16_t free_clusters)
{
	AU_INFO_T *au = NULL;
	FCLU_NODE_T *fclu_iter;

	if (free_clusters <= 0 || free_clusters > amap->clusters_per_au) {
		EMSG("AMAP: amap_find_cold_au_bestfit / unexpected arg. (%d)\n",
				free_clusters);
		return NULL;
	}

	fclu_iter = NODE(free_clusters, amap);

	if (amap->fclu_hint < free_clusters) {
		/* There is no AUs with enough free_clusters */
		return NULL;
	}

	/* Naive Hash management (++) */
	do {
		if (!list_empty(&fclu_iter->head)) {
			struct list_head *first = fclu_iter->head.next;

			au = list_entry(first, AU_INFO_T, head);

			break;
		}

		fclu_iter++;
	} while (fclu_iter < (amap->fclu_nodes + amap->clusters_per_au));


	// BUG_ON(au->free_clusters < 0);
	BUG_ON(au && (au->free_clusters > amap->clusters_per_au));

	return au;
}


/* "Pop" best fit AU
 *
 * returns NULL if there is no AU w/ enough free space.
 * The returned AU will not be in the list anymore.
 */
AU_INFO_T *amap_pop_cold_au_bestfit(AMAP_T *amap, uint16_t free_clusters)
{
	/* Naive implementation */
	AU_INFO_T *au;

	au = amap_find_cold_au_bestfit(amap, free_clusters);
	if (au)
		amap_remove_cold_au(amap, au);

	return au;
}



/* Pop the AU with the largest free space
 *
 * search from 'start_fclu' to 0
 * (target freecluster : -1 for each step)
 * start_fclu = 0 means to search from the max. value
 */
AU_INFO_T *amap_pop_cold_au_largest(AMAP_T *amap, uint16_t start_fclu)
{
	AU_INFO_T *au = NULL;
	FCLU_NODE_T *fclu_iter;

	if (!start_fclu)
		start_fclu = amap->clusters_per_au;
	if (start_fclu > amap->clusters_per_au)
		start_fclu = amap->clusters_per_au;

	/* Use hint (search start point) */
	if (amap->fclu_hint < start_fclu)
		fclu_iter = NODE(amap->fclu_hint, amap);
	else
		fclu_iter = NODE(start_fclu, amap);

	/* Naive Hash management */
	do {
		if (!list_empty(&fclu_iter->head)) {
			struct list_head *first = fclu_iter->head.next;

			au = list_entry(first, AU_INFO_T, head);
			// BUG_ON((au < amap->entries) || ((amap->entries + amap->n_au) <= au));

			amap_list_del(first);

			// (Hint) Possible maximum value of free clusters (among cold)
			/* if it wasn't the whole search, don't update fclu_hint */
			if (start_fclu == amap->clusters_per_au)
				amap->fclu_hint = au->free_clusters;

			break;
		}

		fclu_iter--;
	} while (amap->fclu_nodes <= fclu_iter);

	return au;
}



/*
 * ===============================================
 * Allocation Map related functions
 * ===============================================
 */

/* Create AMAP related data structure (mount time) */
int amap_create(struct super_block *sb, u32 pack_ratio, u32 sect_per_au, u32 hidden_sect)
{
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);
	AMAP_T *amap;
	int total_used_clusters;
	int n_au_table = 0;
	int i, i_clu, i_au;
	int i_au_root = -1, i_au_hot_from = INT_MAX;
	u32 misaligned_sect = hidden_sect;
	u64 tmp;

	BUG_ON(!fsi->bd_opened);

	if (fsi->amap)
		return -EEXIST;

	/* Check conditions */
	if (fsi->vol_type != FAT32) {
		sdfat_msg(sb, KERN_ERR, "smart allocation is only available "
					"with fat32-fs");
		return -ENOTSUPP;
	}

	if (fsi->num_sectors < AMAP_MIN_SUPPORT_SECTORS) {
		sdfat_msg(sb, KERN_ERR, "smart allocation is only available "
			"with sectors above %d", AMAP_MIN_SUPPORT_SECTORS);
		return -ENOTSUPP;
	}

	/* AU size must be a multiple of clu_size */
	if ((sect_per_au <= 0) || (sect_per_au & (fsi->sect_per_clus - 1))) {
		sdfat_msg(sb, KERN_ERR,
			"invalid AU size (sect_per_au : %u, "
			"sect_per_clus : %u) "
			"please re-format for performance.",
			sect_per_au, fsi->sect_per_clus);
		return -EINVAL;
	}

	/* the start sector of this partition must be a multiple of clu_size */
	if (misaligned_sect & (fsi->sect_per_clus - 1)) {
		sdfat_msg(sb, KERN_ERR,
			"misaligned part (start sect : %u, "
			"sect_per_clus : %u) "
			"please re-format for performance.",
			misaligned_sect, fsi->sect_per_clus);
		return -EINVAL;
	}

	/* data start sector must be a multiple of clu_size */
	if (fsi->data_start_sector & (fsi->sect_per_clus - 1)) {
		sdfat_msg(sb, KERN_ERR,
			"misaligned data area (start sect : %llu, "
			"sect_per_clus : %u) "
			"please re-format for performance.",
			fsi->data_start_sector, fsi->sect_per_clus);
		return -EINVAL;
	}

	misaligned_sect &= (sect_per_au - 1);

	/* Allocate data structrues */
	amap = kzalloc(sizeof(AMAP_T), GFP_NOIO);
	if (!amap)
		return -ENOMEM;

	amap->sb = sb;

	tmp = fsi->num_sectors + misaligned_sect + sect_per_au - 1;
	do_div(tmp, sect_per_au);
	amap->n_au = tmp;
	amap->n_clean_au = 0;
	amap->n_full_au = 0;

	/* Reflect block-partition align first,
	 * then partition-data_start align
	 */
	amap->clu_align_bias = (misaligned_sect / fsi->sect_per_clus);
	amap->clu_align_bias += (fsi->data_start_sector >> fsi->sect_per_clus_bits) - CLUS_BASE;
	amap->clusters_per_au = sect_per_au / fsi->sect_per_clus;

	/* That is,
	 * the size of cluster is at least 4KB if the size of AU is 4MB
	 */
	if (amap->clusters_per_au > MAX_CLU_PER_AU) {
		sdfat_log_msg(sb, KERN_INFO,
			"too many clusters per AU (clus/au:%d > %d).",
		       amap->clusters_per_au,
		       MAX_CLU_PER_AU);
	}

	/* is it needed? why here? */
	// set_sb_dirty(sb);

	spin_lock_init(&amap->amap_lock);

	amap->option.packing_ratio = pack_ratio;
	amap->option.au_size = sect_per_au;
	amap->option.au_align_factor = hidden_sect;


	/* Allocate AU info table */
	n_au_table = (amap->n_au + N_AU_PER_TABLE - 1) / N_AU_PER_TABLE;
	amap->au_table = kmalloc(sizeof(AU_INFO_T *) * n_au_table, GFP_NOIO);
	if (!amap->au_table) {
		sdfat_msg(sb, KERN_ERR,
			"failed to alloc amap->au_table\n");
		kfree(amap);
		return -ENOMEM;
	}

	for (i = 0; i < n_au_table; i++)
		amap->au_table[i] = (AU_INFO_T *)get_zeroed_page(GFP_NOIO);

	/* Allocate buckets indexed by # of free clusters */
	amap->fclu_order = get_order(sizeof(FCLU_NODE_T) * amap->clusters_per_au);

	// XXX: amap->clusters_per_au limitation is 512 (w/ 8 byte list_head)
	sdfat_log_msg(sb, KERN_INFO, "page orders for AU nodes : %d "
			"(clus_per_au : %d, node_size : %lu)",
			amap->fclu_order,
			amap->clusters_per_au,
			(unsigned long)sizeof(FCLU_NODE_T));

	if (!amap->fclu_order)
		amap->fclu_nodes = (FCLU_NODE_T *)get_zeroed_page(GFP_NOIO);
	else
		amap->fclu_nodes = vzalloc(PAGE_SIZE << amap->fclu_order);

	amap->fclu_hint = amap->clusters_per_au;

	/* Hot AU list, ignored AU list */
	amap->slist_hot.next = NULL;
	amap->slist_hot.head = &amap->slist_hot;
	amap->total_fclu_hot = 0;

	amap->slist_ignored.next = NULL;
	amap->slist_ignored.head = &amap->slist_ignored;

	/* Strategy related vars. */
	amap->cur_cold.au = NULL;
	amap->cur_hot.au = NULL;
	amap->n_need_packing = 0;


	/* Build AMAP info */
	total_used_clusters = 0;		// Count # of used clusters

	i_au_root = i_AU_of_CLU(amap, fsi->root_dir);
	i_au_hot_from = amap->n_au - (SMART_ALLOC_N_HOT_AU - 1);

	for (i = 0; i < amap->clusters_per_au; i++)
		INIT_LIST_HEAD(&amap->fclu_nodes[i].head);

	/*
	 * Thanks to kzalloc()
	 * amap->entries[i_au].free_clusters = 0;
	 * amap->entries[i_au].head.prev = NULL;
	 * amap->entries[i_au].head.next = NULL;
	 */

	/* Parse FAT table */
	for (i_clu = CLUS_BASE; i_clu < fsi->num_clusters; i_clu++) {
		u32 clu_data;
		AU_INFO_T *au;

		if (fat_ent_get(sb, i_clu, &clu_data)) {
			sdfat_msg(sb, KERN_ERR,
				"failed to read fat entry(%u)\n", i_clu);
			goto free_and_eio;
		}

		if (IS_CLUS_FREE(clu_data)) {
			au = GET_AU(amap, i_AU_of_CLU(amap, i_clu));
			au->free_clusters++;
		} else
			total_used_clusters++;
	}

	/* Build AU list */
	for (i_au = 0; i_au < amap->n_au; i_au++) {
		AU_INFO_T *au = GET_AU(amap, i_au);

		au->idx = i_au;
		BUG_ON(au->free_clusters > amap->clusters_per_au);

		if (au->free_clusters == amap->clusters_per_au)
			amap->n_clean_au++;
		else if (au->free_clusters == 0)
			amap->n_full_au++;

		/* If hot, insert to the hot list */
		if (i_au >= i_au_hot_from) {
			amap_add_hot_au(amap, au);
			amap->total_fclu_hot += au->free_clusters;
		} else if (i_au != i_au_root || SMART_ALLOC_N_HOT_AU == 0) {
		/* Otherwise, insert to the free cluster hash */
			amap_add_cold_au(amap, au);
		}
	}

	/* Hot list -> (root) -> (last) -> (last - 1) -> ... */
	if (i_au_root >= 0 && SMART_ALLOC_N_HOT_AU > 0) {
		amap_add_hot_au(amap, GET_AU(amap, i_au_root));
		amap->total_fclu_hot += GET_AU(amap, i_au_root)->free_clusters;
	}

	fsi->amap = amap;
	fsi->used_clusters = total_used_clusters;

	sdfat_msg(sb, KERN_INFO,
			"AMAP: Smart allocation enabled (opt : %u / %u / %u)",
			amap->option.au_size, amap->option.au_align_factor,
			amap->option.packing_ratio);

	/* Debug purpose - check */
	//{
	//u32 used_clusters;
	//fat_count_used_clusters(sb, &used_clusters)
	//ASSERT(used_clusters == total_used_clusters);
	//}

	return 0;


free_and_eio:
	if (amap) {
		if (amap->au_table) {
			for (i = 0; i < n_au_table; i++)
				free_page((unsigned long)amap->au_table[i]);
			kfree(amap->au_table);
		}
		if (amap->fclu_nodes) {
			if (!amap->fclu_order)
				free_page((unsigned long)amap->fclu_nodes);
			else
				vfree(amap->fclu_nodes);
		}
		kfree(amap);
	}
	return -EIO;
}


/* Free AMAP related structure */
void amap_destroy(struct super_block *sb)
{
	AMAP_T *amap = SDFAT_SB(sb)->fsi.amap;
	int n_au_table;

	if (!amap)
		return;

	DMSG("%s\n", __func__);

	n_au_table = (amap->n_au + N_AU_PER_TABLE - 1) / N_AU_PER_TABLE;

	if (amap->au_table) {
		int i;

		for (i = 0; i < n_au_table; i++)
			free_page((unsigned long)amap->au_table[i]);

		kfree(amap->au_table);
	}
	if (!amap->fclu_order)
		free_page((unsigned long)amap->fclu_nodes);
	else
		vfree(amap->fclu_nodes);
	kfree(amap);
	SDFAT_SB(sb)->fsi.amap = NULL;
}


/*
 * Check status of FS
 * and change destination if needed to disable AU-aligned alloc.
 * (from ALLOC_COLD_ALIGNED to ALLOC_COLD_SEQ)
 */
static inline int amap_update_dest(AMAP_T *amap, int ori_dest)
{
	FS_INFO_T *fsi = &(SDFAT_SB(amap->sb)->fsi);
	int n_partial_au, n_partial_freeclus;

	if (ori_dest != ALLOC_COLD_ALIGNED)
		return ori_dest;

	/* # of partial AUs and # of clusters in those AUs */
	n_partial_au = amap->n_au - amap->n_clean_au - amap->n_full_au;
	n_partial_freeclus = fsi->num_clusters - fsi->used_clusters -
				amap->clusters_per_au * amap->n_clean_au;

	/* Status of AUs : Full / Partial / Clean
	 * If there are many partial (and badly fragmented) AUs,
	 * the throughput will decrease extremly.
	 *
	 * The follow code will treat those worst cases.
	 */

	/* XXX: AMAP heuristics */
	if ((amap->n_clean_au * 50 <= amap->n_au) &&
		(n_partial_freeclus*2) < (n_partial_au*amap->clusters_per_au)) {
		/* If clean AUs are fewer than 2% of n_au (80 AUs per 16GB)
		 * and fragment ratio is more than 2 (AVG free_clusters=half AU)
		 *
		 * disable clean-first allocation
		 * enable VFAT-like sequential allocation
		 */
		return ALLOC_COLD_SEQ;
	}

	return ori_dest;
}


#define PACKING_SOFTLIMIT      (amap->option.packing_ratio)
#define PACKING_HARDLIMIT      (amap->option.packing_ratio * 4)
/*
 * Pick a packing AU if needed.
 * Otherwise just return NULL
 *
 * This function includes some heuristics.
 */
static inline AU_INFO_T *amap_get_packing_au(AMAP_T *amap, int dest, int num_to_wb, int *clu_to_skip)
{
	AU_INFO_T *au = NULL;

	if (dest == ALLOC_COLD_PACKING) {
		/* ALLOC_COLD_PACKING:
		 * Packing-first mode for defrag.
		 * Optimized to save clean AU
		 *
		 * 1) best-fit AU
		 * 2) Smallest AU (w/ minimum free clusters)
		 */
		if (num_to_wb >= amap->clusters_per_au)
			num_to_wb = num_to_wb % amap->clusters_per_au;

		/* 이거 주석처리하면, AU size 딱 맞을때는 clean, 나머지는 작은거부터 */
		if (num_to_wb == 0)
			num_to_wb = 1;		// Don't use clean AUs

		au = amap_find_cold_au_bestfit(amap, num_to_wb);
		if (au && au->free_clusters == amap->clusters_per_au && num_to_wb > 1) {
			/* if au is clean then get a new partial one */
			au = amap_find_cold_au_bestfit(amap, 1);
		}

		if (au) {
			amap->n_need_packing = 0;
			amap_remove_cold_au(amap, au);
			return au;
		}
	}


	/* Heuristic packing:
	 * This will improve QoS greatly.
	 *
	 * Count # of AU_ALIGNED allocation.
	 * If the number exceeds the specific threshold,
	 * allocate on a partial AU or generate random I/O.
	 */
	if ((PACKING_SOFTLIMIT > 0) &&
		(amap->n_need_packing >= PACKING_SOFTLIMIT) &&
		(num_to_wb < (int)amap->clusters_per_au)) {
		/* Best-fit packing:
		 * If num_to_wb (expected number to be allocated) is smaller
		 * than AU_SIZE, find a best-fit AU.
		 */

		/* Back margin (heuristics) */
		if (num_to_wb < amap->clusters_per_au / 4)
			num_to_wb = amap->clusters_per_au / 4;

		au = amap_find_cold_au_bestfit(amap, num_to_wb);
		if (au != NULL) {
			amap_remove_cold_au(amap, au);

			MMSG("AMAP: packing (cnt: %d) / softlimit, "
			     "best-fit (num_to_wb: %d))\n",
				amap->n_need_packing, num_to_wb);

			if (au->free_clusters > num_to_wb) { // Best-fit search: if 문 무조건 hit
				*clu_to_skip = au->free_clusters - num_to_wb;
				/* otherwise don't skip */
			}
			amap->n_need_packing = 0;
			return au;
		}
	}

	if ((PACKING_HARDLIMIT != 0) &&
	    amap->n_need_packing >= PACKING_HARDLIMIT) {
		/* Compulsory SLC flushing:
		 * If there was no chance to do best-fit packing
		 * and the # of AU-aligned allocation exceeds HARD threshold,
		 * then pick a clean AU and generate a compulsory random I/O.
		 */
		au = amap_pop_cold_au_largest(amap, amap->clusters_per_au);
		if (au) {
			MMSG("AMAP: packing (cnt: %d) / hard-limit, largest)\n",
				amap->n_need_packing);

			if (au->free_clusters >= 96) {
				*clu_to_skip = au->free_clusters / 2;
				MMSG("AMAP: cluster idx re-position\n");
			}
			amap->n_need_packing = 0;
			return au;
		}
	}

	/* Update # of clean AU allocation */
	amap->n_need_packing++;
	return NULL;
}


/* Pick a target AU:
 * This function should be called
 * only if there are one or more free clusters in the bdev.
 */
TARGET_AU_T *amap_get_target_au(AMAP_T *amap, int dest, int num_to_wb)
{
	int loop_count = 0;

retry:
	if (++loop_count >= 3) {
		/* No space available (or AMAP consistency error)
		 * This could happen because of the ignored AUs but not likely
		 * (because the defrag daemon will not work if there is no enough space)
		 */
		BUG_ON(amap->slist_ignored.next == NULL);
		return NULL;
	}

	/* Hot clusters (DIR) */
	if (dest == ALLOC_HOT) {

		/* Working hot AU exist? */
		if (amap->cur_hot.au == NULL || amap->cur_hot.au->free_clusters == 0) {
			AU_INFO_T *au;

			if (amap->total_fclu_hot == 0) {
				/* No more hot AU avaialbe */
				dest = ALLOC_COLD;

				goto retry;
			}

			au = amap_find_hot_au_partial(amap);

			BUG_ON(au == NULL);
			BUG_ON(au->free_clusters <= 0);

			amap->cur_hot.au = au;
			amap->cur_hot.idx = 0;
			amap->cur_hot.clu_to_skip = 0;
		}

		/* Now allocate on a hot AU */
		return &amap->cur_hot;
	}

	/* Cold allocation:
	 * If amap->cur_cold.au has one or more free cluster(s),
	 * then just return amap->cur_cold
	 */
	if ((!amap->cur_cold.au)
		|| (amap->cur_cold.idx == amap->clusters_per_au)
		|| (amap->cur_cold.au->free_clusters == 0)) {

		AU_INFO_T *au = NULL;
		const AU_INFO_T *old_au = amap->cur_cold.au;
		int n_clu_to_skip = 0;

		if (old_au) {
			ASSERT(!IS_AU_WORKING(old_au, amap));
			/* must be NOT WORKING AU.
			 * (only for information gathering)
			 */
		}

		/* Next target AU is needed:
		 * There are 3 possible ALLOC options for cold AU
		 *
		 * ALLOC_COLD_ALIGNED: Clean AU first, but heuristic packing is ON
		 * ALLOC_COLD_PACKING: Packing AU first (usually for defrag)
		 * ALLOC_COLD_SEQ    : Sequential AU allocation (VFAT-like)
		 */

		/* Experimental: Modify allocation destination if needed (ALIGNED => SEQ) */
		// dest = amap_update_dest(amap, dest);

		if ((dest == ALLOC_COLD_SEQ) && old_au) {
			int i_au = old_au->idx + 1;

			while (i_au != old_au->idx) {
				au = GET_AU(amap, i_au);

				if ((au->free_clusters > 0) &&
					!IS_AU_HOT(au, amap) &&
					!IS_AU_IGNORED(au, amap)) {
					MMSG("AMAP: new cold AU(%d) with %d "
					     "clusters (seq)\n",
						au->idx, au->free_clusters);

					amap_remove_cold_au(amap, au);
					goto ret_new_cold;
				}
				i_au++;
				if (i_au >= amap->n_au)
					i_au = 0;
			}

			// no cold AUs are available => Hot allocation
			dest = ALLOC_HOT;
			goto retry;
		}


		/*
		 * Check if packing is needed
		 * (ALLOC_COLD_PACKING is treated by this function)
		 */
		au = amap_get_packing_au(amap, dest, num_to_wb, &n_clu_to_skip);
		if (au) {
			MMSG("AMAP: new cold AU(%d) with %d clusters "
				"(packing)\n", au->idx, au->free_clusters);
			goto ret_new_cold;
		}

		/* ALLOC_COLD_ALIGNED */
		/* Check if the adjacent AU is clean */
		if (old_au && ((old_au->idx + 1) < amap->n_au)) {
			au = GET_AU(amap, old_au->idx + 1);
			if ((au->free_clusters == amap->clusters_per_au) &&
						!IS_AU_HOT(au, amap) &&
						!IS_AU_IGNORED(au, amap)) {
				MMSG("AMAP: new cold AU(%d) with %d clusters "
					"(adjacent)\n", au->idx, au->free_clusters);
				amap_remove_cold_au(amap, au);
				goto ret_new_cold;
			}
		}

		/* Clean or largest AU */
		au = amap_pop_cold_au_largest(amap, 0);
		if (!au) {
			//ASSERT(amap->total_fclu_hot == (fsi->num_clusters - fsi->used_clusters - 2));
			dest = ALLOC_HOT;
			goto retry;
		}

		MMSG("AMAP: New cold AU (%d) with %d clusters\n",
				au->idx, au->free_clusters);

ret_new_cold:
		SET_AU_WORKING(au);

		amap->cur_cold.au = au;
		amap->cur_cold.idx = 0;
		amap->cur_cold.clu_to_skip = n_clu_to_skip;
	}

	return &amap->cur_cold;
}

/* Put and update target AU */
void amap_put_target_au(AMAP_T *amap, TARGET_AU_T *cur, unsigned int num_allocated)
{
	/* Update AMAP info vars. */
	if (num_allocated > 0 &&
		(cur->au->free_clusters + num_allocated) == amap->clusters_per_au) {
		/* if the target AU was a clean AU before this allocation ... */
		amap->n_clean_au--;
	}
	if (num_allocated > 0 &&
		cur->au->free_clusters == 0)
		amap->n_full_au++;

	if (IS_AU_HOT(cur->au, amap)) {
		/* Hot AU */
		MMSG("AMAP: hot allocation at AU %d\n", cur->au->idx);
		amap->total_fclu_hot -= num_allocated;

		/* Intra-AU round-robin */
		if (cur->idx >= amap->clusters_per_au)
			cur->idx = 0;

		/* No more space available */
		if (cur->au->free_clusters == 0)
			cur->au = NULL;

	} else {
		/* non-hot AU */
		ASSERT(IS_AU_WORKING(cur->au, amap));

		if (cur->idx >= amap->clusters_per_au || cur->au->free_clusters == 0) {
			/* It should be inserted back to AU MAP */
			cur->au->shead.head = NULL;		// SET_AU_NOT_WORKING
			amap_add_cold_au(amap, cur->au);

			// cur->au = NULL;	// This value will be used for the next AU selection
			cur->idx = amap->clusters_per_au;	// AU closing
		}
	}

}


/* Reposition target->idx for packing (Heuristics):
 * Skip (num_to_skip) free clusters in (cur->au)
 */
static inline int amap_skip_cluster(struct super_block *sb, TARGET_AU_T *cur, int num_to_skip)
{
	AMAP_T *amap = SDFAT_SB(sb)->fsi.amap;
	u32 clu, read_clu;
	MMSG_VAR(int num_to_skip_orig = num_to_skip);

	if (num_to_skip >= cur->au->free_clusters) {
		EMSG("AMAP(%s): skip mis-use. amap_566\n", __func__);
		return -EIO;
	}

	clu = CLU_of_i_AU(amap, cur->au->idx, cur->idx);
	while (num_to_skip > 0) {
		if (clu >= CLUS_BASE) {
			/* Cf.
			 * If AMAP's integrity is okay,
			 * we don't need to check if (clu < fsi->num_clusters)
			 */

			if (fat_ent_get(sb, clu, &read_clu))
				return -EIO;

			if (IS_CLUS_FREE(read_clu))
				num_to_skip--;
		}

		// Move clu->idx
		clu++;
		(cur->idx)++;

		if (cur->idx >= amap->clusters_per_au) {
			/* End of AU (Not supposed) */
			EMSG("AMAP: Skip - End of AU?! (amap_596)\n");
			cur->idx = 0;
			return -EIO;
		}
	}

	MMSG("AMAP: Skip_clusters (%d skipped => %d, among %d free clus)\n",
			num_to_skip_orig, cur->idx, cur->au->free_clusters);

	return 0;
}


/* AMAP-based allocation function for FAT32 */
s32 amap_fat_alloc_cluster(struct super_block *sb, u32 num_alloc, CHAIN_T *p_chain, s32 dest)
{
	AMAP_T *amap = SDFAT_SB(sb)->fsi.amap;
	TARGET_AU_T *cur = NULL;
	AU_INFO_T *target_au = NULL;				/* Allocation target AU */
	s32 ret = -ENOSPC;
	u32 last_clu = CLUS_EOF, read_clu;
	u32 new_clu, total_cnt;
	u32 num_allocated = 0, num_allocated_each = 0;
	FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi);

	BUG_ON(!amap);
	BUG_ON(IS_CLUS_EOF(fsi->used_clusters));

	total_cnt = fsi->num_clusters - CLUS_BASE;

	if (unlikely(total_cnt < fsi->used_clusters)) {
		sdfat_fs_error_ratelimit(sb,
				"AMAP(%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;

	p_chain->dir = CLUS_EOF;

	set_sb_dirty(sb);

	// spin_lock(&amap->amap_lock);

retry_alloc:
	/* Allocation strategy implemented */
	cur = amap_get_target_au(amap, dest, fsi->reserved_clusters);
	if (unlikely(!cur)) {
		// There is no available AU (only ignored-AU are left)
		sdfat_msg(sb, KERN_ERR, "AMAP Allocator: no avaialble AU.");
		goto error;
	}

	/* If there are clusters to skip */
	if (cur->clu_to_skip > 0) {
		if (amap_skip_cluster(sb, &amap->cur_cold, cur->clu_to_skip)) {
			ret = -EIO;
			goto error;
		}
		cur->clu_to_skip = 0;
	}

	target_au = cur->au;

	/*
	 * cur->au  : target AU info pointer
	 * cur->idx : the intra-cluster idx in the AU to start from
	 */
	BUG_ON(!cur->au);
	BUG_ON(!cur->au->free_clusters);
	BUG_ON(cur->idx >= amap->clusters_per_au);

	num_allocated_each = 0;
	new_clu = CLU_of_i_AU(amap, target_au->idx, cur->idx);

	do {
		/* Allocate at the target AU */
		if ((new_clu >= CLUS_BASE) && (new_clu < fsi->num_clusters)) {
			if (fat_ent_get(sb, new_clu, &read_clu)) {
				// spin_unlock(&amap->amap_lock);
				ret = -EIO;
				goto error;
			}

			if (IS_CLUS_FREE(read_clu)) {
				BUG_ON(GET_AU(amap, i_AU_of_CLU(amap, new_clu)) != target_au);

				/* Free cluster found */
				if (fat_ent_set(sb, new_clu, CLUS_EOF)) {
					ret = -EIO;
					goto error;
				}

				num_allocated_each++;

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

				/* Update au info */
				target_au->free_clusters--;
			}

		}

		new_clu++;
		(cur->idx)++;

		/* End of the AU */
		if ((cur->idx >= amap->clusters_per_au) || !(target_au->free_clusters))
			break;
	} while (num_allocated_each < num_alloc);

	/* Update strategy info */
	amap_put_target_au(amap, cur, num_allocated_each);


	num_allocated += num_allocated_each;
	fsi->used_clusters += num_allocated_each;
	num_alloc -= num_allocated_each;


	if (num_alloc > 0)
		goto retry_alloc;

	// spin_unlock(&amap->amap_lock);
	return 0;
error:
	if (num_allocated)
		fsi->fs_func->free_cluster(sb, p_chain, 0);
	return ret;
}


/* Free cluster for FAT32 (not implemented yet) */
s32 amap_free_cluster(struct super_block *sb, CHAIN_T *p_chain, s32 do_relse)
{
	return -ENOTSUPP;
}


/*
 * This is called by fat_free_cluster()
 * to update AMAP info.
 */
s32 amap_release_cluster(struct super_block *sb, u32 clu)
{
	AMAP_T *amap = SDFAT_SB(sb)->fsi.amap;
	AU_INFO_T *au;
	int i_au;

	// spin_lock(&amap->amap_lock);

	/* Update AU info */
	i_au = i_AU_of_CLU(amap, clu);
	BUG_ON(i_au >= amap->n_au);
	au = GET_AU(amap, i_au);
	if (au->free_clusters >= amap->clusters_per_au) {
		sdfat_fs_error(sb, "%s, au->free_clusters(%hd) is "
			"greater than or equal to amap->clusters_per_au(%hd)",
			__func__, au->free_clusters, amap->clusters_per_au);
		return -EIO;
	}

	if (IS_AU_HOT(au, amap)) {
		MMSG("AMAP: Hot cluster freed\n");
		au->free_clusters++;
		amap->total_fclu_hot++;
	} else if (!IS_AU_WORKING(au, amap) && !IS_AU_IGNORED(au, amap)) {
		/* Ordinary AU - update AU tree */
		// Can be optimized by implementing amap_update_au
		amap_remove_cold_au(amap, au);
		au->free_clusters++;
		amap_add_cold_au(amap, au);
	} else
		au->free_clusters++;


	/* Update AMAP info */
	if (au->free_clusters == amap->clusters_per_au)
		amap->n_clean_au++;
	if (au->free_clusters == 1)
		amap->n_full_au--;

	// spin_unlock(&amap->amap_lock);
	return 0;
}


/*
 * Check if the cluster is in a working AU
 * The caller should hold sb lock.
 * This func. should be used only if smart allocation is on
 */
s32 amap_check_working(struct super_block *sb, u32 clu)
{
	AMAP_T *amap = SDFAT_SB(sb)->fsi.amap;
	AU_INFO_T *au;

	BUG_ON(!amap);
	au = GET_AU(amap, i_AU_of_CLU(amap, clu));
	return IS_AU_WORKING(au, amap);
}


/*
 * Return the # of free clusters in that AU
 */
s32 amap_get_freeclus(struct super_block *sb, u32 clu)
{
	AMAP_T *amap = SDFAT_SB(sb)->fsi.amap;
	AU_INFO_T *au;

	BUG_ON(!amap);
	au = GET_AU(amap, i_AU_of_CLU(amap, clu));
	return (s32)au->free_clusters;
}


/*
 * Add the AU containing 'clu' to the ignored AU list.
 * The AU will not be used by the allocator.
 *
 * XXX: Ignored counter needed
 */
s32 amap_mark_ignore(struct super_block *sb, u32 clu)
{
	AMAP_T *amap = SDFAT_SB(sb)->fsi.amap;
	AU_INFO_T *au;

	BUG_ON(!amap);
	au = GET_AU(amap, i_AU_of_CLU(amap, clu));

	if (IS_AU_HOT(au, amap)) {
		/* Doesn't work with hot AUs */
		return -EPERM;
	} else if (IS_AU_WORKING(au, amap)) {
		return -EBUSY;
	}

	//BUG_ON(IS_AU_IGNORED(au, amap) && (GET_IGN_CNT(au) == 0));
	if (IS_AU_IGNORED(au, amap))
		return 0;

	amap_remove_cold_au(amap, au);
	amap_insert_to_list(au, &amap->slist_ignored);

	BUG_ON(!IS_AU_IGNORED(au, amap));

	//INC_IGN_CNT(au);
	MMSG("AMAP: Mark ignored AU (%d)\n", au->idx);
	return 0;
}


/*
 * This function could be used only on IGNORED AUs.
 * The caller should care whether it's ignored or not before using this func.
 */
s32 amap_unmark_ignore(struct super_block *sb, u32 clu)
{
	AMAP_T *amap = SDFAT_SB(sb)->fsi.amap;
	AU_INFO_T *au;

	BUG_ON(!amap);

	au = GET_AU(amap, i_AU_of_CLU(amap, clu));

	BUG_ON(!IS_AU_IGNORED(au, amap));
	// BUG_ON(GET_IGN_CNT(au) == 0);

	amap_remove_from_list(au, &amap->slist_ignored);
	amap_add_cold_au(amap, au);

	BUG_ON(IS_AU_IGNORED(au, amap));

	//DEC_IGN_CNT(au);

	MMSG("AMAP: Unmark ignored AU (%d)\n", au->idx);

	return 0;
}

/*
 * Unmark all ignored AU
 * This will return # of unmarked AUs
 */
s32 amap_unmark_ignore_all(struct super_block *sb)
{
	AMAP_T *amap = SDFAT_SB(sb)->fsi.amap;
	struct slist_head *entry;
	AU_INFO_T *au;
	int n = 0;

	BUG_ON(!amap);
	entry = amap->slist_ignored.next;
	while (entry) {
		au = list_entry(entry, AU_INFO_T, shead);

		BUG_ON(au != GET_AU(amap, au->idx));
		BUG_ON(!IS_AU_IGNORED(au, amap));

		//CLEAR_IGN_CNT(au);
		amap_remove_from_list(au, &amap->slist_ignored);
		amap_add_cold_au(amap, au);

		MMSG("AMAP: Unmark ignored AU (%d)\n", au->idx);
		n++;

		entry = amap->slist_ignored.next;
	}

	BUG_ON(amap->slist_ignored.next != NULL);
	MMSG("AMAP: unmark_ignore_all, total %d AUs\n", n);

	return n;
}

/**
 * @fn          amap_get_au_stat
 * @brief       report AUs status depending on mode
 * @return      positive on success, 0 otherwise
 * @param       sbi             super block info
 * @param       mode    TOTAL, CLEAN and FULL
 */
u32 amap_get_au_stat(struct super_block *sb, s32 mode)
{
	AMAP_T *amap = SDFAT_SB(sb)->fsi.amap;

	if (!amap)
		return 0;

	if (mode == VOL_AU_STAT_TOTAL)
		return amap->n_au;
	else if (mode == VOL_AU_STAT_CLEAN)
		return amap->n_clean_au;
	else if (mode == VOL_AU_STAT_FULL)
		return amap->n_full_au;

	return 0;
}