#include "MemMan.h"
#include "naskfunc.h"
#include "debug.h"
#include "vector.h"

#define EFLAGS_AC_BIT		0x00040000
#define CR0_CACHE_DISABLE	0x60000000

BYTE MemMan::buf[sizeof(MemMan)];

void MemMan::Init(){
	new ((void *)buf) MemMan();
}

MemMan::MemMan(void)
{
	debugprint("memory checking...\n");
	memsize = memtest(SECOND_BEGIN, 0xbfffffff);
	if(memsize >= 0x80000000){
		memsize = 0x80000000;
	}
	debugprint("memory:%d (max mem is 2G byte)\n", memsize);

	//0~memsize-1まで使える
	pagecount = (FIRST_END - FIRST_BEGIN) / PAGE_SIZE + memsize / PAGE_SIZE;
	pages_for_pageinfo = (pagecount * sizeof(PAGEINFO) + PAGE_SIZE - 1) / PAGE_SIZE;
	//0x00001000 - 0x0009efffに入るBLOCKINFOの数を計算
	maxfirst = (FIRST_END - FIRST_BEGIN) / sizeof(PAGEINFO);

	for(int i = 0; i < pagecount; i++){
		PAGEINFO &pi = *getpageinfo(i);
		if(i < pages_for_pageinfo){
			//PAGEINFO格納に利用されるページ
			pi.commited = 1;
			pi.unpageoutable = 1;
			pi.system = 1;
			pi.pid = 0;
		}else{
			pi.commited = 0;
			pi.unpageoutable = 0;
			pi.system = 0;
			pi.pid = 0;
		}
	}

	minfo = new Vector<MEMINFO>(0);
}


MemMan::~MemMan(void)
{
	delete minfo;
}

MemMan::PAGEINFO *MemMan::getpageinfo(DWORD i){
	if(pagecount <= i){
		return NULL;
	}
	if(i < maxfirst){
		//一つ目の空き領域
		return (PAGEINFO *)(FIRST_BEGIN + i * sizeof(PAGEINFO));
	}else{
		return (PAGEINFO *)(SECOND_BEGIN + (i - maxfirst) * sizeof(PAGEINFO));
	}
}

DWORD MemMan::getpageaddr(DWORD i){
	if(pagecount <= i){
		return -1;
	}
	if(FIRST_BEGIN + i * PAGE_SIZE < FIRST_END){
		return FIRST_BEGIN + i * PAGE_SIZE;
	}else{
		return SECOND_BEGIN + (i - (FIRST_END - FIRST_BEGIN) / PAGE_SIZE) * PAGE_SIZE;
	}
}

DWORD MemMan::getpageindex(DWORD addr){
	DWORD pageindex = 0;
	if(addr < FIRST_END){
		pageindex = (addr - FIRST_BEGIN) / PAGE_SIZE;
	}else{
		pageindex = (FIRST_END - FIRST_BEGIN) / PAGE_SIZE + (addr - SECOND_BEGIN) / PAGE_SIZE;
	}
	return pageindex;
}

DWORD MemMan::malloc(bool unpageoutable, bool system, DWORD pid, DWORD count){
	for(int i = 0; i < pagecount - count + 1; i++){
		bool allocable = true;
		for(DWORD j = 0; j < count; j++){
			if(getpageinfo(i + j)->commited){
				allocable = false;
				break;
			}
		}
		if(allocable){
			for(DWORD j = 0; j < count; j++){
				//debugprint("malloc phy:%p\n", getpageaddr(i + j));
				PAGEINFO &pi = *getpageinfo(i + j);
				pi.commited = 1;
				pi.unpageoutable = unpageoutable;
				pi.pid = pid;
				pi.system = system;
			}

			return getpageaddr(i);
		}
	}
}

void MemMan::free(DWORD addr, DWORD count){
	DWORD pageindex = getpageindex(addr);
	for(DWORD i = 0; i < count; i++){
		//debugprint("free phy:%p\n", getpageaddr(pageindex + i));
		getpageinfo(pageindex + i)->commited = 0;
	}
}


DWORD MemMan::memtest(DWORD start, DWORD end)
{
	bool flg486 = false;
	DWORD eflg, cr0, i;

	/* 386か、486以降なのかの確認 */
	eflg = io_load_eflags();
	eflg |= EFLAGS_AC_BIT; /* AC-bit = 1 */
	io_store_eflags(eflg);
	eflg = io_load_eflags();
	if ((eflg & EFLAGS_AC_BIT) != 0) { /* 386ではAC=1にしても自動で0に戻ってしまう */
		flg486 = true;
	}
	eflg &= ~EFLAGS_AC_BIT; /* AC-bit = 0 */
	io_store_eflags(eflg);

	if (flg486) {
		cr0 = load_cr0();
		cr0 |= CR0_CACHE_DISABLE; /* キャッシュ禁止 */
		store_cr0(cr0);
	}

	i = memtest_sub(start, end);

	if (flg486) {
		cr0 = load_cr0();
		cr0 &= ~CR0_CACHE_DISABLE; /* キャッシュ許可 */
		store_cr0(cr0);
	}

	return i;
}

DWORD MemMan::memtest_sub(DWORD start, DWORD end)
{
	volatile DWORD i, *p, old, pat0 = 0xaa55aa55, pat1 = 0x55aa55aa;
	for (i = start; i <= end; i += 0x1000) {
		p = (DWORD *) (i + 0xffc);
		old = *p;			/* いじる前の値を覚えておく */
		*p = pat0;			/* ためしに書いてみる */
		*p ^= 0xffffffff;	/* そしてそれを反転してみる */
		if (*p != pat1) {	/* 反転結果になったか？ */
not_memory:
			*p = old;
			break;
		}
		*p ^= 0xffffffff;	/* もう一度反転してみる */
		if (*p != pat0) {	/* 元に戻ったか？ */
			goto not_memory;
		}
		*p = old;			/* いじった値を元に戻す */
	}
	return i;
}



void *MemMan::sys_malloc(size_t size){
	MEMINFO m;
	m.addr = malloc(true, true, 0, (size + PAGE_SIZE - 1) / PAGE_SIZE);
	m.size = size;
	minfo->Add(m);
	debugprint("malloced:%p %p %p\n", m.addr, m.size, (size + PAGE_SIZE - 1) / PAGE_SIZE);

	return (void *)m.addr;
}

void MemMan::sys_free(void *p){
	DWORD i = 0;
	for(i = 0; i < minfo->GetCount(); i++){
		if((*minfo)[i]->addr == (DWORD)p){
			break;
		}
	}
	if(i == minfo->GetCount()){
		debugprint("sys_free error");
		hung();
		return;
	}
	MEMINFO m = *((*minfo)[i]);
	free(m.addr, (m.size + PAGE_SIZE - 1) / PAGE_SIZE);
	debugprint("freed:%p %p\n", m.addr, m.size);

	minfo->Remove(i);
}
