#include "armdefs.h"

/* mmu cache init
 *
 * @cache_t :cache_t to init
 * @width	:cache line width in byte
 * @way		:way of each cache set
 * @set		:cache set num
 *
 * $ -1: error
 * 	 0: sucess
 */
int
mmu_cache_init (cache_t * cache_t, int width, int way, int set, int w_mode)
{
	int i, j;
	cache_set_t *sets;
	cache_line_t *lines;

	/*alloc cache set */
	sets = NULL;
	lines = NULL;
	//fprintf(stderr, "mmu_cache_init: mallloc beg size %d,sets 0x%x\n", sizeof(cache_set_t) * set,sets);
	//exit(-1);
	sets = (cache_set_t *) malloc (sizeof (cache_set_t) * set);
	if (sets == NULL) {
		err_msg ("set malloc size %d\n", sizeof (cache_set_t) * set);
		goto sets_error;
	}
	//fprintf(stderr, "mmu_cache_init: mallloc end sets 0x%x\n", sets);
	cache_t->sets = sets;

	/*init cache set */
	for (i = 0; i < set; i++) {
		/*alloc cache line */
		lines = (cache_line_t *) malloc (sizeof (cache_line_t) * way);
		if (lines == NULL) {
			err_msg ("line malloc size %d\n",
				 sizeof (cache_line_t) * way);
			goto lines_error;
		}
		/*init cache line */
		for (j = 0; j < way; j++) {
			lines[j].tag = 0;	//invalid
			lines[j].data = (ARMword *) malloc (width);
			if (lines[j].data == NULL) {
				err_msg ("data alloc size %d\n", width);
				goto data_error;
			}
		}

		sets[i].lines = lines;
		sets[i].cycle = 0;

	}
	cache_t->width = width;
	cache_t->set = set;
	cache_t->way = way;
	cache_t->w_mode = w_mode;
	return 0;

      data_error:
	/*free data */
	while (j-- > 0)
		free (lines[j].data);
	/*free data error line */
	free (lines);
      lines_error:
	/*free lines already alloced */
	while (i-- > 0) {
		for (j = 0; j < way; j++)
			free (sets[i].lines[j].data);
		free (sets[i].lines);
	}
	/*free sets */
	free (sets);
      sets_error:
	return -1;
};

/* free a cache_t's inner data, the ptr self is not freed,
 * when needed do like below:
 * 		mmu_cache_exit(cache);
 * 		free(cache_t);
 *
 * @cache_t : the cache_t to free
 */

void
mmu_cache_exit (cache_t * cache_t)
{
	int i, j;
	cache_set_t *sets, *set;
	cache_line_t *lines, *line;

	/*free all set */
	sets = cache_t->sets;
	for (set = sets, i = 0; i < cache_t->set; i++, set++) {
		/*free all line */
		lines = set->lines;
		for (line = lines, j = 0; j < cache_t->way; j++, line++)
			free (line->data);
		free (lines);
	}
	free (sets);
}

/* mmu cache search
 *
 * @state	:ARMul_State
 * @cache_t	:cache_t to search
 * @va		:virtual address
 *
 * $	NULL:	no cache match
 * 		cache	:cache matched
 */
cache_line_t *
mmu_cache_search (ARMul_State * state, cache_t * cache_t, ARMword va)
{
	int i;
	int set = va_cache_set (va, cache_t);
	ARMword tag = va_cache_align (va, cache_t);
	cache_line_t *cache;

	cache_set_t *cache_set = cache_t->sets + set;
	for (i = 0, cache = cache_set->lines; i < cache_t->way; i++, cache++) {
		if ((cache->tag & TAG_VALID_FLAG)
		    && (tag == va_cache_align (cache->tag, cache_t)))
			return cache;
	}
	return NULL;
}

/* mmu cache search by set/index
 *
 * @state	:ARMul_State
 * @cache_t	:cache_t to search
 * @index	:set/index value. 
 *
 * $	NULL:	no cache match
 * 		cache	:cache matched
 */
cache_line_t *
mmu_cache_search_by_index (ARMul_State * state, cache_t * cache_t,
			   ARMword index)
{
	int way = cache_t->way;
	int set_v = index_cache_set (index, cache_t);
	int i = 0, index_v = 0;
	cache_set_t *set;

	while ((way >>= 1) >= 1)
		i++;
	index_v = index >> (32 - i);
	set = cache_t->sets + set_v;

	return set->lines + index_v;
}


/* mmu cache alloc
 *
 * @state :ARMul_State
 * @cache_t	:cache_t to alloc from
 * @va		:virtual address that require cache alloc, need not cache aligned
 * @pa		:physical address of va
 *
 * $	cache_alloced, always alloc OK
 */
cache_line_t *
mmu_cache_alloc (ARMul_State * state, cache_t * cache_t, ARMword va,
		 ARMword pa)
{
	cache_line_t *cache;
	cache_set_t *set;
	int i;

	va = va_cache_align (va, cache_t);
	pa = va_cache_align (pa, cache_t);

	set = &cache_t->sets[va_cache_set (va, cache_t)];

	/*robin-round */
	cache = &set->lines[set->cycle++];
	if (set->cycle == cache_t->way)
		set->cycle = 0;

	if (cache_t->w_mode == CACHE_WRITE_BACK) {
		ARMword t;

		/*if cache valid, try to write back */
		if (cache->tag & TAG_VALID_FLAG) {
			mmu_cache_write_back (state, cache_t, cache);
		}
		/*read in cache_line */
		t = pa;
		for (i = 0; i < (cache_t->width >> WORD_SHT);
		     i++, t += WORD_SIZE) {
			cache->data[i] = mem_read_word (state, t);
		}
	}
	/*store tag and pa */
	cache->tag = va | TAG_VALID_FLAG;
	cache->pa = pa;

	return cache;
};

/* mmu_cache_write_back write cache data to memory
 * @state
 * @cache_t :cache_t of the cache line
 * @cache : cache line
 */
void
mmu_cache_write_back (ARMul_State * state, cache_t * cache_t,
		      cache_line_t * cache)
{
	ARMword pa = cache->pa;
	int nw = cache_t->width >> WORD_SHT;
	ARMword *data = cache->data;
	int i;
	int t0, t1, t2;

	if ((cache->tag & 1) == 0)
		return;

	switch (cache->
		tag & ~1 & (TAG_FIRST_HALF_DIRTY | TAG_LAST_HALF_DIRTY)) {
	case 0:
		return;
	case TAG_FIRST_HALF_DIRTY:
		nw /= 2;
		break;
	case TAG_LAST_HALF_DIRTY:
		nw /= 2;
		pa += nw << WORD_SHT;
		data += nw;
		break;
	case TAG_FIRST_HALF_DIRTY | TAG_LAST_HALF_DIRTY:
		break;
	}
	for (i = 0; i < nw; i++, data++, pa += WORD_SIZE)
		mem_write_word (state, pa, *data);

	cache->tag &= ~(TAG_FIRST_HALF_DIRTY | TAG_LAST_HALF_DIRTY);
};


/* mmu_cache_clean: clean a cache of va in cache_t
 *
 * @state	:ARMul_State
 * @cache_t	:cache_t to clean
 * @va		:virtaul address
 */
void
mmu_cache_clean (ARMul_State * state, cache_t * cache_t, ARMword va)
{
	cache_line_t *cache;

	cache = mmu_cache_search (state, cache_t, va);
	if (cache)
		mmu_cache_write_back (state, cache_t, cache);
}

/* mmu_cache_clean_by_index: clean a cache by set/index format value
 *
 * @state	:ARMul_State
 * @cache_t	:cache_t to clean
 * @va		:set/index format value
 */
void
mmu_cache_clean_by_index (ARMul_State * state, cache_t * cache_t,
			  ARMword index)
{
	cache_line_t *cache;

	cache = mmu_cache_search_by_index (state, cache_t, index);
	if (cache)
		mmu_cache_write_back (state, cache_t, cache);
}

/* mmu_cache_invalidate : invalidate a cache of va
 *
 * @state	:ARMul_State
 * @cache_t	:cache_t to invalid
 * @va		:virt_addr to invalid
 */
void
mmu_cache_invalidate (ARMul_State * state, cache_t * cache_t, ARMword va)
{
	cache_line_t *cache;

	cache = mmu_cache_search (state, cache_t, va);
	if (cache) {
		mmu_cache_write_back (state, cache_t, cache);
		cache->tag = 0;
	}
}

/* mmu_cache_invalidate_by_index : invalidate a cache by index format
 *
 * @state	:ARMul_State
 * @cache_t	:cache_t to invalid
 * @index	:set/index data
 */
void
mmu_cache_invalidate_by_index (ARMul_State * state, cache_t * cache_t,
			       ARMword index)
{
	cache_line_t *cache;

	cache = mmu_cache_search_by_index (state, cache_t, index);
	if (cache) {
		mmu_cache_write_back (state, cache_t, cache);
		cache->tag = 0;
	}
}

/* mmu_cache_invalidate_all
 *
 * @state:
 * @cache_t
 * */
void
mmu_cache_invalidate_all (ARMul_State * state, cache_t * cache_t)
{
	int i, j;
	cache_set_t *set;
	cache_line_t *cache;

	set = cache_t->sets;
	for (i = 0; i < cache_t->set; i++, set++) {
		cache = set->lines;
		for (j = 0; j < cache_t->way; j++, cache++) {
			mmu_cache_write_back (state, cache_t, cache);
			cache->tag = 0;
		}
	}
};

void
mmu_cache_soft_flush (ARMul_State * state, cache_t * cache_t, ARMword pa)
{
	ARMword set, way;
	cache_line_t *cache;
	pa = (pa / cache_t->width);
	way = pa & (cache_t->way - 1);
	set = (pa / cache_t->way) & (cache_t->set - 1);
	cache = &cache_t->sets[set].lines[way];

	mmu_cache_write_back (state, cache_t, cache);
	cache->tag = 0;
}
