#include <system.h>
#include <nand.h>
#include <nandbus.h>
#include <chunks.h>
#include <scsi_layer.h>
#include <tm.h>
#include <blocks.h>

#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <string.h>
#include <hint_data.h>
#include <ngssd.h>
/****
 *
 * TODO:
 * DONE 1. CLEAN reserve chunk is nullfieid to avoid starvation of pending regular reqs?
 * V 2. relevant hints of non-cached chunks - what to do if we don't have a free chunk to store them?
 *    simply reject would slow requests, enqueuing them will occupy memory for regualr scsi reqs in the queue
 *    17/11 - decided to reject. nothing else to do really.
 * V 3. no more references to current
 * V 4. on reclaim shouldn't read every page, only those that contain valid chunks
 * V 5.  during reclamation should be able to process hint requests
 *
 */

#ifdef PROFILING
extern uint32_t ch_real_writes;
extern uint32_t ch_real_reads;
extern uint32_t ch_tm_reclaim_reads;
extern uint32_t ch_reclaim_reads;
extern uint32_t ch_reclaim_writes;
#endif

extern int maintid;
extern int tids[NAND_CHIP_COUNT];
static char mode[10] = "a+";

extern uint32_t program[NAND_PAGE_COUNT];
extern chm_t chm;

static FILE* open_file(char* fname){
	return fopen(fname, mode);
}

static void close_file(FILE* file){
	fclose(file);
}

static char filename[10] = "chunks.c";

#ifdef Debug
#define LOG(fmt, ...){ \
        int curr_tid = pthread_self(); \
        char fname[20]; \
        FILE* file; \
        struct stat st; \
        stat(mainlog, &st); \
        if(st.st_size  > 1024 * 1024*1024/* 1GB*/){ \
                mainlog[8] = 't' + ((mainlog[8]+1 -'t') % 2); \
                file = fopen(mainlog, "w+"); \
                fclose(file); \
        } \
        if(curr_tid == maintid){ \
                strcpy(fname, mainlog); \
        } \
        else{ \
                strcpy(fname, "log_chip0"); \
                int i; \
                for(i=0;i<NAND_CHIP_COUNT;i++){ \
                        if(tids[i] == curr_tid){ \
                                fname[8] = '0'+i; \
                        } \
                } \
        } \
        file = open_file(fname); \
        fprintf(file, "%s:%d. %s() - ", filename, __LINE__, __func__); \
        fprintf(file, fmt, ## __VA_ARGS__); \
        fclose(file); \
}
#else
#define LOG(fmt, ...)
#endif

#ifdef PROFILING
extern uint32_t hint_accessed;
extern uint32_t read_accessed;

extern uint32_t ch_real_writes;
extern uint32_t ch_real_reads;
extern uint32_t ch_reclaim_reads;
extern uint32_t ch_reclaim_writes;
#endif
/****************************************************************************/
/* 						Chunk Manager												 	 												*/
/****************************************************************************/
/*****************************************************/
/* 						Defines												 			   */
/*****************************************************/

/*****************************************************/
/* 						Macros												 			   */
/*****************************************************/
#define CH_ADDR_EMPTY (0xffffffff)
#define CH_ROOT_OFFSET_EMPTY (0xffffffff)

#define CH_ADDR_SET_PAGE(CH_ADDR, PAGE)      CH_ADDR = ((CH_ADDR) & (0xff000000)) | (PAGE);
#define CH_ADDR_SET_OFFSET(CH_ADDR, OFFSET)	 CH_ADDR = ((CH_ADDR) & (0x00ffffff)) | ((OFFSET) << 24);

#define CH_ADDR_SET_BY_FIELDS(CH_ADDR, PAGE, OFFSET)  CH_ADDR_SET_PAGE(CH_ADDR, PAGE);\
																											CH_ADDR_SET_OFFSET(CH_ADDR, OFFSET);}

#define CH_ADDR_GET_PAGE(CH_ADDR)         (CH_ADDR & 0x00ffffff)
#define CH_ADDR_GET_OFFSET(CH_ADDR)   ((CH_ADDR & 0xff000000) >> 24)

#define CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(LBA)   ((LBA) / CH_SECTORS_PER_CHUNK)
#define CH_CALC_LBA_OFFSET_IN_CHUNK(LBA)       ((LBA) % CH_SECTORS_PER_CHUNK)

#define CH_BUF_GET_ROOT_OFFSET(BUF, OFFSET) (((uint32_t*)(&((BUF)[NAND_SPARE_CHUNK_OFFSET_START])))[OFFSET])
#define CH_BUF_SET_ROOT_OFFSET(BUF, OFFSET, ROOT_OFFSET) ((uint32_t*)(&((BUF)[NAND_SPARE_CHUNK_OFFSET_START])))[OFFSET] = ROOT_OFFSET
#define CH_GET_PAGE(BUF, LBA)  (((uint32_t*)(BUF))[CH_CALC_LBA_OFFSET_IN_CHUNK(LBA)])

//void LOCK_CHM_MUTEX(){
//	pthread_mutex_lock(&(chm.mutex));chm.mutex_counter++;LOG("chm.mutex_counter lock %u\n", chm.mutex_counter);assert(chm.mutex_counter == 1);
//}
//
//void UNLOCK_CHM_MUTEX(){
//	chm.mutex_counter--;LOG("chm.mutex_counter unlock %u\n", chm.mutex_counter);assert(chm.mutex_counter == 0);pthread_mutex_unlock(&(chm.mutex));
//}
/************************************************/
/*							write buffer list  							*/
/************************************************/
list_t*
chm_list_alloc_node(req_t* req){
	list_t* new_node = (list_t*)malloc(sizeof(list_t));
	LOG("starting\n");
	new_node->next = 0;
	new_node->prev = 0;
	new_node->req  = req;

	return new_node;
}

list_t*
chm_list_lookup(list_t** list, req_t* req){
	list_t* node = *list;
	LOG("starting lba %u (list %x)\n", CH_REQ_GET_SPECIFIC(req)->lba, *list);assert(req);

	/* if no list*/
	if(node == 0){
		return 0;
	}

	LOG(" looking for %x\n", req);
	while(node != 0){
		if(node->req != req){
			node = node->next;
			continue;
		}

		LOG("found relevant_node %x\n", node);
		return node;
	}

	LOG("none found\n");
	return 0;
}

/**
 * push to head of list
 */
void
chm_list_push_head(list_t** list, req_t* req){
	LOG("starting. *list %x req %x\n", *list, req);
	list_t* new_node = chm_list_alloc_node(req);

//	{
//		list_t* node = *list;
//		while(node != 0){
////			LOG("node->lba %u node->req %x req %x\n", node->lba, node->req, req);
////			LOG("node->req - lba %u %s magic_num %u. req  - lba %u %s magic_num %u\n",
////					node->lba, ((tm_specific_t*)(node->req->specific))->cdb->cmd==SCSI_READ?"READ":"WRITE", ((tm_specific_t*)(node->req->specific))->cdb->magic_num,
////					((tm_specific_t*)(req->specific))->cdb->start_lba, ((tm_specific_t*)(req->specific))->cdb->cmd==SCSI_READ?"READ":"WRITE", ((tm_specific_t*)(req->specific))->cdb->magic_num);
//			assert(node->req != req);
//			assert(node->lba != lba);
//			node = node->next;
//		}
//	}
	new_node->next = *list;
	if(new_node->next){
		new_node->next->prev = new_node;
	}
	*list = new_node;

  return;
}
/**
 * push to end of list
 *
 */
void
chm_list_push(list_t** list, req_t* req){
	LOG("starting. *list %x req %x\n", *list, req);
	list_t* new_node = chm_list_alloc_node(req);

//	{
//		list_t* node = *list;
//		while(node != 0){
////			LOG("node->lba %u node->req %x req %x\n", node->lba, node->req, req);
////			LOG("node->req - lba %u %s magic_num %u. req  - lba %u %s magic_num %u\n",
////					node->lba, ((tm_specific_t*)(node->req->specific))->cdb->cmd==SCSI_READ?"READ":"WRITE", ((tm_specific_t*)(node->req->specific))->cdb->magic_num,
////					((tm_specific_t*)(req->specific))->cdb->start_lba, ((tm_specific_t*)(req->specific))->cdb->cmd==SCSI_READ?"READ":"WRITE", ((tm_specific_t*)(req->specific))->cdb->magic_num);
//			assert(node->req != req);
//			assert(node->lba != lba);
//			node = node->next;
//		}
//	}
	if(*list == 0){
		*list = new_node;LOG("list empty, push as head\n");
		return;
	}

	list_t* node = *list; //LOG("list not empty, push to tail\n");

	while(node->next != 0){
		node = node->next;
	}

	node->next = new_node;
	new_node->prev = node;

  return;
}

/**
 *
 * return node->req if popped, 0 if not found
 */
req_t*
chm_list_pop_specific(list_t** list, list_t* node){
	/* if this is the first node*/
	if((*list) == node){ assert(node->prev == 0);
		*list =	node->next;LOG("pop head\n");
		if((*list))	(*list)->prev = 0;
	}
	else{
//	/* if has prev*/
//	if(node->prev != 0){
		node->prev->next = node->next;
	}

	/* if has next*/
	if(node->next != 0){
		node->next->prev = node->prev;
	}

	req_t* req = node->req;
	free(node);

	LOG("success. return req %x\n", req);
	return req;
}

/**
 *
 * return req if popped, 0 if not found
 */
req_t*
chm_list_pop_specific_req(list_t** list, req_t* req){
	if(req == 0){LOG("req is 0. return 0\n");
		return 0;
	}

	list_t* node = chm_list_lookup(list, req);

	if(node == 0){LOG("req %x not in list. return 0\n", req);
		return 0;
	}

	if(node->prev){
		node->prev->next = node->next;
	}
	else{assert((*list) == node);
		(*list) = node->next;

	}

	if(node->next){
		node->next->prev = node->prev;
	}

	free(node);

	return req;
}

/**
 *
 * return 0 if popped, -1 if not found
 */
req_t*
chm_list_pop(list_t** list, req_t* req){
	list_t* node;	LOG("starting req %x\n", req);assert(req);
	node = chm_list_lookup(list, req);
	if(node !=0){
		return chm_list_pop_specific(list, node);
	}

	LOG("ERROR\n");return 0;
}

/**
 *
 * return req if popped, 0 if none found
 */
req_t*
chm_list_pop_head(list_t** list){
	//req_t* req;LOG("starting\n");
	if((*list) == 0){
		return 0;
	}

	return chm_list_pop(list, (*list)->req);
}
/********************************************
 * 						entry tm_pending_list         *
 * ******************************************/
list_t* chm_entry_tm_list_alloc_node(req_t* tm_req){
	list_t* new_node = (list_t*)malloc(sizeof(list_t));
	LOG("starting. new_node %x\n", new_node);
	new_node->next = 0;
	new_node->prev = 0;
	new_node->req  = tm_req;

	return new_node;
}

list_t*
chm_entry_tm_list_query(ch_buffer_entry* chunk_p, req_t* req){
	LOG("starting. chunk_p %x req->caller_req %x\n", chunk_p, req->caller_req);
	list_t* p = chunk_p->tm_pending_list;

	while(p){
//		LOG("p %x p->req %x (looking for req->caller_req %x(\n", p, p->req, req->caller_req);
		if(req->caller_req == p->req){
			return p;
		}

		p = p->next;
	}

	LOG("none found!\n");
	return 0;
}

void
chm_entry_tm_list_pop(ch_buffer_entry* chunk_p, req_t* req){
	LOG("starting. chunk_p %x req->caller_req %x magic_num %u\n", chunk_p, req->caller_req, ((tm_specific_t*)(req->caller_req->specific))->cdb->magic_num);
	list_t* p = chm_entry_tm_list_query(chunk_p, req);
	assert(p);

	if(p->prev){
		p->prev->next = p->next;
	}
	else{
		assert(chunk_p->tm_pending_list == p);
		chunk_p->tm_pending_list = p->next;
		if(chunk_p->tm_pending_list)
			chunk_p->tm_pending_list->prev = 0;
	}

	if(p->next){
		p->next->prev = p->prev;
	}

	free(p);LOG("free node %x\n", p);

	p = chunk_p->tm_pending_list;
	while(p !=0){
#ifdef Fanatic
		assert(p->req != req);
#endif
		p = p->next;
	}
}

void
chm_entry_tm_list_push(ch_buffer_entry* chunk_p, req_t* req){
	LOG("starting. chunk_p %x req->caller_req %x\n", chunk_p, req->caller_req);

	assert(!chm_entry_tm_list_query(chunk_p, req));

	list_t* node = chm_entry_tm_list_alloc_node(req->caller_req);

	if(!(chunk_p->tm_pending_list)){
		chunk_p->tm_pending_list = node;
		return;
	}

	assert(chunk_p->tm_pending_list->prev == 0);
	chunk_p->tm_pending_list->prev = node;
	node->next = chunk_p->tm_pending_list;
	chunk_p->tm_pending_list = node;
}

void
chm_entry_tm_list_cond_push(ch_buffer_entry* chunk_p, req_t* req){
	chm_specific_t* specific = CH_REQ_GET_SPECIFIC(req);

	if(specific->rw == CHM_READ_BEFORE_WRITE){
		if(!chm_entry_tm_list_query(chunk_p, req)){
			LOG("call chm_entry_tm_list_push\n");
			chm_entry_tm_list_push(chunk_p, req);
			chunk_p->tm_req_count++;

//			{
//				list_t *p = chunk_p->tm_pending_list;
//				int n = 0;
//				LOG("list reqs for chunk_p:\n");
//				while(p){
//					LOG("			p->req %x, magic_num %u\n", p->req, TM_REQ_GET_SPECIFIC(p->req)->cdb->magic_num);
//					n++;
//					p = p->next;
//				}
//				if(n!= chunk_p->tm_req_count){
//					list_t *p = chunk_p->tm_pending_list;
//					LOG("list contains %u requests. exepcted %u\n", n, chunk_p->tm_req_count);
//					assert(chunk_p->tm_req_count == n);
//				}
//			}
		}
	}
}

/*****************************************************
												Utils
 *****************************************************/

bool_t
chm_is_dequeued_request(req_t *req){
	return CH_REQ_GET_SPECIFIC(req)->dequeued;
}

void
chm_enqueue_chunk_related(req_t* req, uint32_t enq_reason){LOG("starting\n");
	chm_specific_t* specific = CH_REQ_GET_SPECIFIC(req);
	chm_list_push(&(chm.buffer_pending), req);LOG("no chunk. ENQUEUE buffer_pending\n\n\n\n");

	/* on dequeue the chunk may not be cached at all.
	 * regress to initial state*/
	specific->state = CHM_INITIAL;
	specific->chunk_p = 0;
	specific->enq_reason = enq_reason;
}
void
chm_enqueue_no_free_chunk(req_t* req){LOG("starting\n");
	chm_enqueue_chunk_related(req, CHM_ENQUEUE_REASON_NO_BUFFER);
	chm.unforced_counter++;assert(chm.unforced_counter > 0);
//	CH_REQ_GET_SPECIFIC(req)->enq_reason = CHM_ENQUEUE_REASON_NO_BUFFER;
}

void
chm_enqueue_cache_chunk_taken(req_t* req){LOG("starting\n");
	/* mark enq_reason as special*/
	chm_enqueue_chunk_related(req, CHM_ENQUEUE_REASON_BUFFER_TAKEN);
//	CH_REQ_GET_SPECIFIC(req)->enq_reason = CHM_ENQUEUE_REASON_BUFFER_TAKEN;
}

bool_t
chm_is_hint_request(req_t* req){
	return (CH_REQ_GET_SPECIFIC(req)->hint != 0);
}

bool_t
chm_is_hint_read_request(req_t* req){
	return (chm_is_hint_request(req) && CH_REQ_GET_SPECIFIC(req)->rw == CHM_READ);
}

/**
 * is request initiated as part of a tm reclamation write
 */
bool_t
chm_is_tm_reclaim_req(req_t* req){//LOG("specific->rec_chip_idx %u\n", specific->rec_chip_idx);
	return tm_is_reclaim_or_with_pending(req->caller_req);//(specific->rec_chip_idx != IDX_EMPTY);//
}

/**
 * caller req is tm reclamation, or chm reclamation
 */
bool_t
is_part_of_reclaim(req_t* req){
  return (req->caller_req->handler == baHandler || (req->caller_req->handler == tmHandler && tm_is_reclaim_req(req->caller_req->specific)));
}

/**
 * is regular request
 */
bool_t
chm_is_regular_req(req_t* req){//LOG("specific->rec_chip_idx %u\n", specific->rec_chip_idx);
	return (!chm_is_hint_request(req)
					&&
					!chm_is_tm_reclaim_req(req));
}

bool_t
chm_is_chunk_reserved(uint32_t i){
	return chm.buffer_entry[i].reserved;
}

bool_t
chm_is_req_hint_chunk_reserve(req_t* req, ch_buffer_entry* chunk_p){
	return ((chm_is_chunk_reserved(chunk_p->idx) && chm_is_hint_request(req)));
}

bool_t
chm_is_req_regular_chunk_reserve(req_t* req, ch_buffer_entry* chunk_p){
	return ((chm_is_chunk_reserved(chunk_p->idx) && chm_is_regular_req(req)));
}

void
chm_init_open_eu(uint32_t blk_idx, req_t* reclamation_in_progress){
  chm.open_eu.blk_idx = blk_idx;
  chm.open_eu.address = chm.block[blk_idx].address;
  chm.open_eu.offset  = 0;
  chm.open_eu.current = 0;LOG("chm.open_eu.current 0\n");
  chm.open_eu.reclamation_in_progress = reclamation_in_progress;
  chm.block[blk_idx].state = CH_BLOCK_PARTIAL;
}

/**
 * init buffer entry in idx.
 * req is newly designated current
 */
void
chm_init_buf_entry_simple(uint32_t idx, bool_t reserved){LOG("starting idx %u\n", idx);
	memset(&(chm.buffer_entry[idx].buf), 0xff, CH_CHUNK_SIZE);
	chm.buffer_entry[idx].idx     = idx;
	chm.buffer_entry[idx].address = CH_ADDR_EMPTY;
	chm.buffer_entry[idx].state   = CH_BUF_ENTRY_FREE;
	chm.buffer_entry[idx].root_offset = CH_ROOT_OFFSET_EMPTY;

	chm.buffer_entry[idx].tm_req_count = 0;
	chm.buffer_entry[idx].tm_pending_list  = 0;
	chm.buffer_entry[idx].written_req = 0;

	chm.buffer_entry[idx].reserved = reserved;
	chm.buffer_entry[idx].hinted   = 0;
	chm.buffer_entry[idx].sent     = 0;
#ifdef PROFILING
	chm.buffer_entry[idx].accessed = 0;
#endif
}

///**
// * init buffer entry in idx.
// * req is newly designated current
// */
//void
//chm_init_buf_entry(uint32_t idx, req_t* req){LOG("starting idx %u\n", idx);
//	memset(&(chm.buffer_entry[idx].buf), 0xff, CH_CHUNK_SIZE);
//	chm.buffer_entry[idx].idx     = idx;
//	chm.buffer_entry[idx].address = CH_ADDR_EMPTY;
//	chm.buffer_entry[idx].state   = CH_BUF_ENTRY_FREE;
//	chm.buffer_entry[idx].root_offset = CH_ROOT_OFFSET_EMPTY;
//	chm.buffer_entry[idx].written_req = 0;
//
//	if(chm.buffer_entry[idx].current != req){LOG("current %x != req %x %u. true clean\n", chm.buffer_entry[idx].current, req);
//		assert(chm.buffer_entry[idx].tm_pending_list == 0);
//		assert(chm.buffer_entry[idx].tm_req_count == 0);
//		assert(chm.buffer_entry[idx].chm_pending_list == 0);
//		assert(chm.buffer_entry[idx].current == 0/* || chm.buffer_entry[idx].current == req*/);
//		chm.buffer_entry[idx].tm_req_count = 0;
//		chm.buffer_entry[idx].tm_pending_list  = 0;
//		chm.buffer_entry[idx].chm_pending_list = 0;LOG("chunk %u initialized!\n", idx);
//		chm.buffer_entry[idx].current = 0;LOG("chm.buffer_entry[%u].current now 0\n", idx);
//	}
//}

uint32_t chm_get_ch_page_address(uint32_t lba){
	LOG("root_offset %u, ch addr %x\n", lba / CH_SECTORS_PER_CHUNK, chm.chunk[lba / CH_SECTORS_PER_CHUNK].address);
	if(chm.chunk[CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(lba)].address == CH_ADDR_EMPTY){
		LOG("return CH_ADDR_EMPTY\n");
		return CH_ADDR_EMPTY;
	}

	return CH_ADDR_GET_PAGE(chm.chunk[CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(lba)].address);
}

uint32_t chm_get_ch_page_offset(uint32_t lba){
	LOG("lba %u. chunk offset in page is %u\n", lba, CH_ADDR_GET_OFFSET(chm.chunk[CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(lba)].address));
	return CH_ADDR_GET_OFFSET(chm.chunk[CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(lba)].address);
}

void ch_set_new_lba(uint8_t* ch_buf, uint32_t lba, uint32_t phy_addr){
	LOG("set in buffered chunk lba %u to phy_addr %u. second lba phy_addr = %u\n", lba, phy_addr, (((uint32_t*)(ch_buf))[1]));
	((uint32_t*)(ch_buf))[CH_CALC_LBA_OFFSET_IN_CHUNK(lba)] = phy_addr;//LOG("after change second lba phy_addr = %u\n", lba, phy_addr, ((uint32_t*)(ch_buf))[1]);
}

///**
// * Get cached chunk pointer by lba
// *
// * Assumption:
// * 1.chm mutex is locked when we get here
// *
// * @return buffer_entry pointer if successful
// *         0 if relevant chunk is not cached
// */
//ch_buffer_entry* chm_get_cached_chunk(req_t* req){
//	chm_specific_t* specific = CH_REQ_GET_SPECIFIC(req);
//	int i;
//	uint32_t lba = specific->lba;
//	uint32_t lba_ch_root_offset = CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(lba);
//	LOG("specific->lba %u - see if chunk of root_offset %u is cached\n", lba, lba_ch_root_offset);
//	ch_buffer_entry* entry;
//
////	for(i=0; i<CH_BUFFER_ENTRIES;i++){
//	for(i=0; i<CH_TM_RECLAIM_BUFFER_ENTRIES;i++){
//		entry = &(chm.buffer_entry[i]);
////		/*for a non-tm-reclamation req, we can use only first
////		 * CH_BUFFER_ENTRIES */
////		if(chm_is_chunk_reserved_for_tm_reclaim(entry) && !chm_is_tm_reclaim_req(specific)){
////			LOG("non tm-reclaim req. haven't found cached entry\n");
////			return 0;
////		}
//		LOG("entry[%d] (current %x, reserve %u). root_offset %u (looking for %u), state %u\n",
//				i, entry->current, i>= CH_BUFFER_ENTRIES, entry->root_offset, lba_ch_root_offset, entry->state);
//		{
//			list_t* p = entry->tm_pending_list;
//			LOG("  tm_pending head node %x\n", entry->tm_pending_list);
//			while(p){
//#ifdef Fanatic
//				if(entry->root_offset != CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(TM_REQ_GET_SPECIFIC(p->req)->curr_lba)){
//					LOG("entry->root_offset %u expected %u from req %x\n", entry->root_offset, CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(TM_REQ_GET_SPECIFIC(p->req)->curr_lba), p->req);
//					assert(entry->root_offset == CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(TM_REQ_GET_SPECIFIC(p->req)->curr_lba));
//				}
//#endif
//				p = p->next;
//			}
//
//			p = entry->chm_pending_list;
//			LOG("  chm_pending head %x\n", p);
//			while(p){
//				LOG("         p->req %x, magic_num %u\n", p->req, ((tm_specific_t*)(p->req->caller_req->specific))->cdb->magic_num);
//				p = p->next;
//			}
//		}
//		if(entry->root_offset !=lba_ch_root_offset)
//			continue;
//
//
//		/*this is a relevant entry*/
//		switch(entry->state){
//		  case CH_BUF_ENTRY_ALLOCATED_OBS: /* same as ALLOCATED, but the same chunk was written to some other entry in the meantime (as hint...)*/
//		  	entry->state = CH_BUF_ENTRY_FREE;
//		  	break;
//			case CH_BUF_ENTRY_FREE:
//				/*this is an old entry*/
//				break;
//			case CH_BUF_ENTRY_ALLOCATED: /* empty, but will be filled when a read operation completes*/
//			case CH_BUF_ENTRY_CLEAN: /* in use, but identical to an on-flash copy */
//			case CH_BUF_ENTRY_CLEAN_WRITE: /* in use by write op, but identical to an on-flash copy. needed to prevent possible starvation of write request (if simply CLEAN, may get evacuated from cache without the write op ever using it)*/
//			case CH_BUF_ENTRY_WRITTEN: /* part of a pending flash write operation*/
//			case CH_BUF_ENTRY_DIRTY: /* in use and not stored on flash*/
//				LOG("got cached chunk %d entry->root_offset %u\n", i, entry->root_offset);
//				return entry;
//				break;
//			default:
//				assert(0);
//		}
//	}
//
//	/*failure*/
//	return 0;
//}
//
//bool_t
//chm_is_chunk_allocated_to_req(ch_buffer_entry* entry, req_t* req){
//		return (entry->current == req); /* only if its allocated to this req*/
//}
//
//bool_t
//chm_is_chunk_free(ch_buffer_entry* entry, req_t* req){LOG("starting. state %u reserved %u\n", entry->state, entry->reserved);
//	if(entry->current != 0){LOG("entry not free, current %x\n", entry->current);
//		return chm_is_chunk_allocated_to_req(entry, req); /*free only if its allocated to this entry*/
//	}
//
//	if(entry->chm_pending_list != 0){LOG("not free chm_pending_list %x req %x\n", entry->chm_pending_list, entry->chm_pending_list->req);
//		return 0;
//	}
//
//	if(entry->tm_pending_list != 0){LOG("not free tm_pending_list %x req %x\n", entry->tm_pending_list, entry->tm_pending_list->req);
//		return 0;
//	}
//
//	switch(entry->state){
//		case CH_BUF_ENTRY_ALLOCATED_OBS: /* same as ALLOCATED, but the same chunk was written to some other entry in the meantime (as hint...)*/
//		case CH_BUF_ENTRY_CLEAN: /* in use, but identical to an on-flash copy */
//		case CH_BUF_ENTRY_FREE:
//			break;
//		case CH_BUF_ENTRY_WRITTEN:
//		case CH_BUF_ENTRY_DIRTY:
//			LOG("not free state %u\n", entry->state);return 0;
//		/* we shouldn't be here in these states - previous checks should cover this*/
//		case CH_BUF_ENTRY_CLEAN_WRITE:
//		case CH_BUF_ENTRY_ALLOCATED:
//		default:
//			LOG("buffer entry state is %u\n", entry->state);
//			assert(0);
//	}
//	LOG("free]\n");
//	return 1;
//}

bool_t chm_is_entry_available(int ch_idx, req_t* req);

/**
 * Find a free/clean chunk that we can re-use to store
 * an other chunk
 *
 * Assumption:
 * 1.chm mutex is locked when we get here
 *
 * @return chunk of free/clean chunk buffer
 *         0 if none found
 */
ch_buffer_entry*
chm_get_free_chunk(req_t* req){
	ch_buffer_entry* entry;
	int i;
	//uint32_t idx = IDX_EMPTY;
	LOG("starting\n");
#ifdef Fanatic
	{
		uint32_t n = 0;
		for(i=0; i<CH_TM_RECLAIM_BUFFER_ENTRIES;i++){
			if(!chm_is_chunk_reserved(i)) continue;

			entry = &(chm.buffer_entry[i]);
			if(entry->tm_pending_list != 0) n++;

			if(n>NAND_CHIP_COUNT){
				LOG("we have more than %u reserved entries with tm_pending reqs\n", NAND_CHIP_COUNT);
				assert(n <NAND_CHIP_COUNT+1);
			}
		}
	}
#endif

	for(i=CH_TM_RECLAIM_BUFFER_ENTRIES -1; i>=0 ; i--){
		if(chm_is_entry_available(i, req)){
			chm_init_buf_entry_simple(i, chm.buffer_entry[i].reserved);
                        /*reserved entries usage count*/
                        if(chm.buffer_entry[i].reserved)
                          chm.reserved_used++;
			return &(chm.buffer_entry[i]);
		}
	}

	return 0;
}

ch_block* chm_get_block_by_address(uint32_t block_address){
	LOG("block_address %u\n", block_address);
	assert(block_address % NAND_PAGES_PER_ERASE_UNIT == 0);
	int i;

	for(i=0;i<CH_BLOCK_COUNT;i++){
		if(chm.block[i].address == block_address){
			return &(chm.block[i]);
		}
	}

	/*chunk address should always be in some ch_block*/
	assert(0);
	return 0;
}

bool_t
ch_get_bitmap_chunk(uint32_t ch_root_offset){
	uint32_t page_address   = CH_ADDR_GET_PAGE(chm.chunk[ch_root_offset].address);
	uint32_t offset_in_page = CH_ADDR_GET_OFFSET(chm.chunk[ch_root_offset].address);
	uint32_t page_offset_in_block = page_address % NAND_PAGES_PER_ERASE_UNIT;
	uint32_t block_address  = (page_address -page_offset_in_block);

	ch_block* block = chm_get_block_by_address(block_address);
	bool_t curr_bit = (block->page[page_offset_in_block].bitmap[offset_in_page / sizeof(uint8_t)] & (1 << (offset_in_page % sizeof(uint8_t)))) >> (offset_in_page % sizeof(uint8_t));
	LOG("ch_root_offset %u curr_bit %u\n", ch_root_offset, curr_bit);

	return curr_bit;
}

/**
 * mark chunk address in offset at root map as obsolete
 *
 * Assumption:
 * 1.chm mutex is locked when we get here
 */
void ch_set_bitmap_chunk(uint32_t ch_root_offset, uint8_t bit){
	LOG("ch_root_offset %u bit %u\n", ch_root_offset, bit);assert(ch_root_offset < CH_CHUNK_COUNT);
	assert(ch_root_offset != CH_ROOT_OFFSET_EMPTY);
	if(chm.chunk[ch_root_offset].address == CH_ADDR_EMPTY){
		LOG("chunk addr in ch_root_offset %u is empty. can't mark it either way\n", ch_root_offset);
		return;
	}
	uint32_t page_address   = CH_ADDR_GET_PAGE(chm.chunk[ch_root_offset].address);
	uint32_t offset_in_page = CH_ADDR_GET_OFFSET(chm.chunk[ch_root_offset].address);
	uint32_t page_offset_in_block = page_address % NAND_PAGES_PER_ERASE_UNIT;
	uint32_t block_address  = (page_address -page_offset_in_block);

	LOG("page_address %u (chm.chunk[ch_root_offset].address %u CH_ADDR_EMPTY %x), offset_in_page %u, block_address %u, page_offset_in_block %u\n",
			 page_address, chm.chunk[ch_root_offset].address, CH_ADDR_EMPTY, offset_in_page, block_address, page_offset_in_block);

	ch_block* block = chm_get_block_by_address(block_address);
	bool_t curr_bit = (block->page[page_offset_in_block].bitmap[offset_in_page / sizeof(uint8_t)] & (1 << (offset_in_page % sizeof(uint8_t)))) >> (offset_in_page % sizeof(uint8_t));
#ifdef Fanatic
	assert(curr_bit == ch_get_bitmap_chunk(ch_root_offset));
	assert(bit != curr_bit);
#endif
	if(bit){
		if(!curr_bit){
			block->page[page_offset_in_block].bitmap[offset_in_page / sizeof(uint8_t)] |= (1 << (offset_in_page % sizeof(uint8_t)));
			block->obs_count--;
		}
	}
	else{
		if(curr_bit){
			block->page[page_offset_in_block].bitmap[offset_in_page / sizeof(uint8_t)] &= (~(1 << (offset_in_page % sizeof(uint8_t))));
			block->obs_count++;
		}
	}
}

bool_t
chm_is_reclamation_in_progress(){
	return (chm.open_eu.reclamation_in_progress != 0);
}

/**
 * Check if a chunk cache flush is in progress (not by current request)
 * by verifying if there is any cache entry in WRITTEN state,
 * and that no reclamation is occuring
 *
 * @return 0 if no, 1 if yes
 */
bool_t
chm_is_write_in_progress(req_t* curr_req){
	LOG("return %u\n", chm.open_eu.current);
	return ((chm.open_eu.current != 0  &&  curr_req != chm.open_eu.current)
			    ||
			    chm_is_reclamation_in_progress());
}

bool_t
chm_current_write_req_pending(){
	/* no current*/
	if(chm.open_eu.current == 0){
		return 0;
	}

	/* current not activated yet*/
	uint32_t i;
	for(i=0;i<CH_TM_RECLAIM_BUFFER_ENTRIES; i++){
		/*entry written ~> req activated*/
		if(chm.buffer_entry[i].state == CH_BUF_ENTRY_WRITTEN){LOG("(chm.buffer_entry[%u].state WRITTEN\n", i);
			return 0;
		}
	}LOG("(no entry WRITTEN. current inactive. return 1\n");

	return 1;
}

chm_is_dirty_cache_threshold_reached(){
	LOG("starting\n");
	int i, n = 0;

	/* count dirty entries*/
	for(i=0; i<CH_TM_RECLAIM_BUFFER_ENTRIES;i++){
		/*this is a relevant entry*/
		if(chm.buffer_entry[i].state == CH_BUF_ENTRY_DIRTY){
				n++;
				LOG("chm.buffer_entry[%d].state = %u (n %u)\n", i, chm.buffer_entry[i].state,n);
		}
	}

	if(n>=CH_DIRTY_CACHE_ENTRIES_THRESHOLD){
		LOG("counted %u dirty entries.\n", n, CH_DIRTY_CACHE_ENTRIES_THRESHOLD);
		/*check if we have enough pending write requests for this much dirty entries*/
		uint32_t write_reqs = 0;
		list_t* node = chm.write_pending;
		while(node){
			write_reqs++;
			node = node->next;
		}

//		/* add current write req if its not activated yet*/
//		if(chm_current_write_req_pending()){
//			write_reqs++;LOG("add inactive write req %x\n", chm.open_eu.current);
//		}

		LOG("n %u ,write_reqs %u,threshold %u \n", n, write_reqs, CH_DIRTY_CACHE_ENTRIES_THRESHOLD);
		n -= CH_DIRTY_CACHE_ENTRIES_THRESHOLD * write_reqs;assert(n>=0);LOG("%d dirty entries with no write_pending/inactive req\n", n);
		if(n < CH_DIRTY_CACHE_ENTRIES_THRESHOLD){LOG("not enough for a new request. return\n");
			return 0;
		}
		assert(n>=CH_DIRTY_CACHE_ENTRIES_THRESHOLD);LOG("return successfuly\n");
		return 1;
	}

	/*failure*/
	LOG("not enough for a new request. return\n");
	return 0;
}

/**
 * req has a free chunk_p pointed by specific
 * tm_reclaim_chunk_p is a reserved entry previously allocated for the same chunk
 * copy details to regular entry, and continue processing as required
 */
void
chm_handle_tm_reclaim_entry_copy(ch_buffer_entry* reserve_entry, ch_buffer_entry* free_entry){LOG("starting. reserve_entry %u (idx %u)\n", reserve_entry->idx);
	uint32_t idx = reserve_entry->idx;LOG("reserve_entry->idx %u\n", reserve_entry->idx);
	reserve_entry->reserved = 0; /*reserved now becomes regular*/
	chm_init_buf_entry_simple(free_entry->idx, 1);/*empty chunk is now the reserved*/
}

/**
 * After a cache flush, make written entries clean
 *
 */
void chm_clean_written_entries(req_t* req){
	int i, n = 0;
	ch_buffer_entry* entry;
	LOG("starting. should clean %u entries\n", CH_DIRTY_CACHE_ENTRIES_THRESHOLD);

	/* count written entries*/
	for(i=0; i<CH_TM_RECLAIM_BUFFER_ENTRIES;i++){
		entry = &(chm.buffer_entry[i]);
		LOG("entry %d state %u (root_offset %u)\n", i, entry->state, entry->root_offset);
		/*is this a relevant entry?*/
		switch(entry->state){
			case CH_BUF_ENTRY_WRITTEN: /* part of a pending flash write operation*/
//				entry->state = CH_BUF_ENTRY_CLEAN;
				ch_set_bitmap_chunk(entry->root_offset, 0); /*old address is now obsolete*/

				chm.chunk[entry->root_offset].version++;
				entry->sent = 0;
				CH_ADDR_SET_PAGE(chm.chunk[entry->root_offset].address, chm.open_eu.address+chm.open_eu.offset);
				CH_ADDR_SET_OFFSET(chm.chunk[entry->root_offset].address, n);LOG("chunk root_offset %d page %u offset %u\n", n, CH_ADDR_GET_PAGE(chm.chunk[entry->root_offset].address), CH_ADDR_GET_OFFSET(chm.chunk[entry->root_offset].address));
//				LOG("now chunk page %u (expected %u) offset %u (expected %u). first page addr %u\n",
//						CH_ADDR_GET_PAGE(chm.chunk[entry->root_offset].address),chm.open_eu.address+chm.open_eu.offset, CH_ADDR_GET_OFFSET(chm.chunk[entry->root_offset].address), n,
//						((uint32_t*)(entry->buf))[0]);
				entry->address = chm.chunk[entry->root_offset].address;LOG("chm.chunk[%u].address %u\n", entry->root_offset, chm.chunk[entry->root_offset].address);
				ch_set_bitmap_chunk(entry->root_offset, 1); /*new address is now valid*/

				/* change count of requests and written pointers, set state accordingly*/
				LOG("entry %d written_req %u root_offset %u\n", i,entry->written_req, entry->root_offset);
				assert(entry->written_req == req);
				chm_handle_entry_clean_op(i);
				n++;

				/* send chunk back as hint*/
				ngssd_tentative_cached_hint_notify();
				break;
			default:
				break;
		}
	}

	assert(n>0);
	if(n!=CH_DIRTY_CACHE_ENTRIES_THRESHOLD){
		LOG("counted %u written entries. expected %u\n", n, CH_DIRTY_CACHE_ENTRIES_THRESHOLD);
		assert(n<=CH_DIRTY_CACHE_ENTRIES_THRESHOLD);
	}
}

/**
 * prepare the write buffer
 *
 * Assumption:
 * 1.chm mutex is locked when we get here
 */
void chm_prepare_write_buf(req_t* written_req){
	int i, n = 0;
	ch_buffer_entry* entry;
	LOG("starting\n");

	/* count written entries*/
	for(i=0; i<CH_TM_RECLAIM_BUFFER_ENTRIES;i++){
		entry = &(chm.buffer_entry[i]);LOG("entry %u state %u\n", entry->idx, entry->state);

		/*this is a relevant entry*/
		switch(entry->state){
			case CH_BUF_ENTRY_DIRTY:
				entry->state = CH_BUF_ENTRY_WRITTEN;
				entry->written_req = written_req;
//				printf("chm_prepare_write_buf - chunk %u entry->buf[1] %x\n", entry->idx, ((uint32_t*)(entry->buf))[1]);
				memcpy(&(chm.write_buf[n*CH_CHUNK_SIZE]), entry->buf, CH_CHUNK_SIZE);
				LOG("copied chunk %d to offset %u (first entry is page addr %u)\n", i, n,((uint32_t*)(&(chm.write_buf[n*CH_CHUNK_SIZE])))[0]);
				LOG("set spare root offset %u to %u\n", n, entry->root_offset);
				CH_BUF_SET_ROOT_OFFSET(chm.write_buf, n, entry->root_offset);

				n++;
				break;
			default:
				break;
		}

		if(n>=CH_DIRTY_CACHE_ENTRIES_THRESHOLD){
			break;
		}
	}

	assert(n>0);
	if(n>0){
		if(n!=CH_DIRTY_CACHE_ENTRIES_THRESHOLD) LOG("counted %u dirty entries. expected %u\n", n, CH_DIRTY_CACHE_ENTRIES_THRESHOLD);
		assert(n==CH_DIRTY_CACHE_ENTRIES_THRESHOLD);
	}
}

uint32_t chm_get_root_offset_by_ch_addr(ch_addr chunk_addr){
	uint32_t i;
	LOG("chunk_addr %x\n", chunk_addr);

	if(chunk_addr == CH_ADDR_EMPTY){
		return CH_ROOT_OFFSET_EMPTY;
	}

	/* find first entry that references this address*/
	for(i = 0;i<CH_CHUNK_COUNT;i++){
		LOG("CH_CHUNK_COUNT %u chunk %d\n",CH_CHUNK_COUNT,  i);
		if(chm.chunk[i].address != CH_ADDR_EMPTY){
			LOG("chm.chunk[%u].address = %x\n", i, chm.chunk[i].address);
		}
		if(chm.chunk[i].address == chunk_addr){
			LOG("found chunk_addr %x in root_offset %u\n", chunk_addr, i);
			return i;
		}
	}

	/* default*/
	LOG("return CH_ROOT_OFFSET_EMPTY - not found chunk_addr %x.\n", chunk_addr);
	return CH_ROOT_OFFSET_EMPTY;
}

void
chm_cond_state_clean_write(ch_buffer_entry *entry){
	LOG("entry state %u\n", entry->state);
	switch(entry->state){
		case CH_BUF_ENTRY_CLEAN: /* in use, but identical to an on-flash copy */
			entry->state = CH_BUF_ENTRY_CLEAN_WRITE;
			LOG("entry state now CH_BUF_ENTRY_CLEAN_WRITE\n");
			break;
		case CH_BUF_ENTRY_CLEAN_WRITE: /* in use by write op, but identical to an on-flash copy. needed to prevent possible starvation of write request */
		case CH_BUF_ENTRY_DIRTY: /* in use and not stored on flash*/
		case CH_BUF_ENTRY_WRITTEN: /* part of a pending flash write operation*/
			LOG("no change to entry state\n");
			/* do nothing here*/
			break;
		case CH_BUF_ENTRY_ALLOCATED_OBS: /* same as ALLOCATED, but the same chunk was written to some other entry in the meantime (as hint...)*/
		case CH_BUF_ENTRY_ALLOCATED: /* empty, but will be filled when a read operation completes*/
		case CH_BUF_ENTRY_FREE:
		default:
			LOG("something bad happened!\n");
			assert(0);
	}
}

/**
 *
 * Assumptions:
 * 1. pre - chm lock acquired
 *
 */
void
chm_handle_cached_entry_read(ch_buffer_entry *chunk_p, req_t* req){
	chm_specific_t* specific = CH_REQ_GET_SPECIFIC(req);

	LOG("lba %u. read cached chunk (idx %u state %u)\n", specific->lba, specific->chunk_p->idx, specific->chunk_p->state);
	switch(specific->chunk_p->state){
		case CH_BUF_ENTRY_WRITTEN: /* part of a pending flash write operation*/
		case CH_BUF_ENTRY_CLEAN: /* in use, but identical to an on-flash copy */
		case CH_BUF_ENTRY_CLEAN_WRITE:
		case CH_BUF_ENTRY_DIRTY: /* in use and not stored on flash*/
			chm_entry_tm_list_cond_push(chunk_p, req);
			specific->read_res = CH_GET_PAGE(specific->chunk_p->buf, specific->lba); /* response = pointer to page address*/
#ifdef PROFILING
			/* incremenet hint accessed if necessary
			 * read accessed are updated on flash read (accessed for sure...)*/
			if(!is_part_of_reclaim(req)){
			   if(specific->chunk_p->hinted)
			     hint_accessed++;
                           else
   			     read_accessed++;
			}
#endif
			LOG("CHM_INITIAL. return read result for lba %u from cached chunk. (specific->read_res) %u\n", specific->lba, specific->read_res);
			break;
		case CH_BUF_ENTRY_FREE:
			LOG("CH_BUF_ENTRY_FREE.\n");
		default:
			assert(0);
	}
}

void
chm_handle_defered(req_t* req){
	/* done with everything. move on to deferred*/
	if (chm.chmDeferred != 0) {
		pthread_mutex_lock(&(chm.defer_mutex));
		req->caller_req = chm.chmDeferred;
		chm.chmDeferred = chm.chmDeferred->next;
		chm.chmDeferred->prev = 0;
		req->next       = 0;
		pthread_mutex_unlock(&(chm.defer_mutex));

		(req->caller_req->handler)(req->caller_req,req);
	} else {
		/*free data structs of chm request */
		free(req->specific);
		free(req);
	}
}

bool_t
chm_root_offset_not_taken(uint32_t root_offset){
	uint32_t i;
	for(i=0; i < CH_TM_RECLAIM_BUFFER_ENTRIES; i++){
		if(chm.buffer_entry[i].root_offset == root_offset){//printf("chm.buffer_entry[%u].root_offset == %u\n", i, root_offset);
			return 0;
		}
	}

	return 1;
}

/**********************************************
									Recalamtion
 **********************************************/
/*
 * mark a reclaimed buffered chunk as WRITTEN
 */
void
chm_mark_reclaimed_chunk_written(uint32_t root_offset){
	uint32_t i;

	for(i=0;i < CH_TM_RECLAIM_BUFFER_ENTRIES; i++){
		if(chm.buffer_entry[i].root_offset == root_offset){
			chm.buffer_entry[i].state = CH_BUF_ENTRY_WRITTEN;
		}
	}
}
/**
 *  fill details of read buf - root offsets+chunk_offset
 *
 *  Assumption:
 *  1. read buffer is filled with read page
 *  2. rec_eu offset is incremeneted only here
 */
void chm_rec_fill_read_buf(){
	ch_addr chunk_addr = 0;
	uint32_t i;
	uint32_t root_offset;
	LOG("starting\n");

	chm.rec_eu.read_buf.address = chm.rec_eu.address+chm.rec_eu.offset;
	LOG("chm.rec_eu.read_buf.address %u (chm.rec_eu address %u offset %u)\n", chm.rec_eu.read_buf.address, chm.rec_eu.address, chm.rec_eu.offset);
	chm.rec_eu.offset++;
	assert(chm.rec_eu.offset <= NAND_PAGES_PER_ERASE_UNIT);
	CH_ADDR_SET_PAGE(chunk_addr, chm.rec_eu.read_buf.address);

	for(i=0; i<CH_CHUNKS_PER_PAGE;i++){
		CH_ADDR_SET_OFFSET(chunk_addr, i);
		root_offset = CH_BUF_GET_ROOT_OFFSET(chm.rec_eu.read_buf.buf, i);

		/* empty root offset, partial page written
		 * (possible during previous reclaim)*/
		if(root_offset == CH_ROOT_OFFSET_EMPTY){
#ifdef Fanatic
			LOG("offset %u read is empty chunk. should all be empty now\n",i);
			for(; i<CH_CHUNKS_PER_PAGE;i++){
				CH_ADDR_SET_OFFSET(chunk_addr, i);
				root_offset = CH_BUF_GET_ROOT_OFFSET(chm.rec_eu.read_buf.buf, i);
				if(root_offset != CH_ROOT_OFFSET_EMPTY){
					LOG("offset %u root_offset %u not empty as expected\n",i,root_offset);
					assert(root_offset == CH_ROOT_OFFSET_EMPTY);
				}
			}
#endif
			break;
		}

		LOG("root_offset %u (addr %u)chm_get_root_offset_by_ch_addr(chunk_addr %u)\n", root_offset, (CH_ROOT_OFFSET_EMPTY==root_offset)?IDX_EMPTY:chm.chunk[root_offset].address, chunk_addr);
		/* check if chunk is valid (still pointed by root map).
		 * if so save its offset, and if it is buffered mark it as written*/
		if(chm.chunk[root_offset].address == chunk_addr){LOG("chunk still valid. chm.rec_eu.read_buf.root_offset[%u] = root_offset %u\n", i, root_offset);
			chm.rec_eu.read_buf.root_offset[i] = root_offset;
//			/*if chunk is buffered mark as WRITTEN so it is not changed*/
//			chm_mark_reclaimed_chunk_written(root_offset);
		}
		else{LOG("chunk not valid. dont put in read buf\n");
			assert(ch_get_bitmap_chunk(root_offset));
			chm.rec_eu.read_buf.root_offset[i] = CH_ROOT_OFFSET_EMPTY;
		}
		LOG("chm.rec_eu.read_buf.root_offset[%d] %u\n", i, chm.rec_eu.read_buf.root_offset[i]);
		assert(chm.rec_eu.read_buf.root_offset[chm.rec_eu.read_buf.chunk_offset] < CH_CHUNK_COUNT || chm.rec_eu.read_buf.root_offset[chm.rec_eu.read_buf.chunk_offset] == CH_ROOT_OFFSET_EMPTY);
	}
}

bool_t chm_is_read_buf_process_done(){
	LOG("starting. chm.rec_eu.read_buf.chunk_offset %u addr %u\n", chm.rec_eu.read_buf.chunk_offset, chm.rec_eu.read_buf.address);
	assert(chm.rec_eu.read_buf.chunk_offset <= CH_CHUNKS_PER_PAGE);
	return (chm.rec_eu.read_buf.chunk_offset == CH_CHUNKS_PER_PAGE || chm.rec_eu.read_buf.chunk_offset == 0);
}

/**
 * when done reading all pages in reclaim, check if we reclaimed any
 * for the last write buf iteration
 */
bool_t
chm_is_write_buf_empty(){
	return (chm.rec_eu.write_buf.chunk_offset == 0);
}

bool_t chm_is_write_buf_full(){
//	LOG("starting. chm.rec_eu.write_buf.chunk_offset %u addr %u\n", chm.rec_eu.write_buf.chunk_offset, chm.rec_eu.write_buf.address);
	return (chm.rec_eu.write_buf.chunk_offset == CH_CHUNKS_PER_PAGE);
}

/**
 * fill details of write buf - more chunks (if there are any), root offsets+chunk_offset
 *
 * Assumptions:
 * 1. read buf is full
 */
void chm_rec_fill_write_buf(){
	ch_addr chunk_addr;
	int i;
	uint32_t root_offset;
	LOG("starting. chm.rec_eu.read_buf.chunk_offset %u\n", chm.rec_eu.read_buf.chunk_offset);
//	CH_ADDR_SET_PAGE(chunk_addr, chm.rec_eu.address+chm.rec_eu.offset)
	for(; chm.rec_eu.read_buf.chunk_offset < CH_CHUNKS_PER_PAGE;chm.rec_eu.read_buf.chunk_offset++){
		/*write buf full -> return*/
		if(chm_is_write_buf_full()){
			return;
		}

//		CH_ADDR_SET_OFFSET(chunk_addr, i);
		/* chunk is valid. copy it to write buf, and mark its old root offset*/
		if(chm.rec_eu.read_buf.root_offset[chm.rec_eu.read_buf.chunk_offset] != CH_ROOT_OFFSET_EMPTY){
			LOG("set write_buf chunk[%u] to read chunk from root_offset %u (chunk %u in read_buf)\n", chm.rec_eu.write_buf.chunk_offset, chm.rec_eu.read_buf.root_offset[chm.rec_eu.read_buf.chunk_offset], chm.rec_eu.read_buf.chunk_offset);
			chm.rec_eu.write_buf.root_offset[chm.rec_eu.write_buf.chunk_offset] = chm.rec_eu.read_buf.root_offset[chm.rec_eu.read_buf.chunk_offset];
			assert(chm.rec_eu.read_buf.root_offset[chm.rec_eu.read_buf.chunk_offset] < CH_CHUNK_COUNT
					   ||
					   chm.rec_eu.read_buf.root_offset[chm.rec_eu.read_buf.chunk_offset] == CH_ROOT_OFFSET_EMPTY);
//			LOG("chm.rec_eu.write_buf.root_offset[%u] = %x. addr=%x\n", chm.rec_eu.write_buf.chunk_offset, chm.rec_eu.write_buf.root_offset[chm.rec_eu.write_buf.chunk_offset], chm.chunk[chm.rec_eu.write_buf.root_offset[chm.rec_eu.write_buf.chunk_offset]].address);

			memcpy(&(chm.rec_eu.write_buf.buf[chm.rec_eu.write_buf.chunk_offset * CH_CHUNK_SIZE]),
					   &(chm.rec_eu.read_buf.buf[chm.rec_eu.read_buf.chunk_offset * CH_CHUNK_SIZE]),
					   CH_CHUNK_SIZE);

			CH_BUF_SET_ROOT_OFFSET(chm.rec_eu.write_buf.buf, chm.rec_eu.write_buf.chunk_offset, chm.rec_eu.write_buf.root_offset[chm.rec_eu.write_buf.chunk_offset]);
//			chm.rec_eu.write_buf.buf[NAND_SPARE_CHUNK_OFFSET_START+chm.rec_eu.write_buf.chunk_offset] = ((uint8_t)(chm.rec_eu.write_buf.root_offset[chm.rec_eu.write_buf.chunk_offset]));

			chm.rec_eu.write_buf.chunk_offset++;
		}
	}
}

bool_t chm_is_rec_eu_process_done(){
	LOG("starting. chm.rec_eu.offset %u\n", chm.rec_eu.offset);
	assert(chm.rec_eu.offset <= NAND_PAGES_PER_ERASE_UNIT);
	return (chm.rec_eu.offset == NAND_PAGES_PER_ERASE_UNIT);
}

void init_rec_buf(rec_buf* rb){
	int i;
	LOG("starting\n");

	rb->address = TM_ADDR_EMPTY;
	rb->chunk_offset = 0;
	for(i=0;i<NAND_TOTAL_SIZE;i++){
		rb->buf[i] = 0xff;
	}
	rb->chunk_offset = 0;
	for(i=0;i<CH_CHUNKS_PER_PAGE;i++){
		rb->root_offset[i] = CH_ADDR_EMPTY;
	}
}

void chm_init_rec_eu(){
	int i;
	LOG("starting\n");

	chm.rec_eu.address = CH_ADDR_EMPTY;
	chm.rec_eu.offset  = IDX_EMPTY;
	chm.rec_eu.blk_idx = IDX_EMPTY;

	init_rec_buf(&(chm.rec_eu.read_buf));
	init_rec_buf(&(chm.rec_eu.write_buf));
}

void chm_clean_rec_eu(){
	int i;
	LOG("starting\n");

	if(chm.rec_eu.blk_idx == IDX_EMPTY) return;

	LOG("chm.block[%u].state = CH_BLOCK_EMPTY (addr %u)\n", chm.rec_eu.blk_idx, chm.block[chm.rec_eu.blk_idx].address);
	chm.block[chm.rec_eu.blk_idx].state = CH_BLOCK_EMPTY;
	chm.block[chm.rec_eu.blk_idx].obs_count = CH_CHUNKS_PER_BLOCK;

	chm_init_rec_eu();
}

void chm_choose_block_to_reclaim(req_t *req){LOG("no more free blocks. choose block to reclaim\n");
	uint32_t i, obs_count = 0, blk_idx = IDX_EMPTY;

	chm_clean_rec_eu();
	/* find block to reclaim*/
	for(i=0; i< CH_BLOCK_COUNT;i++){
		LOG("chm.block[%u].state %u, obs_count %u\n", i, chm.block[i].state, chm.block[i].obs_count);
		if(chm.block[i].obs_count > obs_count
			 &&
			 chm.block[i].state == CH_BLOCK_FULL){
			obs_count = chm.block[i].obs_count;
			blk_idx = i;
		}
	}

	assert(blk_idx != IDX_EMPTY);
	chm.rec_eu.address = chm.block[blk_idx].address;
	chm.rec_eu.offset  = 0;
	chm.rec_eu.blk_idx = blk_idx;

	LOG("chose block in idx %u to reclaim (blk addr %u)\n", blk_idx, chm.rec_eu.address);

	/* find empty block*/
	blk_idx = IDX_EMPTY;
	for(i=0; i< CH_BLOCK_COUNT;i++){
		LOG("chm.block[%d].state %u\n", i, chm.block[i].state);
		if(chm.block[i].state == CH_BLOCK_EMPTY){
			blk_idx = i;
			break;
		}
	}
	assert(blk_idx != IDX_EMPTY);

	LOG("will write to empty block idx %u to reclaim (blk addr %u)\n", blk_idx, chm.block[blk_idx].address);
	chm_init_open_eu(blk_idx, req);
}

void chm_rec_clean_read_buf(void){
	init_rec_buf(&(chm.rec_eu.read_buf));
}

/**
 * a buffer entry in a given root_offset was reclaimed.
 * update its address to the new one from the reclaim req
 */
void
chm_update_reclaimed_cached_chunk(uint32_t root_offset, ch_addr new){
	int i;
	LOG("starting\n");
	for(i=0;i < CH_TM_RECLAIM_BUFFER_ENTRIES; i++){
		if(chm.buffer_entry[i].root_offset == root_offset){
			LOG("entry %d root_offset %u reclaimed. new %u\n", i, chm.buffer_entry[i].root_offset, new);
			chm.buffer_entry[i].address = new;
			break;
//			chm_handle_entry_clean_op(i);
		}
	}
}

/**
 * mark new chunks as used and point root mapping to them
 *
 */
void chm_rec_clean_write_buf(){
	int i;
	ch_addr new_addr, old_addr;
	LOG("starting\n");

	CH_ADDR_SET_PAGE(new_addr, chm.rec_eu.write_buf.address);
	for(i=0; i< CH_CHUNKS_PER_PAGE; i++){
		CH_ADDR_SET_OFFSET(new_addr, i);
		LOG("iteration %u - page addr %x offset %u. new_addr %x\n", i, chm.rec_eu.write_buf.address, i, new_addr);

		if(chm.rec_eu.write_buf.root_offset[i] == CH_ROOT_OFFSET_EMPTY){
#ifdef Fanatic
			for(; i< CH_CHUNKS_PER_PAGE; i++){
				if(chm.rec_eu.write_buf.root_offset[i] != CH_ROOT_OFFSET_EMPTY){
					LOG("ERROR chm.rec_eu.write_buf.root_offset[%u] %u\n", i, chm.rec_eu.write_buf.root_offset[i]);
				}
			}
#endif
			break;
		}

		/* set old addr obsolete */
		old_addr = chm.chunk[chm.rec_eu.write_buf.root_offset[i]].address;LOG("old_addr %x make obs. new_addr %x make valid\n", old_addr, new_addr);
		ch_set_bitmap_chunk(chm.rec_eu.write_buf.root_offset[i], 0);

		/*set new addr valid*/
		chm.chunk[chm.rec_eu.write_buf.root_offset[i]].address = new_addr;
		LOG("call ch_mark_bitmap - mark chunk addr in offset %x (%u) as valid\n", chm.chunk[chm.rec_eu.write_buf.root_offset[i]].address, chm.rec_eu.write_buf.root_offset[i]);
		ch_set_bitmap_chunk(chm.rec_eu.write_buf.root_offset[i], 1);
#ifdef Fanatic
		assert(CH_ADDR_GET_PAGE(new_addr) < NAND_PAGE_COUNT);
		assert(CH_ADDR_GET_OFFSET(new_addr) < CH_CHUNKS_PER_PAGE);
#endif

		/* mark in cache as well*/
		LOG("change relevant address in cached chunk (if there is one)\n");
		chm_update_reclaimed_cached_chunk(chm.rec_eu.write_buf.root_offset[i], new_addr);
	}

	/*finally clean everything*/
	init_rec_buf(&(chm.rec_eu.write_buf));
}

/**
 *
 * return 0 if successful in allocating new block
 *        1 on failure
 */
error_t chm_allocate_new_empty_block(){
	int blk_idx, n = 0;
	LOG("starting. CH_BLOCK_COUNT %u\n", CH_BLOCK_COUNT);

	/* count free blocks*/
	for(blk_idx=0; blk_idx< CH_BLOCK_COUNT; blk_idx++){
		LOG("blk idx %u, state %u\n", blk_idx, chm.block[blk_idx].state);
		if(chm.block[blk_idx].state == CH_BLOCK_EMPTY){
			LOG("blk idx %u is CH_BLOCK_EMPTY\n", blk_idx);
			n++;
			/* if more than one, allocate the first
			 * (always leave one spare)*/
			if(n>1){
				/* fill with new block details*/
				chm_init_open_eu(blk_idx, 0);
				LOG("new open_eu address %u blk_idx %u. state %u\n", chm.open_eu.address, chm.open_eu.blk_idx, chm.block[blk_idx].state = CH_BLOCK_PARTIAL);
				return 0;
			}
		}
	}

	return 1;
}

/*****************************************
					general functions
 *****************************************/

//void chm_entry_chm_list_pop_specific(req_t* chm_req);

/**
 * handle address update to cached chunk
 *
 */
void
chm_handle_initial_write(req_t* req){LOG("starting req %x entry %u\n", req, CH_REQ_GET_SPECIFIC(req)->chunk_p->idx);
	chm_specific_t* specific = CH_REQ_GET_SPECIFIC(req);

	switch(specific->chunk_p->state){
		case CH_BUF_ENTRY_ALLOCATED:
		case CH_BUF_ENTRY_ALLOCATED_OBS:
			assert(0); /* shouldn't happen. chunk should be cached and ready for write/dirty?*/
		case CH_BUF_ENTRY_CLEAN:
		case CH_BUF_ENTRY_CLEAN_WRITE:
		case CH_BUF_ENTRY_DIRTY:
			/* chunk is cached. write to it*/
			LOG("CHM_INITIAL. set lba %u to addr %u in chunk\n", specific->lba, specific->new_lba_page);
			ch_set_new_lba(specific->chunk_p->buf, specific->lba, specific->new_lba_page);
			LOG("call chm_entry_tm_list_pop\n");
			chm_entry_tm_list_pop(specific->chunk_p, req);
			specific->chunk_p->tm_req_count--;

#ifdef Fanatic
			{
				list_t *p = specific->chunk_p->tm_pending_list;
				int n = 0;
				LOG("tm_pending_list for chunk_p:\n");
				while(p){
					LOG("			p->req %x, magic_num %u\n", p->req, (specific->rw == CHM_RECLAIM)?IDX_EMPTY:((tm_specific_t*)(p->req->specific))->cdb->magic_num);
					n++;
					p = p->next;
				}
				if(n!= specific->chunk_p->tm_req_count){
					list_t *p = specific->chunk_p->tm_pending_list;
					LOG("list contains %u requests. exepcted %u\n", n, specific->chunk_p->tm_req_count);
					assert(specific->chunk_p->tm_req_count == n);
				}
			}
#endif
			LOG("call CHM_WRITE_DONE\n");
			return;
		default:
			assert(0);
	}
	assert(0);
}

/**
 * Is write_buf available for a pending write request
 *
 */
bool_t
chm_is_write_buf_available(){
	return (chm.write_pending != 0 && chm_is_write_in_progress(0) == 0);
}

/*****************************************************
   				 		New code functions
 *****************************************************/
bool_t
chm_is_chunk_flash_accessed(i){
	/* state implying unavailable*/
	if(chm.buffer_entry[i].state == CH_BUF_ENTRY_WRITTEN /* being written*/
		 ||
		 chm.buffer_entry[i].state == CH_BUF_ENTRY_ALLOCATED /* being read*/){
		return 1;
	}

	return 0;
}

/**
 *
 * @return 1 if made regular
 *         0 if not
 */
bool_t
chm_update_reserve_cache_entry(ch_buffer_entry* reserve_entry, ch_buffer_entry* free_entry){LOG("starting. reserve idx %u\n", reserve_entry->idx);
		if(free_entry !=0){LOG("free_entry idx %u\n", free_entry->idx);
			chm_handle_tm_reclaim_entry_copy(reserve_entry, free_entry);
                        chm.reserved_used--;
			return 1;
		}
		else if(!chm_is_chunk_flash_accessed(reserve_entry->idx)
				    &&
				    reserve_entry->tm_pending_list == 0
				    &&
				    reserve_entry->state != CH_BUF_ENTRY_DIRTY){
			/* remeber to keep it reserved*/
			chm_init_buf_entry_simple(reserve_entry->idx, 1);LOG("reserved entry %u. no tm/chm pending. make entry fresh\n", reserve_entry->idx);
                        chm.reserved_used--;
		}

		return 0;
}

void
chm_update_reserve_cache(){
	ch_buffer_entry* free_entry ;
	int i;
	LOG("starting\n");
	for(i=0; i<CH_TM_RECLAIM_BUFFER_ENTRIES;i++){
		if(!chm_is_chunk_reserved(i)){
			continue;
		}

		/*FREE entry*/LOG("entry %d state %u\n", i, chm.buffer_entry[i].state);
		if(chm.buffer_entry[i].state == CH_BUF_ENTRY_FREE)
			continue;

		free_entry = chm_get_free_chunk(0);
//		if(free_entry == 0){LOG("no free chunk\n");
//			break;
//		}

		chm_update_reserve_cache_entry(&(chm.buffer_entry[i]), free_entry);
	}
}

/**
 * clean flushed (WRITTEN) chunk entry once program is done
 *
 */
void
chm_handle_entry_clean_op(uint32_t idx){
	ch_buffer_entry* entry = &(chm.buffer_entry[idx]), *free_entry;

	chm.buffer_entry[idx].written_req = 0;
	if(entry->tm_req_count == 0){assert(entry->tm_pending_list == 0);
		entry->state = CH_BUF_ENTRY_CLEAN;LOG("entry %u. req_count 0. state ~> CH_BUF_ENTRY_CLEAN\n", idx);
		entry->written_req = 0;
		/* 1. regular entry is simply clean*/
		/* 2. reserve entry with no tm_pending is moved to free regular cache chunk
		 *    (if not possible, becomes fresh, so as not to complex pending regular requests)*/
		if(chm_is_chunk_reserved(idx)){LOG("reserve entry %u.\n", idx);
			free_entry = chm_get_free_chunk(0);
			chm_update_reserve_cache_entry(&(chm.buffer_entry[idx]), free_entry);
		}
	}
	else{
		assert(entry->tm_pending_list != 0);
		entry->state = CH_BUF_ENTRY_CLEAN_WRITE;LOG("entry %u. reqs are pending. state ~> CH_BUF_ENTRY_CLEAN_WRITE\n", idx);
	}
}

bool_t
chm_is_reclaimed_page_obsolete(void){
	uint32_t page_address   = chm.rec_eu.address+chm.rec_eu.offset;
	uint32_t page_offset_in_block = page_address % NAND_PAGES_PER_ERASE_UNIT;

	ch_block* block = chm_get_block_by_address(chm.rec_eu.address);

	uint32_t i;
	for(i=0;i<CH_PAGE_BITMAP_SIZE;i++){
		if(block->page[page_offset_in_block].bitmap[i] != 0x00){
			return 0;
		}
	}

	return 1;
}

/**
 * Get cached chunk pointer by lba
 *
 * @return buffer_entry pointer if successful
 *         0 if relevant chunk is not cached
 */
ch_buffer_entry* chm_get_cached_chunk(req_t* req){
	chm_specific_t* specific = CH_REQ_GET_SPECIFIC(req);
	int i;
	uint32_t lba = specific->lba;
	uint32_t lba_ch_root_offset = CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(lba);
	LOG("specific->lba %u - see if chunk of root_offset %u is cached\n", lba, lba_ch_root_offset);
	ch_buffer_entry* entry;

//	for(i=0; i<CH_BUFFER_ENTRIES;i++){
	for(i=0; i<CH_TM_RECLAIM_BUFFER_ENTRIES;i++){
		entry = &(chm.buffer_entry[i]);
		LOG("entry[%d] (reserve %u page %u offset %u, root_offset %u) (looking for %u), state %u hinted %s\n",
				i, entry->reserved, CH_ADDR_GET_PAGE(entry->address),CH_ADDR_GET_OFFSET(entry->address), entry->root_offset, lba_ch_root_offset, entry->state, entry->hinted?"YES":"NO");
#ifdef Fanatic
		{
			list_t* p = entry->tm_pending_list;
//			LOG("  tm_pending head node %x\n", entry->tm_pending_list);
			while(p){

				if(entry->root_offset != CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(TM_REQ_GET_SPECIFIC(p->req)->curr_lba)){
					LOG("entry->root_offset %u expected %u from req %x\n", entry->root_offset, CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(TM_REQ_GET_SPECIFIC(p->req)->curr_lba), p->req);
					assert(entry->root_offset == CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(TM_REQ_GET_SPECIFIC(p->req)->curr_lba));
				}
				p = p->next;
			}
		}
#endif
		if(entry->root_offset !=lba_ch_root_offset)
			continue;

		/*this is a relevant entry*/
		switch(entry->state){
		  case CH_BUF_ENTRY_ALLOCATED_OBS: /* same as ALLOCATED, but the same chunk was written to some other entry in the meantime (as hint...)*/
		  	entry->state = CH_BUF_ENTRY_FREE;
		  	break;
			case CH_BUF_ENTRY_FREE:
				/*this is an old entry*/
				break;
			case CH_BUF_ENTRY_ALLOCATED: /* empty, but will be filled when a read operation completes*/
			case CH_BUF_ENTRY_CLEAN: /* in use, but identical to an on-flash copy */
			case CH_BUF_ENTRY_CLEAN_WRITE: /* in use by write op, but identical to an on-flash copy. needed to prevent possible starvation of write request (if simply CLEAN, may get evacuated from cache without the write op ever using it)*/
			case CH_BUF_ENTRY_WRITTEN: /* part of a pending flash write operation*/
			case CH_BUF_ENTRY_DIRTY: /* in use and not stored on flash*/
				LOG("got cached chunk %d entry->root_offset %u\n", i, entry->root_offset);
				return entry;
				break;
			default:
				assert(0);
		}
	}

	/*failure*/
	return 0;
}

bool_t
chm_is_entry_available(int ch_idx, req_t* req){
	assert(ch_idx<CH_TM_RECLAIM_BUFFER_ENTRIES && ch_idx>=0);
	/*regular requests can't access reserved tm_reclaim entries*/
	if(req != 0
		 &&
		 (chm_is_req_regular_chunk_reserve(req, &(chm.buffer_entry[ch_idx])) || chm_is_req_hint_chunk_reserve(req, &(chm.buffer_entry[ch_idx])))){
		return 0;
	}
	LOG("entry[%d] state %u root_offset %u tm_pending_list %x state %u written_req %x reserved %u\n",
			ch_idx, chm.buffer_entry[ch_idx].state, chm.buffer_entry[ch_idx].root_offset, chm.buffer_entry[ch_idx].tm_pending_list, chm.buffer_entry[ch_idx].state, chm.buffer_entry[ch_idx].written_req, chm.buffer_entry[ch_idx].reserved);

	/* expecting future tm requests write*/
	if (chm.buffer_entry[ch_idx].tm_pending_list != 0){//LOG("%u tm_pending req\n", chm.buffer_entry[i].tm_req_count);assert(chm.buffer_entry[i].tm_req_count> 0 );
		return 0;
	}

	/* state implying unavailable*/
	if(chm_is_chunk_flash_accessed(ch_idx)
		 ||
		 chm.buffer_entry[ch_idx].state == CH_BUF_ENTRY_DIRTY /*will be written. not free for other chunks*/){
		return 0;
	}
	LOG("entry available. return 1\n");
	return 1;
}
/**
 * check if relevant chunk is already cached.
 *
 */
uint32_t
chm_get_cache_chunk(req_t* req){
	ch_buffer_entry* chunk_p;

	/* see if chunk is already in cache*/
	chunk_p = chm_get_cached_chunk(req);LOG("chunk_p %x\n", chunk_p);
	if(chunk_p != 0){
		/* chunk is cached but not availble (ALLOCATED, WRITTEN, reserve but req is rgular)
		 * return empty-handed (NOTE - can't allocate an other free chunk)*/
		if(chm_is_chunk_flash_accessed(chunk_p->idx)
				||
				chm_is_req_regular_chunk_reserve(req, chunk_p)){
			return IDX_EMPTY;
		}

		return chunk_p->idx;
	}

	/* find available new cache chunk
	 * iterate reserve entries first, then regular*/
	chunk_p = chm_get_free_chunk(req);
	if(chunk_p != 0)
		return chunk_p->idx;

	/*none found*/
	return IDX_EMPTY;
}

void
chm_update_buffer_pending_queue(void){
	ch_buffer_entry* chunk_p;
	req_t* req;
	list_t *node = chm.buffer_pending;
	while(node != 0){
		req = node->req;
		node = node->next;

		/* for regular enqueued requests decrement unforced coutner*/
		if(CH_REQ_GET_SPECIFIC(req)->enq_reason != CHM_ENQUEUE_REASON_BUFFER_TAKEN){
			continue;
		}

		/* see if chunk is already in cache*/
		chunk_p = chm_get_cached_chunk(req);LOG("chunk_p %x\n", chunk_p);

		/* chunk not flash-accessed any more*/
		if(chunk_p != 0
			 &&
			 chm_is_chunk_flash_accessed(chunk_p->idx)){
			continue;
		}

		/* chunk is not flash accessed (or not cached at all)
		 * update accordingly */
		chm.unforced_counter++;assert(chm.unforced_counter >= 0);
		CH_REQ_GET_SPECIFIC(req)->enq_reason = CHM_ENQUEUE_REASON_NO_BUFFER;
	}
}

bool_t
chm_is_last_reclaimed_page_exhausted(void){
	return (chm_is_rec_eu_process_done()
				 &&
				 chm_is_read_buf_process_done()
				 &&
				 chm_is_write_buf_empty());
}

/*****************************************************
 				 		Hint functions
 *****************************************************/
bool_t
chm_is_cache_chunk_hint_accessible(ch_buffer_entry* chunk_p){
	/**/
	return ((chunk_p->state == CH_BUF_ENTRY_CLEAN
			     ||
			     chunk_p->state == CH_BUF_ENTRY_CLEAN_WRITE)
			     &&
			     chunk_p->sent == 0);
}

uint32_t
chm_get_cache_chunk_hint_accessible_idx(void){
	uint32_t i;
	for(i=0; i<CH_TM_RECLAIM_BUFFER_ENTRIES;i++){
		if(chm_is_cache_chunk_hint_accessible(&(chm.buffer_entry[i]))){
			return i;
		}
	}

	return IDX_EMPTY;
}

bool_t
chm_handle_hint_cached_read(req_t* req){
	uint32_t idx = chm_get_cache_chunk_hint_accessible_idx();
	LOG("read cached hint back to host\n");
	if(idx != IDX_EMPTY){
		/* hint read*/
		ch_buffer_entry* chunk_p = &(chm.buffer_entry[idx]);

		chunk_p->sent = 1;
//		printf("chm_handle_hint_cached_read() - chunk %u chunk_p->buf[0] %x [1] %x\n", idx, ((uint32_t*)(chunk_p->buf))[0], ((uint32_t*)(chunk_p->buf))[1]);
		memcpy(CH_REQ_GET_SPECIFIC(req)->hint->data, chunk_p->buf, CH_CHUNK_SIZE);
//		printf("chm_handle_hint_cached_read() - chunk %u data[0] %x [1] %x\n", idx, ((uint32_t*)(CH_REQ_GET_SPECIFIC(req)->hint->data))[0], ((uint32_t*)(CH_REQ_GET_SPECIFIC(req)->hint->data))[1]);
		CH_REQ_GET_SPECIFIC(req)->hint->version = chm.chunk[chunk_p->root_offset].version;
		CH_REQ_GET_SPECIFIC(req)->hint->start_lba = chunk_p->root_offset * CH_SECTORS_PER_CHUNK; /*first lba in chunk*/
		CH_REQ_GET_SPECIFIC(req)->state = CHM_HINT_DONE;
		LOG("root_offset %u (ch idx %u) start_lba %u version %u last lba %u\n",
				chunk_p->root_offset, idx,CH_REQ_GET_SPECIFIC(req)->hint->start_lba, CH_REQ_GET_SPECIFIC(req)->hint->version,
				((uint32_t*)(CH_REQ_GET_SPECIFIC(req)->hint->data))[CH_SECTORS_PER_CHUNK-1]);
		req_activate(req, 0);
		return 1;
	}

	return 0;
}

void
chm_handle_hint_flash_read(req_t* req){
	/* device idle?*/
	LOG("read chunk from flash");
	if(ngssd_is_idle()){
		CH_REQ_GET_SPECIFIC(req)->state = CHM_NANDBUS_READ_READY;
		nandbusAlloc(req);
		return;
	}

	/*device not idle. return req without doing anything*/
	CH_REQ_GET_SPECIFIC(req)->state = CHM_DONE;
	req_activate(req,0);
}
/**
 * write hint to  cached chunk of previously uncached chunk
 * if hint version number < chm current version number, do nothing
 * otherwise, fill chukn accordingly, make it CLEAN and return
 */
void
chm_handle_hint_cached_write(req_t* req){
	ch_buffer_entry* chunk_p;
	LOG("starting\n");

	/* chunk already cached*/
	chunk_p = chm_get_cached_chunk(req);
	if(chunk_p != 0){
		LOG("chunk is already cached. return\n");
		return;
	}

	uint32_t root_offset = CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(CH_REQ_GET_SPECIFIC(req)->lba);
	LOG("write hint root_offset %u (version %u) (last data %u)\n", root_offset, CH_REQ_GET_SPECIFIC(req)->hint->version, ((uint32_t*)(CH_REQ_GET_SPECIFIC(req)->hint->data))[CH_SECTORS_PER_CHUNK-1]);
	/* not the latest version. nothing to do here */
	if(CH_REQ_GET_SPECIFIC(req)->hint->version < chm.chunk[root_offset].version){
		LOG("hint has older version. return\n");
		return;
	}

	/*get free chunk*/
	chunk_p = chm_get_free_chunk(req);;

	/*no place to store hint*/
	if(chunk_p == 0){
		LOG("no free cache. return\n");
		return;
	}

	CH_REQ_GET_SPECIFIC(req)->chunk_p = chunk_p;
	chunk_p->root_offset = root_offset;
	chm.chunk[chunk_p->root_offset].version = CH_REQ_GET_SPECIFIC(req)->hint->version;
	chunk_p->state  = CH_BUF_ENTRY_CLEAN;
	chunk_p->address = chm.chunk[root_offset].address;
//	printf("chm_handle_hint_cached_write() chunk %u is hinted\n", chunk_p->idx);
	chunk_p->hinted  = 1;

//	memcpy(chunk_p->buf, CH_REQ_GET_SPECIFIC(req)->hint->data, sizeof(HINT_DATA_SIZE));

//	printf("HINT_DATA_SIZE %u CH_CHUNK_SIZE %u\n", HINT_DATA_SIZE, CH_CHUNK_SIZE);
	uint32_t k;
	for(k=0;k<HINT_DATA_SIZE;k++){
		chunk_p->buf[k] = CH_REQ_GET_SPECIFIC(req)->hint->data[k];
	}
//	{
//		uint32_t k;
//		for(k=0; k < CH_SECTORS_PER_CHUNK;k++){
//			if(((uint32_t*)(CH_REQ_GET_SPECIFIC(req)->hint->data))[k] != IDX_EMPTY){
//				LOG("hint[%u] %u chunk[%u] %u\n", k, ((uint32_t*)(CH_REQ_GET_SPECIFIC(req)->hint->data))[k],
//						k, ((uint32_t*)(chunk_p->buf))[k]);
//				assert(((uint32_t*)(CH_REQ_GET_SPECIFIC(req)->hint->data))[k] == ((uint32_t*)(chunk_p->buf))[k]);
//			}
//		}
//	}

	return;
}

/*****************************************************
   				 		Functions
 *****************************************************/

/**
 * init chm
 *
 */
void chmInit(void){
  int i, chip_idx;
  uint32_t chip_start_addr, blk_idx = 0;

  /*buffer entries*/
  for(i=0; i<CH_TM_RECLAIM_BUFFER_ENTRIES; i++){
  	/*set entries (including initial tm_reclaim reserved)*/
  	chm_init_buf_entry_simple(i, i>= CH_BUFFER_ENTRIES);
  }
  chm.reserved_used = 0;
  chm.buffer_pending = 0;
  chm.unforced_counter = 0;
  chm.resource_pending = 0;

  /* allocate first blocks in every chip to chunk manager*/
  printf("NAND_CHIPS_PER_BUS %u NAND_BUSES %u NAND_CHIP_COUNT %u NAND_PAGES_PER_BUS %u NAND_PAGES_PER_CHIP %u\n",
  		    NAND_CHIPS_PER_BUS , NAND_BUSES , NAND_CHIP_COUNT , NAND_PAGES_PER_BUS , NAND_PAGES_PER_CHIP);
	printf("HINT_PAYLOAD_SIZE %u NAND_PAGE_SIZE / 16 %u\n", HINT_PAYLOAD_SIZE, NAND_PAGE_SIZE / 16);

  for(chip_idx = 0; chip_idx<NAND_CHIP_COUNT;chip_idx++ ){
  	chip_start_addr = chip_idx * NAND_PAGES_PER_CHIP;
  	printf("ch chip_start_addr %u CH_BLOCKS_PER_CHIP %u\n", chip_start_addr, CH_BLOCKS_PER_CHIP);
		for(; blk_idx<(chip_idx+1)*CH_BLOCKS_PER_CHIP; blk_idx++){
			chm.block[blk_idx].address = chip_start_addr+ (blk_idx % CH_BLOCKS_PER_CHIP)* NAND_PAGES_PER_ERASE_UNIT;

			memset(&(chm.block[blk_idx].page), 0x00, CH_PAGE_BITMAP_SIZE * NAND_PAGES_PER_ERASE_UNIT);
			chm.block[blk_idx].obs_count = CH_CHUNKS_PER_BLOCK;
			chm.block[blk_idx].state     = CH_BLOCK_EMPTY;
			printf("chm.block[%d].address %u\n", blk_idx, chm.block[blk_idx].address);
		}
  }

  /*first open eu is 0*/
  chm_init_open_eu(0, 0);

  for(i=0; i<CH_CHUNK_COUNT; i++){
		chm.chunk[i].address = CH_ADDR_EMPTY;
		chm.chunk[i].version = 0;
	}

  /*write buf*/
	LOG("chmInit() - chm.chunk[0].address %x\n", chm.chunk[0].address);
  memset(&(chm.write_buf), 0xff, NAND_TOTAL_SIZE);
  chm.write_pending = 0;

  /*reclamation*/
	chm_init_rec_eu();

	pthread_mutex_init(&(chm.mutex), NULL);
	chm.mutex_counter = 0;

	chm.chmDeferred = 0;

	pthread_mutex_init(&(chm.defer_mutex), NULL);
//	chm.queue = 0;
//	chm.current = 0;LOG("chm.current 0\n");
}

void chmHandler(req_t* req, void* response);

/**
 * allocate request with chm_specific_t data
 */
void chmAlloc(req_t* caller_req) {
  req_t* req;
  req = (req_t*) malloc(sizeof(req_t));

  /* start allocating*/
  chm_specific_t* specific;
  specific = (chm_specific_t*) malloc(sizeof(chm_specific_t));

  /* allocation successful*/
  if (req != 0 && specific != 0) {
    req->handler    = chmHandler;
    req->caller_req = caller_req;
    req->specific   = specific;
    req->next       = 0; // not in any list
    req->prev       = 0; // not in any list
//    LOG("tmAlloc() - call caller handler\n");
    (caller_req->handler)(caller_req, req);
    return;
  }

  /* allocation failure - defer*/
  if (req != 0)      free(req);
  if (specific != 0) free(specific);

  pthread_mutex_lock(&(chm.defer_mutex));
  caller_req->prev = chm.chmDeferred;
  caller_req->next = 0;
  if(chm.chmDeferred != 0) chm.chmDeferred->next = caller_req;
  chm.chmDeferred = caller_req;
  pthread_mutex_unlock(&(chm.defer_mutex));

  return;
}

error_t chmRequest(req_t *req,
                   uint8_t rw,
                   uint32_t lba,
                   uint32_t new_lba_page,
                   bool_t is_hint) {
	chm_specific_t* specific = CH_REQ_GET_SPECIFIC(req);
	specific->rw    = rw;
  specific->lba   = lba;
  specific->state = CHM_INITIAL;
  specific->hint = 0;

  if(is_hint){
  	specific->hint = ((hint_payload_t*)(((scsi_specific_t*) (req->caller_req->specific)))->cdb->buf);
  	LOG("new hint request. start_lba %u ver %u last lba %u\n", specific->hint->start_lba, specific->hint->version, ((uint32_t*)(specific->hint->data))[CH_SECTORS_PER_CHUNK-1]);
  }
  else if(specific->rw == CHM_RECLAIM){
  	specific->state = CHM_RECLAIM_START;
  	chm.open_eu.reclamation_in_progress = req;
  	chm.open_eu.current = req;
  }
  specific->dequeued = 0;
  specific->new_lba_page = new_lba_page;
  specific->chunk_p = 0;
  specific->need_to_reclaim = 0;
  specific->next = 0;
  specific->prev = 0;

  specific->enq_reason = CHM_ENQUEUE_REASON_NONE;

  req_activate(req,0);
  return 0;
}

/*************************************************************************
																		The Handler
 *************************************************************************/
void chmHandler(req_t* req, void* response) {
  chm_specific_t* specific = CH_REQ_GET_SPECIFIC(req);
  req_t* nandbus_req;
  uint32_t page;
//	uint32_t len;
//	req_t* p;
//	int i, j;
	ch_buffer_entry *chunk_p = 0;
//	bool_t res;
//	req_t* dq_req = 0, *dq_rc = 0;
//	uint32_t idx;

	LOG(" lba %u. state %u. req %x (caller %x), magic_num %u \n",
			specific->lba, specific->state, req, req->caller_req, (specific->rw == CHM_RECLAIM || chm_is_hint_request(req))?IDX_EMPTY:((tm_specific_t*)(req->caller_req->specific))->cdb->magic_num);
//	LOG("entry[0] chm_pending %x first %x current %x enq_reason %u\n", chm.buffer_entry[0].chm_pending_list, (chm.buffer_entry[0].chm_pending_list)?chm.buffer_entry[0].chm_pending_list->req:0, chm.buffer_entry[0].current, specific->enq_reason);
	assert(specific->enq_reason == CHM_ENQUEUE_REASON_NONE);
	switch (specific->state) {
		case CHM_INITIAL:
			specific->state = chm_is_hint_request(req)?CHM_HINT_INITIAL:CHM_LBA_INITIAL; /* req is dequeued or continuing to this state*/
			req_activate(req, 0);
			break;
		case CHM_HINT_INITIAL:
			LOG("CHM_HINT_INITIAL\n");
			/*read hint*/
			if(chm_is_hint_read_request(req)){
				LOG("hint read\n");
				/* HINT - handle read. first try to read from cache.
				 * if not possible read some chunk from flash (when idle) */
				specific->hint->version = IDX_EMPTY;
				specific->hint->start_lba = IDX_EMPTY;
				if(!chm_handle_hint_cached_read(req)){
					chm_handle_hint_flash_read(req);
				}
				break;
			}

			/* write hint */
			LOG("hint write\n");
			chm_handle_hint_cached_write(req);
			specific->state = CHM_DONE;
			req_activate(req, 0);
			break;
		case CHM_LBA_INITIAL:
			chunk_p = chm_get_cached_chunk(req);LOG("chunk_p %x\n", chunk_p);

			/*for a non-tm-reclamation (non-hint!) req, we can use only regular entries */
			if(chunk_p != 0
				 &&
				 chm_is_req_regular_chunk_reserve(req, chunk_p)){
				LOG("non tm-reclaim req. haven't found cached entry\n");
				chunk_p = 0;
			}

			specific->chunk_p = chunk_p; /* if 0 or not*/

//			/* reclamation on*/
//			if(chm_is_reclamation_in_progress()){LOG("reclamation_in_progress\n");
//				/* during reclamation can't process hint requests (not idle!)*/
//				if(chm_is_hint_request(req)){
//					specific->state = CHM_DONE;
//					req_activate(req, 0);
//					break;
//				}
//			}

		  /* read page address from chunk*/
			if(specific->rw == CHM_READ || specific->rw == CHM_READ_BEFORE_WRITE){
			  LOG("lba %u. read chunk\n", specific->lba);
			  /* chunk is cached - return as response pointer to page address.
			   * a hint read will be handkled as if not cached (lazyness to avoid more code)*/
				if(chunk_p != 0){
					specific->chunk_p = chunk_p;
					LOG("lba %u. call cached read\n", specific->lba);
					specific->state = CHM_HANDLE_CACHED_READ;
					req_activate(req, 0);
					break;
				}

				/* chunk not cached. get it (or - hint requets which needs a free chunk/chunk read)*/
				LOG("lba %u. read non-cached chunk\n", specific->lba);
				specific->state = CHM_READ_READY;
				req_activate(req, 0);
				break;
			}
			/* write page address to chunk*/
			else if (specific->rw == CHM_WRITE){assert(chunk_p !=0);
				specific->chunk_p = chunk_p;
				specific->state = CHM_HANDLE_CACHED_WRITE;
				req_activate(req, 0);
				break;
			}
			assert(0);
			break;
		case CHM_HANDLE_CACHED_READ:
			CH_REQ_GET_SPECIFIC(req)->dequeued = 0;/* either way, request is not dequeued anymore */
			chunk_p = specific->chunk_p;
			/* cant read chunk that's being read*/
			if(chunk_p->state == CH_BUF_ENTRY_ALLOCATED){
				chm_enqueue_cache_chunk_taken(req);
				break;
			}
			LOG("chunk_p %x idx %u state %u root_offset %u\n", specific->chunk_p, specific->chunk_p->idx, specific->chunk_p->state, specific->chunk_p->root_offset);assert(specific->chunk_p->root_offset == CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(specific->lba));

			/*we can definitely read and cached chunk pointed bt specific->chunk_p*/
			chm_handle_cached_entry_read(specific->chunk_p, req);
			specific->state = CHM_DONE;
			LOG("send read response %x\n", &(specific->read_res));
			req_activate(req, &(specific->read_res));
			break;
		case CHM_HANDLE_CACHED_WRITE:
			CH_REQ_GET_SPECIFIC(req)->dequeued = 0;/* either way, request is not dequeued anymore */
			/*we can definitely write and cached chunk pointed bt specific->chunk_p*/
			assert(!chm_is_hint_request(req));/*if chunk is cached. surely its the latest version*/
			chunk_p = specific->chunk_p;
			/* cant write to chunk that's being written*/
			if(chunk_p->state == CH_BUF_ENTRY_WRITTEN){
				chm_enqueue_cache_chunk_taken(req);
				break;
			}

			chm_handle_initial_write(req);
			specific->state = CHM_WRITE_DONE;
			req_activate(req, 0);
			break;
		case CHM_READ_READY:
			/* we have regular no-free-chunk requests in line, issued
			 * previously to this fresh *not dequeued* req
			 * (and not ones queued because their relevant chunk is in flash-access)*/
			if(chm.unforced_counter > 0
					&&
					chm_is_regular_req(req) /* queue does not apply to hint reqs*/
			    &&
			    !chm_is_dequeued_request(req)){
				chm_enqueue_no_free_chunk(req);
				break;
			}

			CH_REQ_GET_SPECIFIC(req)->dequeued = 0;/* either way, request is not dequeued anymore */
			/* get FREE fresh chunk*/
			chunk_p = chm_get_free_chunk(req);

			/* no free chunk. enqeueue and regress to initial state*/
			if(chunk_p == 0){
				chm_enqueue_no_free_chunk(req);
				break;
			}

			/* mark chunk as allocated and prepare nand read*/
			LOG("got free chunk (for root_offset %u)\n", CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(specific->lba));assert(chm_root_offset_not_taken(CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(specific->lba)));
			specific->chunk_p = chunk_p;
			specific->chunk_p->state  = CH_BUF_ENTRY_ALLOCATED;
//				LOG("specific->lba %u. chm.buffer_entry[1].state %u (should be %u).\n", specific->lba, chm.buffer_entry[1].state, CH_BUF_ENTRY_ALLOCATED);
			specific->chunk_p->root_offset = CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(specific->lba);
			specific->chunk_p->address = chm_get_ch_page_address(specific->lba);
			chm_entry_tm_list_cond_push(chunk_p, req);

			/* if chunk doesn't exist yet, mock-read it*/
			LOG("CHM_READ_READY ch addr %x chm.buffer_entry[1].state %u\n", chm_get_ch_page_address(specific->lba), chm.buffer_entry[1].state);
			if(specific->chunk_p->address == CH_ADDR_EMPTY){
				LOG("CHM_READ_READY ch addr empty. return empty addr\n");
				memset(specific->chunk_p->buf, 0xff, CH_CHUNK_SIZE);
				chm_handle_entry_clean_op(specific->chunk_p->idx); /* XXX is this safe?*/
				specific->read_res = CH_GET_PAGE(specific->chunk_p->buf, specific->lba); /* response = pointer to page address*/
				specific->state = CHM_DONE;
				req_activate(req, &(specific->read_res));
				break;
			}LOG("call CHM_NANDBUS_READ_READY. chm.buffer_entry[1].state %u\n", chm.buffer_entry[1].state);

			specific->state = CHM_NANDBUS_READ_READY;

			nandbusAlloc(req);
			break;
		case CHM_NANDBUS_READ_READY:
                        #ifdef PROFILING
 			ch_real_reads++;
                        if(is_part_of_reclaim(req)){
                          ch_tm_reclaim_reads++;
                        }
                        else if(!chm_is_hint_request(req)){
                          ch_real_reads++;
                        }
                        #endif
			nandbus_req = (req_t*) response;
			specific->state = chm_is_hint_request(req)?CHM_NAND_HINT_READ_DONE:CHM_NAND_READ_DONE;
			/* NOTICE - if reclamation req reclaimed/reclaiming this exact chunk
			 * then its either -
			 * 1. in the process of writing and read will precede erase
			 * 2. preparing write buf - therefore the original chunk is still not erased and the read will precede erase
			 * 3. after re-write - read will get updated address*/
			page = chm_get_ch_page_address(specific->lba);
			LOG("specific->lba %u CHM_NANDBUS_READ_READY. page %u. chm.buffer_entry[1].state %u\n", specific->lba, page, chm.buffer_entry[1].state);

			/* do nandbusCmd*/
			nandbusCmd(nandbus_req,
			 /* buf*/  (!chm_is_hint_request(req))?specific->chunk_p->buf:specific->hint->data,
			/* bus*/   page / NAND_PAGES_PER_BUS,
			/* chip*/  (page % NAND_PAGES_PER_BUS) / NAND_PAGES_PER_CHIP,
			/* cmd*/	 NAND_CMD_READ,
			/* page*/	 page,
	/* offset */	 chm_get_ch_page_offset(specific->lba) * CH_CHUNK_SIZE,
			 /* len*/  CH_CHUNK_SIZE,
								 0, 0);
			break;
		case CHM_WRITE_DONE:
			LOG("CHM_WRITE_DONE. set specific->chunk_p->state to DIRTY. chm.open_eu.offset %u, addr %u, blk_idx %u\n",chm.open_eu.offset, chm.open_eu.address, chm.open_eu.blk_idx);
			specific->chunk_p->state = CH_BUF_ENTRY_DIRTY;
#ifdef Fanatic
			assert(chm.open_eu.offset <= NAND_PAGES_PER_ERASE_UNIT); /* open eu should be write-ready/overflow by 1*/
#endif
			/*   dirty threshold not reached
			 *  - we're done here, and dont need to write anything to flash*/
			if(!chm_is_dirty_cache_threshold_reached()){
				LOG("CHM_WRITE_DONE. not dirty_threshold_reached\n");
				specific->state = CHM_DONE;
				req_activate(req, 0);
				break;
			}

			/* if a previous write is in progress
			 * reaching the threshold is not enough, queue request in write_buf list*/
			if(chm_is_write_in_progress(req)){
				LOG("CHM_WRITE_DONE. other write_in_progress\n");
				specific->state = CHM_WRITE_BUFFER_QUEUE;
				chm_list_push(&(chm.write_pending), req);
				break;
			}

			/*we can write*/
			specific->state = CHM_WRITE_BUFFER_QUEUE;
			req_activate(req, 0);
			break;
		case CHM_WRITE_BUFFER_QUEUE:
			chm.open_eu.current = req;LOG("chm.open_eu.current req %x\n", req);
			specific->state = CHM_WRITE_THRESHOLD_REACHED;
			req_activate(req, 0);
			break;
		case CHM_WRITE_THRESHOLD_REACHED:
			/* PRE - chm.open_eu.current == req*/LOG("CHM_WRITE_THRESHOLD_REACHED \n");
			/* this is where the chm functionality kicks in - cache flush,reclamation, erasure... */
			/* prepare flush. after this:
			 * 1. ch write buffer is set
			 * 2. relevant chunks marked as WRITTEN
			 * 3. open eu marked as write in progress*/
			chm_prepare_write_buf(req);
			specific->state = CHM_NANDBUS_WRITE_READY;

			/*before we continue to nandbus, free mutex (to avoid possibly being locked when request is defered)
			 * this shouldn't matter - chunk states are now WRITTEN. it won't be disturbed*/
			nandbusAlloc(req);
			break;
		case CHM_NANDBUS_WRITE_READY:
#ifdef PROFILING
			ch_real_writes++;
#endif
			nandbus_req = (req_t*) response;
			specific->state = CHM_NAND_WRITE_DONE;
			page = chm.open_eu.address+chm.open_eu.offset;
			LOG("CHM_NANDBUS_WRITE_READY. call nandbusCmd() to page %u\n", page);

			/* do nandbusCmd*/
			nandbusCmd(nandbus_req,
			 /* buf*/  chm.write_buf,
			/* bus*/   page / NAND_PAGES_PER_BUS,
			/* chip*/  (page % NAND_PAGES_PER_BUS) / NAND_PAGES_PER_CHIP,
			/* cmd*/	 NAND_CMD_WRITE,
			/* page*/	 page,
	/* offset */	 0,
			 /* len*/  NAND_PAGE_SIZE,
								 &(chm.write_buf[NAND_SPARE_AREA_START]), 1);
			break;
		case CHM_NAND_HINT_READ_DONE:
			/* HINT - finish read*/
#ifdef Fanatic
			assert(chm_is_hint_read_request(req));
#endif
			specific->hint->version = chm.chunk[CH_CALC_CHUNK_ROOT_OFFSETBY_LBA(specific->lba)].version;
			specific->hint->start_lba = specific->lba;
			specific->state = CHM_HINT_DONE;
			req_activate(req, 0);
			break;
		case CHM_NAND_READ_DONE:
			/* chunk was read into buf, and it is no longer in ALLOCATED state.
			 * NOTICE - no problem if this happens after start of reclamation*/
			LOG("CHM_NAND_READ_DONE. read lba %u page, in-chunk offset %u - addr is %u\n",
					specific->lba, CH_CALC_LBA_OFFSET_IN_CHUNK(specific->lba), CH_GET_PAGE(specific->chunk_p->buf, specific->lba));
#ifdef Fanatic
			if(CH_GET_PAGE(specific->chunk_p->buf, specific->lba)!= 0xffffffff){
				assert(CH_GET_PAGE(specific->chunk_p->buf, specific->lba)<NAND_PAGE_COUNT);
			}
#endif
			specific->read_res = CH_GET_PAGE(specific->chunk_p->buf, specific->lba); /* response = pointer to page address*/
			/*NOTICE - do this AFTER we get address of page. otherwise, clean_op
			 *         may clean entry (in some edge cases)*/
			chm_handle_entry_clean_op(specific->chunk_p->idx);

#ifdef PROFILING
			/* incremenet hint accessed if necessary
			 * read accessed are updated on flash read (accessed for sure...)*/
			if(!is_part_of_reclaim(req)){
			  read_accessed++;
			}
#endif

			/* send chunk back as hint*/
			ngssd_tentative_cached_hint_notify();

			/* req DONE*/
			specific->state = CHM_DONE;
			req_activate(req, &(specific->read_res));
			break;
		case CHM_NAND_WRITE_DONE:
			/* NOTICE - no need to deal with some req starting reclamation before we return.
			 *          chm.write buf is used by one req at a time.*/
			LOG("CHM_NAND_WRITE_DONE. write done to %u.\n", chm.open_eu.address+chm.open_eu.offset);
			/* change buffer entries state, root mapping (for every chunk)
			 * and open eu flag*/
			chm.open_eu.current = 0;LOG("chm.open_eu.current 0\n");
			chm_clean_written_entries(req);

			/* no we can increment offset*/
			chm.open_eu.offset += 1;
			assert(chm.open_eu.offset <= NAND_PAGES_PER_ERASE_UNIT); /* open eu should be write-ready/overflow by 1*/

			/* can still write to eu*/
			if(chm.open_eu.offset < NAND_PAGES_PER_ERASE_UNIT){
				specific->state = CHM_DONE;
				LOG("CHM_NAND_WRITE_DONE. write done. chm.open_eu.offset %u (NAND_PAGES_PER_ERASE_UNIT %u). finish\n", chm.open_eu.offset, NAND_PAGES_PER_ERASE_UNIT);
				req_activate(req, 0);
				break;
			}

			/* open eu full. check if we have more than 1 free block*/LOG("mark old block (idx %u, addr %u) as full\n", chm.open_eu.blk_idx, chm.block[chm.open_eu.blk_idx].address);
			chm.block[chm.open_eu.blk_idx].state = CH_BLOCK_FULL; /* prev block full*/
			LOG("CHM_NAND_WRITE_DONE - need to allocate new open eu. chm.open_eu.blk_idx %u, addr %u, offset %u\n", chm.open_eu.blk_idx, chm.open_eu.address, chm.open_eu.offset);
			if(chm_allocate_new_empty_block() == 0){
				assert(chm.block[chm.open_eu.blk_idx].state != CH_BLOCK_EMPTY);LOG("CHM_NAND_WRITE_DONE. done allocating new free ch block.\n");
				specific->state = CHM_DONE;
				req_activate(req, 0);
				break;
			}

			/* no more freebies! need to RECLAIM.
			 * (no nested reclamation so no worries here)*/
			chm_choose_block_to_reclaim(CHM_PREPARE_TO_RECLAIM_REQ_MARKER);

			chm.open_eu.offset = 0;
			chm.open_eu.reclamation_in_progress = CHM_PREPARE_TO_RECLAIM_REQ_MARKER;
			assert(chm.open_eu.current == 0);
			chm.open_eu.current = CHM_PREPARE_TO_RECLAIM_REQ_MARKER;
			specific->need_to_reclaim = 1;
			/*mark request for reclamation and continue*/
			specific->state = CHM_DONE;
			req_activate(req, 0);
			break;
		case CHM_RECLAIM_START:
			/* read buffer processing was finisehd? clean it b4 moving on*/
			LOG("chm.rec_eu.blk_idx %u\n", chm.rec_eu.blk_idx);
			LOG("CHM_RECLAIM_START. specific->lba %u. chm.rec_eu.address %u, offset %u, blk_idx %u, chm.block[%u].address %u.start reclaiming\n", specific->lba, chm.rec_eu.address, chm.rec_eu.offset, chm.rec_eu.blk_idx, chm.rec_eu.blk_idx, chm.block[chm.rec_eu.blk_idx].address);
			if(chm_is_read_buf_process_done()){
				LOG("chm_rec_clean_read_buf\n");
				chm_rec_clean_read_buf();
			}

			/*skip pages with no valid chunks*/
			while(chm.rec_eu.offset < NAND_PAGES_PER_ERASE_UNIT
						&&
						chm_is_reclaimed_page_obsolete()){
				chm.rec_eu.offset++;
			}

			LOG("more pages to reclaim?\n");

			/*if no more pages to reclaim*/
			if(chm_is_last_reclaimed_page_exhausted()){
				specific->state = CHM_RECLAIM_ERASE;
				req_activate(req, 0);
				break;
			}

			/*if we're at the start of a new page, read it*/
			LOG("chm.rec_eu.read_buf.chunk_offset %u\n", chm.rec_eu.read_buf.chunk_offset);
			if(chm.rec_eu.read_buf.chunk_offset == 0
				 &&
				 !chm_is_rec_eu_process_done()){
				LOG("CHM_RECLAIM_START. specific->lba %u. need to fill read buf again\n", specific->lba);
				specific->state = CHM_RECLAIM_NANDBUS_PAGE_READ;

				/*before we continue to nandbus, free mutex.
				 * this shouldn't matter - all new requests will be queued anyway*/
				nandbusAlloc(req);
				break;
			}

			/* in the middle of reclaim*/
			LOG("CHM_RECLAIM_START. specific->lba %u.in the middle of reclaim\n", specific->lba);
			/* fill details of write buf - more chunks (if there are any), root offsets+chunk_offset*/
			chm_rec_fill_write_buf();

			/*  write buf full || this is the last reclaimed page.
			 *  flush to flash*/
			if(chm_is_write_buf_full()
				 ||
				 chm_is_rec_eu_process_done()){
				specific->state = CHM_RECLAIM_NANDBUS_PAGE_WRITE;
				/*before we continue to nandbus, free mutex.
				 * this shouldn't matter - all new requests will be queued anyway*/
				nandbusAlloc(req);
				break;
			}

			/* write buf not full yet*/
			specific->state = CHM_RECLAIM_START;
			req_activate(req, 0);
			break;
		case CHM_RECLAIM_NANDBUS_PAGE_READ:
#ifdef PROFILING
			ch_reclaim_reads++;
#endif
			LOG("CHM_RECLAIM_NANDBUS_PAGE_READ. rec block chm.block[%u].state %u\n", chm.rec_eu.blk_idx, chm.block[chm.rec_eu.blk_idx].state);
			assert(chm.block[chm.rec_eu.blk_idx].state == CH_BLOCK_FULL);
			nandbus_req = (req_t*) response;
			specific->state = CHM_RECLAIM_NAND_READ_DONE;

			page = chm.rec_eu.address+chm.rec_eu.offset;
			LOG("chm.rec_eu.address %u, offset %u, blk_idx %u\n", chm.rec_eu.address, chm.rec_eu.offset, chm.rec_eu.blk_idx);
			assert(page < NAND_PAGE_COUNT);
			LOG("CHM_RECLAIM_NANDBUS_PAGE_READ. specific->lba %u. read page %u\n", specific->lba, page);

			/* do nandbusCmd*/
			nandbusCmd(nandbus_req,
			 /* buf*/  chm.rec_eu.read_buf.buf,
			/* bus*/   page / NAND_PAGES_PER_BUS,
			/* chip*/  (page % NAND_PAGES_PER_BUS) / NAND_PAGES_PER_CHIP,
			/* cmd*/	 NAND_CMD_READ,
			/* page*/	 page,
	/* offset */	 0,
			 /* len*/  NAND_TOTAL_SIZE,
								 0, 0);
			break;
		case CHM_RECLAIM_NAND_READ_DONE:
			/* nandthread finished. lock mutex again*/
			LOG("CHM_RECLAIM_NAND_READ_DONE. specific->lba %u.fill rec bufs. call chm_rec_fill_read_buf()\n", specific->lba);
			/* fill details of read buf - root offsets+chunk_offset*/
			chm_rec_fill_read_buf();//			LOG("CHM_RECLAIM_NAND_READ_DONE. specific->lba %u. fill write bufs. call chm_rec_fill_write_buf()\n", specific->lba);
			/* fill details of write buf - more chunks (if there are any), root offsets+chunk_offset*/
			chm_rec_fill_write_buf();

			/* write buf full. flush it*/
			LOG("CHM_RECLAIM_NAND_READ_DONE. specific->lba %u. write done. continue\n", specific->lba);
			if(chm_is_write_buf_full()
				 ||
				 (chm_is_rec_eu_process_done() && chm_is_read_buf_process_done())){
				/* buffer is empty*/
				if(chm_is_write_buf_empty()){
					specific->state = CHM_RECLAIM_NEXT_WRITE;
					req_activate(req, 0);
					break;
				}

				LOG("write buffer is full (or rec eu+read buf is done)\n");
				specific->state = CHM_RECLAIM_NANDBUS_PAGE_WRITE;
				/*before we continue to nandbus, free mutex.
				 * this shouldn't matter - all new requests will be queued anyway*/
				nandbusAlloc(req);
				break;
			}

			/* write buf not full yet*/
			LOG("CHM_RECLAIM_NAND_READ_DONE. specific->lba %u. write not done, reclaim\n", specific->lba);
			specific->state = CHM_RECLAIM_START;
		//	UNLOCK_CHM_MUTEX();
			req_activate(req, 0);
			break;
		case CHM_RECLAIM_NANDBUS_PAGE_WRITE:
#ifdef PROFILING
			ch_reclaim_writes++;
#endif
			LOG("CHM_RECLAIM_NANDBUS_PAGE_WRITE. chm.block[chm.rec_eu.blk_idx].state %u\n", chm.block[chm.rec_eu.blk_idx].state);
			nandbus_req = (req_t*) response;
			specific->state = CHM_RECLAIM_NAND_WRITE_DONE;

			page = chm.open_eu.address+chm.open_eu.offset;
			LOG("CHM_RECLAIM_NANDBUS_PAGE_WRITE. specific->lba %u. write to page %u\n", specific->lba, page);
			LOG("chm.rec_eu.write_buf.buf %x \n", chm.rec_eu.write_buf.buf);
			LOG("page %u \n", page);
			LOG("nandbus_req %x \n", nandbus_req);
			/* do nandbusCmd*/
			nandbusCmd(nandbus_req,
			 /* buf*/  chm.rec_eu.write_buf.buf,
			/* bus*/   page / NAND_PAGES_PER_BUS,
			/* chip*/  (page % NAND_PAGES_PER_BUS) / NAND_PAGES_PER_CHIP,
			/* cmd*/	 NAND_CMD_WRITE,
			/* page*/	 page,
	/* offset */	 0,
			 /* len*/  NAND_TOTAL_SIZE,
								 0, 0);
			break;
		case CHM_RECLAIM_NAND_WRITE_DONE:
			chm.rec_eu.write_buf.address = chm.open_eu.address+chm.open_eu.offset;
			LOG("CHM_RECLAIM_NAND_WRITE_DONE. specific->lba %u, written page %u. call chm_rec_clean_write_buf()\n", specific->lba, chm.open_eu.address+chm.open_eu.offset);
			/* mark new chunks as used and point root mapping to them*/
			chm_rec_clean_write_buf();

			chm.open_eu.offset++;
			assert(chm.block[chm.open_eu.blk_idx].state == CH_BLOCK_PARTIAL);
			LOG("chm.open_eu blk_idx %u, addr %u,  offset %u\n", chm.open_eu.blk_idx, chm.open_eu.address, chm.open_eu.offset);

			specific->state = CHM_RECLAIM_NEXT_WRITE;
			req_activate(req, 0);
			break;
		case CHM_RECLAIM_NEXT_WRITE:
			/* if we're done reclaiming and processing the last read buf,
			 * erase unit and continue*/
			if(chm_is_last_reclaimed_page_exhausted()){
				LOG("do CHM_RECLAIM_ERASE\n");
				specific->state = CHM_RECLAIM_ERASE;
			}
			else{
				LOG("no need to erase yet. call CHM_RECLAIM_START\n");
				specific->state = CHM_RECLAIM_START;
			}
			req_activate(req, 0);
			break;
		case CHM_RECLAIM_ERASE:
			specific->state = CHM_RECLAIM_NANDBUS_ERASE;
			/*before we continue to nandbus, free mutex.
			 * this shouldn't matter - all new requests will be queued anyway*/
			LOG("CHM_RECLAIM_ERASE. specific->lba %u. call nandbusAlloc()\n", specific->lba);
		//	UNLOCK_CHM_MUTEX();
			nandbusAlloc(req);
			break;
		case CHM_RECLAIM_NANDBUS_ERASE:
			nandbus_req = (req_t*) response;
			specific->state = CHM_RECLAIM_NAND_ERASE_DONE;

			page = chm.rec_eu.address;
			LOG("CHM_RECLAIM_NANDBUS_ERASE. specific->lba %u. page %u chm.rec_eu.address %u\n", specific->lba, page, chm.rec_eu.address);

			/* do nandbusCmd*/
			nandbusCmd(nandbus_req,
			 /* buf*/  0,
			/* bus*/   page / NAND_PAGES_PER_BUS,
			/* chip*/  (page % NAND_PAGES_PER_BUS) / NAND_PAGES_PER_CHIP,
			/* cmd*/	 NAND_CMD_ERASE,
			/* page*/	 page,
	/* offset */	 0,
			 /* len*/  0,
								 0, 0);
			break;
		case CHM_RECLAIM_NAND_ERASE_DONE:
			/* mark rec_eu as empty */
			LOG("CHM_RECLAIM_NAND_ERASE_DONE. specific->lba %u. call chm_clean_rec_eu(). chm.rec_eu.address %u\n", specific->lba, chm.rec_eu.address);
#ifdef Fanatic
			assert(program[chm.rec_eu.address] == 0);assert(program[chm.block[chm.rec_eu.blk_idx].address] == 0);
#endif
			chm_clean_rec_eu();
			chm.open_eu.reclamation_in_progress = 0;
			chm.open_eu.current = 0;

			specific->state = CHM_DONE;
			req_activate(req, 0);
			break;
		case CHM_HINT_DONE:
			/* hint was read*/
			if(chm_is_hint_read_request(req) && specific->hint->start_lba != IDX_EMPTY){
				req_activate(req->caller_req, specific->hint);
			}
			/*  write / failed read */
			else{
				req_activate(req->caller_req,0);
			}
			chm_handle_defered(req);/* done with everything. move on to deferred*/
			break;
		case CHM_DONE:
			LOG("CHM_DONE. specific->rw =%u\n", specific->rw);
			if(specific->rw == CHM_READ){
				/* finished with simple chunk read*/
				/* handle cases of allocated / obsolete */
				LOG("req %x CHM_DONE for CHM_READ. D. specific->chunk_p %x state %x\n", req, specific->chunk_p, specific->chunk_p->state);
				if(specific->chunk_p->state  == CH_BUF_ENTRY_ALLOCATED_OBS){
					specific->chunk_p->state = CH_BUF_ENTRY_FREE;
				}
				else{
					/* should we do anything here?*/;
					LOG("CHM_READ done. specific->chunk_p->state %u\n", specific->chunk_p->state);
				}
				LOG("CHM_DONE. specific->rw == CHM_READ. new chunk_p->state %u\n", specific->chunk_p->state);
			}
			else if(specific->rw == CHM_READ_BEFORE_WRITE){
				/* read for write done*/
				LOG("specific->lba %u CHM_DONE for CHM_READ_BEFORE_WRITE. chm_cond_state_clean_write for specific->chunk_p->state %u\n", specific->lba, specific->chunk_p->state);
				chm_cond_state_clean_write(specific->chunk_p);
			}
			else if(specific->rw == CHM_WRITE){
				/* write done */
			}

			/* notify caller*/
			LOG("CHM_DONE. call req_caller. response %x\n\n\n\n", response);
		//	UNLOCK_CHM_MUTEX();
			req_activate(req->caller_req,response);

			/* if we triggered reclamation.
			 * create a request for it*/
			if(specific->need_to_reclaim){LOG("we triggered reclamation\n");
				baRequest(0, IDX_EMPTY, BA_TYPE_CHM);
			}

			/* done with everything. move on to deferred*/
			chm_handle_defered(req);
			break;
		default:
			assert(0);
	}
}
