/*
 * mm.c
 *
 * Copyright 2002, Minoru Murashima. All rights reserved.
 * Distributed under the terms of the BSD License.
 *
 * メモリー管理プログラム
 */


#include"types.h"
#include"config.h"
#include"lib.h"
#include"lock.h"
#include"interrupt.h"
#include"proc.h"
#include"mp.h"
#include"segment.h"
#include"errno.h"
#include"mm.h"
#include"device.h"
/****************************/
extern void test_program();
/*****************************/


enum{
	PAGE_NUM=0x400,				/* Number of pages in a table or a directory */
	MAX_REF_PAGE_STORE=255,		/* Max refrence counts of page store */
	HEAP_NUM=11,				/* kmalloc用MEM構造体最大値 */

	INT_BIT=sizeof(int)*8,		/* Number of integer bit */

	HASH_NUM=CACHE_NUM/2,		/* Number of cache hash table */
	SECTER_SIZE=512
};


/*
 * 物理メモリーテーブル用link struct
 * 割り当て先のpage tableとprocessを保持する
 */
typedef struct PAGE_TBL_ADD{
	uint *page_table;
	PROC *proc;
	struct PAGE_TBL_ADD *next;
}PAGE_TBL_ADD;

/*
 * 物理メモリーテーブル構造体
 * Pageごと
 */
typedef struct PHYS_PAGE{
	ushort ref_count;				/* reference count */
	ushort heap_num;				/* Heap number */
	PAGE_TBL_ADD *page_tbl_add;		/* Link of page table address struct */
	struct PHYS_PAGE *next;
	struct PHYS_PAGE *prev;
}PHYS_PAGE;

typedef struct FREE{
	char* last;
	struct FREE *prev;
	struct FREE *next;
}FREE;

typedef struct{
	uint size;
	FREE *free;
	int gate;
}HEAP;

typedef struct CACHE_TABLE{
	struct CACHE_TABLE *hnext;	/* Hash next */
	struct CACHE_TABLE *hprev;	/* Hash previous */
	void *addr;					/* Cache address */
	int dev;					/* Device number */
	uint block;					/* Device block index */
	struct CACHE_TABLE *next;	/* Cache chain next */
	struct CACHE_TABLE *prev;	/* Cache chain previous */
	WAIT_QUEUE wait_queue;		/* Wait queue */
}CACHE_TABLE;


/* メモリーサイズ、setup.Sで代入 */
uint all_memory_size;

/* メモリーテーブルリンク */
/*
 * free_mem_top (kernel mem) → (user mem) free_mem_end
 */
static PHYS_PAGE free_mem_top={0,0,NULL,NULL,NULL};		/* 空き物理メモリーリンクトップ */
static PHYS_PAGE free_mem_end={0,0,NULL,NULL,NULL};		/* 空き物理メモリーリンクエンド */
/*
 * used_mem_top (new kernel mem) → (old kernel mem) → (new user mem) → (old user mem) used_mem_end
 */
static PHYS_PAGE used_mem_top={0,0,NULL,NULL,NULL};		/* 使用中（横取可）物理メモリーリンクトップ */
static PHYS_PAGE used_mem_end={0,0,NULL,NULL,NULL};		/* 使用中（横取可）物理メモリーリンクエンド */
static PHYS_PAGE *used_usrmem_new=NULL;					/* 使用中のユーザー空間物理メモリー */

static int page_alloc_gate=0;							/* PHYS_PAGE lock gate */

/* kfree用番兵 */
static FREE guard[HEAP_NUM];

/* kmalloc用構造体 */
static HEAP heap[]={
	{0,NULL,0},
	{0x8,   &guard[1], 0},
	{0x10,  &guard[2], 0},
	{0x20,  &guard[3], 0},
	{0x40,  &guard[4], 0},
	{0x80,  &guard[5], 0},
	{0x100, &guard[6], 0},
	{0x200, &guard[7], 0},
	{0x400, &guard[8], 0},
	{0x800, &guard[9], 0},
	{0x1000,&guard[10],0}
};

/* Page store table */
static size_t page_store_size=0;			/* Page store size */
static uchar *page_store_ref_table=NULL;	/* For refrence */
static uint *page_store_use_table=NULL;		/* For search */
static int unuse_store_table=0;				/* Unuse store table number */
static int page_sotre_gate=0;				/* Lock gate for page store handling */

/* Cache */
static CACHE_TABLE cache_tbl[CACHE_NUM];			/* Cache table */
static CACHE_TABLE *cache_hash[HASH_NUM];			/* Hash table for searching cache table */
static CACHE_TABLE *cache_tbl_old=&cache_tbl[0];	/* Cache table old point */
static int cache_tbl_gate=0;						/* Lock gate */

static void *alloc_kernel_page();
static void *alloc_user_page(uint*);
static void link_page(void*);
static void unlink_page(void*);
static int buckup_used_page(void*);

static uint find_all_memsize();
static void init_physical_page_table();
static int init_page_backstore(char*);

#ifdef IO_CASH
static int init_cache();
#endif


/*
 * Physical addressからPHYS_PAGE addressを返す
 * parameters : Physical address
 * return : PHYS_PAGE address
 */
extern inline PHYS_PAGE *get_physpage_from_addr(void *addr)
{
	return (PHYS_PAGE*)((uint)addr/PAGE_SIZE*sizeof(PHYS_PAGE)+PHYSICAL_PAGE_TABLE_ADD);
}


/*
 * PHYS_PAGE addressからPhysical addresを返す
 * parameters : PHYS_PAGE address
 * return : Physical address
 */
extern inline void *get_addr_from_physpage(PHYS_PAGE *memtable)
{
	return (void*)(((uint)memtable-PHYSICAL_PAGE_TABLE_ADD)/sizeof(PHYS_PAGE)*PAGE_SIZE);
}


/*
 * Page tableのread write flagをクリアーする
 * parameters : Page table address
 */
extern inline void clear_rwflag(uint *page)
{
	int i;


	for(i=0;i<PAGE_SIZE/sizeof(uint);++i)page[i]&=0xfffffffd;
}


/*
 * page_store_use_tableの該当bitをマークする
 * parameters : Number
 */
extern inline void mark_page_store_use(int num)
{
	page_store_use_table[num/INT_BIT]|=1<<num%INT_BIT;
}


/*
 * page_store_use_tableの該当bitをクリアーする
 * parameters : Number
 */
extern inline void clear_page_store_use(int num)
{
	page_store_use_table[num/INT_BIT]&=~(1<<num%INT_BIT);
}


/***********************************************************************************
 *
 * 初期化関数
 *
 ***********************************************************************************/


/*
 * メモリーシステムの初期化
 */
void init_mm()
{
	/* Find size of physical memory  */
	printk("Memory size = %d MB\n",find_all_memsize()/0x100000);

	/* 物理メモリーテーブルの設定 */
	init_physical_page_table();

	/* ページング用記憶領域の初期化 */
	init_page_backstore(NULL);

#ifdef IO_CASH
	/* IOキャッシュを初期化する */
	init_cache();
#endif
}


/*
 * Find size of all physical memory
 */
uint find_all_memsize()
{
	enum{EXIST_MEM_SIZE=0x800000};		/* メモリー測定単位 */

	uint volatile *point;


	/* メモリーを単位ごとに測定する*/
	for(point=(uint*)EXIST_MEM_SIZE-1;;point+=EXIST_MEM_SIZE/sizeof(uint))
	{
		*point=0x5a5a5a5a;
		if(*point!=0x5a5a5a5a)break;
		else
		{
			*point=0xf0f0f0f0;
			if(*point!=0xf0f0f0f0)break;
		}
	}

	return (all_memory_size=(uint)point-EXIST_MEM_SIZE+sizeof(uint));
}


/***********************************************************************************
 *
 * メモリー管理関数
 *
 ***********************************************************************************/


/*
 * 物理メモリーテーブルを初期化する。
 */
void init_physical_page_table()
{
	int i,last;
	PHYS_PAGE *p=(PHYS_PAGE*)PHYSICAL_PAGE_TABLE_ADD;


	/* 最初の1Mbyte分メモリーを割り当てる（0初期化）。 */
	memset(p,0,0x100000/PAGE_SIZE*sizeof(PHYS_PAGE));

	/* 残りをフリーに設定する。 */
	for(i=0x100000/PAGE_SIZE,last=all_memory_size/PAGE_SIZE;i<last;++i)
	{
		p[i].ref_count=0;
		p[i].heap_num=0;
		p[i].page_tbl_add=NULL;
		p[i].next=&p[i+1];
		p[i].prev=&p[i-1];
	}
	free_mem_top.next=&p[0x100000/PAGE_SIZE];
	free_mem_end.prev=&p[i-1];
	p[0x100000/PAGE_SIZE].prev=&free_mem_top;
	p[i-1].next=&free_mem_end;

	/* メモリーテーブル分メモリーを割り当てる（横取不可）。 */
	last=ROUNDUP(PHYSICAL_PAGE_TABLE_ADD+all_memory_size/PAGE_SIZE*sizeof(PHYS_PAGE),PAGE_SIZE)/PAGE_SIZE;
	if(&p[PHYSICAL_PAGE_TABLE_ADD/PAGE_SIZE]==free_mem_top.next)
		free_mem_top.next=&p[last];
	else p[PHYSICAL_PAGE_TABLE_ADD/PAGE_SIZE-1].next=&p[last];

	/* Used linkを初期化する。 */
	used_mem_top.next=&used_mem_end;
	used_mem_end.prev=&used_mem_top;
	used_usrmem_new=&used_mem_end;
}


/*
 * カーネル空間へ物理ページを割り当てる。
 * 割り当てたページは横取り不可
 * return : Allocate addres or NULL
 */
void *alloc_kernel_page()
{
	void *address=NULL;
	bool used=0;
	PHYS_PAGE *p;


	enter_spinlock(&page_alloc_gate);
	{
		if(((p=free_mem_top.next)!=&free_mem_end)&((address=get_addr_from_physpage(p))<(void*)KERNEL_DATA_END))
		{
			p->ref_count=1;
			free_mem_top.next=p->next;
			p->next->prev=&free_mem_top;
		}
		else if(((p=used_mem_top.next)!=&used_mem_end)&(used_usrmem_new!=p))
		{
			address=get_addr_from_physpage(p);
			used_mem_top.next=p->next;
			p->next->prev=&used_mem_top;
			used=1;
		}
	}
	exit_spinlock(&page_alloc_gate);

	/* メモリーの内容をバックアップする。 */
	if(used&&(buckup_used_page(address)==-1))return NULL;

	return address;
}


/*
 * ユーザー空間へ物理ページを割り当てる。
 * 割り当てたページは横取り可
 * parameters : ページテーブルアドレス
 * return : Allocate addres or NULL
 */
void *alloc_user_page(uint *page_tbl)
{
	void *address=NULL;
	bool used=0;
	PHYS_PAGE *p;
	PAGE_TBL_ADD *ptadd;


	enter_spinlock(&page_alloc_gate);
	{
		if((p=free_mem_end.prev)!=&free_mem_top)
		{
			address=(void*)get_addr_from_physpage(p);
			p->ref_count=1;
			free_mem_end.prev=p->prev;
			p->prev->next=&free_mem_end;

		}
		else if((p=used_mem_end.prev)!=&used_mem_top)
		{
			address=(void*)get_addr_from_physpage(p);
			used_mem_end.prev=p->prev;
			p->prev->next=&used_mem_end;
			used=1;
			if(used_usrmem_new==p)used_usrmem_new=&used_mem_end;
		}
		else
		{
			exit_spinlock(&page_alloc_gate);
			return NULL;
		}

		/* used linkに加える。 */
		if(address<(void*)KERNEL_DATA_END)
		{
			p->prev=&used_mem_top;
			p->next=used_mem_top.next;
			used_mem_top.next=p;
			p->next->prev=p;
		}
		else
		{
			p->prev=used_usrmem_new->prev;
			p->next=used_usrmem_new;
			used_usrmem_new->prev=p;
			p->prev->next=p;
			used_usrmem_new=p;
		}
	}
	exit_spinlock(&page_alloc_gate);

	/* メモリーの内容をback up */
	if(used&&(buckup_used_page(address)==-1))return NULL;

	/* ページテーブルアドレスリンクに加える */
	if((ptadd=kmalloc(sizeof(PAGE_TBL_ADD)))==NULL)
	{
		unlink_page(address);
		return NULL;
	}
	ptadd->page_table=page_tbl;
	ptadd->proc=get_current_task();
	ptadd->next=p->page_tbl_add;
	p->page_tbl_add=ptadd;

	return address;
}


/*
 * 物理メモリーページの参照カウントを増やす。
 * parameters : 物理アドレス
 */
void link_page(void *add)
{
	PHYS_PAGE *p;


	p=get_physpage_from_addr(add);

	enter_spinlock(&page_alloc_gate);
	{
		if(++p->ref_count==2)
		{
			/* Used linkから削除する */
			p->prev->next=p->next;
			p->next->prev=p->prev;
			if(used_usrmem_new==p)used_usrmem_new=p->next;
		}
	}
	exit_spinlock(&page_alloc_gate);
}


/*
 * 参照カウントを減らし、物理メモリーページを開放する。
 * parameters : 物理アドレス
 */
void unlink_page(void *add)
{
	PROC *proc;
	PHYS_PAGE *p;
	PAGE_TBL_ADD *q=NULL,**r;


	p=get_physpage_from_addr(add);

	enter_spinlock(&page_alloc_gate);
	{
		switch(--p->ref_count)
		{
			case -1:			/* 既に使われていない、または割り当て不可のページ */
				p->ref_count=0;
				break;
			case 0:
				/* Used linkから削除する */
				p->prev->next=p->next;
				p->next->prev=p->prev;
				if(used_usrmem_new==p)used_usrmem_new=p->next;

				if(add<(void*)KERNEL_DATA_END)
				{
					p->next=free_mem_top.next;
					p->prev=&free_mem_top;
					free_mem_top.next=p;
					p->next->prev=p;
				}
				else
				{
					p->prev=free_mem_end.prev;
					p->next=&free_mem_end;
					free_mem_end.prev=p;
					p->prev->next=p;
				}

				p->heap_num=0;
				q=p->page_tbl_add;
				p->page_tbl_add=NULL;

				break;
			case 1:
				/* used linkに加える。 */
				if(add<(void*)KERNEL_DATA_END)
				{
					p->prev=&used_mem_top;
					p->next=used_mem_top.next;
					used_mem_top.next=p;
					p->next->prev=p;
				}
				else
				{
					p->prev=used_usrmem_new->prev;
					p->next=used_usrmem_new;
					used_usrmem_new->prev=p;
					p->prev->next=p;
					used_usrmem_new=p;
				}
			default:
				/* Page table address linkから削除する */
				proc=cputask[get_current_cpu()].current_task;
				for(q=p->page_tbl_add,r=&p->page_tbl_add;q!=NULL;r=&q->next,q=q->next)
					if(q->proc==proc)
					{
						*r=q->next;
						break;
					}
		}
	}
	exit_spinlock(&page_alloc_gate);

	kfree(q);
}


/*
 * 2の冪乗サイズごとにページサイズメモリーを割り当て分割する
 * 最大割当サイズはページサイズ
 * parameters : Size by byte
 * return : address
 */
void *kmalloc(size_t size)
{
	char *addr=NULL;
	int i;


	if((size==0)||(size>PAGE_SIZE))return NULL;		/* Require size is over */

	for(i=1;size>heap[i].size;++i);					/* Require size=heap[i].size */

	enter_spinlock(&heap[i].gate);
	{
		if(heap[i].free==&guard[i])
		{
			if((heap[i].free=(FREE*)alloc_kernel_page())==NULL)goto OUT;

			/*
			 * 開放するとき、addressから割当サイズがわかるようにする
			 */
			get_physpage_from_addr((void*)heap[i].free)->heap_num=i;

			heap[i].free->prev=&guard[i];
			heap[i].free->next=&guard[i];
			guard[i].prev=heap[i].free;
			guard[i].next=heap[i].free;
			heap[i].free->last=(char*)heap[i].free+PAGE_SIZE;
		}

		addr=(char*)heap[i].free->last-heap[i].size;

		if(heap[i].free==(FREE*)addr)
		{
			heap[i].free=((FREE*)addr)->next;
			((FREE*)addr)->prev->next=((FREE*)addr)->next;
			((FREE*)addr)->next->prev=((FREE*)addr)->prev;
		}
		else heap[i].free->last=addr;
	}
OUT:
	exit_spinlock(&heap[i].gate);

	return addr;
}


/*
 * メモリー開放。
 * parameters : Address
 */
void kfree(void *ptr)
{
	uint i;
	FREE *p,*end;


	if(ptr==NULL)return;

	if((i=(int)get_physpage_from_addr(ptr)->heap_num)==0)return;

	enter_spinlock(&heap[i].gate);
	{
		/* Free linkは昇順 */
		for(p=heap[i].free;p!=&guard[i];p=p->next)
			if(p>(FREE*)ptr)break;

		/* Linkに挿入する */
		p->prev->next=(FREE*)ptr;
		((FREE*)ptr)->prev=p->prev;
		((FREE*)ptr)->next=p;
		((FREE*)ptr)->last=(char*)ptr+heap[i].size;
		p->prev=(FREE*)ptr;

		/* アドレスが続いていればメモリーをつなげる */
		end=((FREE*)ptr)->prev;
		do
		{
			p=p->prev;

			if(p->last==(char*)p->next)
			{
				p->last=p->next->last;
				p->next=p->next->next;
			}

			/* 開放したメモリーがPAGE_SIZEならページを開放する */
			if(!((uint)p&PAGE_BOUND)&&(p->last>=(char*)p+PAGE_SIZE))
			{
				if(p->last>(char*)p+PAGE_SIZE)
				{
					p->prev->next=(FREE*)((char*)p+PAGE_SIZE);
					p->prev->next->next=p->next;
					p->prev->next->prev=p->prev;
					p->prev->next->last=p->last;
					p->next->prev=p->prev->next;
				}
				else
				{
					p->prev->next=p->next;
					p->next->prev=p->prev;
				}
				unlink_page(p);
				break;
			}

		}while(p!=end);

		/* Free memory link の先頭を代入する */
		heap[i].free=guard[i].next;
	}
	exit_spinlock(&heap[i].gate);
}


/***********************************************************************************
 *
 * ページング関数
 *
 * ページアウトの条件
 * ・参照カウントが１であること
 *
 ***********************************************************************************/


/*
 * カーネルページテーブルを設定する
 * return : Page directory or NULL
 */
uint *init_idle_page()
{
	void *stack;
	uint *idle_pagedir,*pagetable;
	uint i;


	/* カーネルページディレクトリの設定 */
	if((idle_pagedir=(uint*)alloc_kernel_page())==NULL)return NULL;
	memset(idle_pagedir,0,PAGE_SIZE);
	for(i=0;i<KERNEL_STACK_BEG/PAGEDIR_ALLOC_SIZE;++i)
		idle_pagedir[i]=i*PAGEDIR_ALLOC_SIZE|PAGE_PRESEN|PAGE_RW|PAGE_4M;

	/* カーネルスタックページを割り当てる */
	if((pagetable=(uint*)alloc_kernel_page())==NULL)return NULL;
	memset(pagetable,0,PAGE_SIZE);
	idle_pagedir[KERNEL_STACK_BEG/PAGEDIR_ALLOC_SIZE]=(uint)pagetable|PAGE_PRESEN|PAGE_RW;
	if((stack=alloc_kernel_page())==NULL)return NULL;
	pagetable[1023]=(uint)stack|PAGE_PRESEN|PAGE_RW;

	/* メモリーマップドIOページ用ディレクトリの設定 */
	for(i=IOMAP_BEG/PAGEDIR_ALLOC_SIZE;i<1024;++i)
	   	idle_pagedir[i]=i*PAGEDIR_ALLOC_SIZE|PAGE_PRESEN|PAGE_RW|PAGE_4M|PAGE_CASH_DISABLE;

	/* ページング開始 */
	asm volatile(
			"movl	%%eax,%%cr3\n"			/* Set page directory address */
			"movl	$0x10,%%eax\n"
			"movl	%%eax,%%cr4\n"			/* 4Mページ拡張 */
			"movl	%%cr0,%%eax\n"
			"orl	$0x80010000,%%eax\n"	/* Enable paging,Enable wp(スーパーバイザからの書き込み保護) */
			"movl	%%eax,%%cr0\n"
			"jmp	1f\n"
		"1:"
		::"a"(idle_pagedir)
	);

	return idle_pagedir;
}


/*
 * ページングでのページ内容の保存先の初期化。
 * 参照の最大数は255
 * parameters : 保存先デバイス名
 * return : 0 or Error number
 */
int init_page_backstore(char *path)
{
	enum{BYTE_BIT=8};

	int i,last;
	PHYS_PAGE *p,*q,*r,*beg,*befo;


	/* デバイスからサイズを取得する */
	page_store_size=0x4000000;

	/* 参照カウント用テーブルを連続領域で割り当てる（横取不可） */
	last=ROUNDUP(page_store_size/PAGE_SIZE,PAGE_SIZE)/PAGE_SIZE;
	for(p=free_mem_top.next,befo=NULL;;befo=r)
	{
		beg=p;
		if(p==NULL)return -ENOMEM;
		for(i=0,q=p;p==q;r=p,p=p->next,++q)
			if(++i==last)goto EXIT1;
	}
EXIT1:
	if(befo==NULL)free_mem_top.next=p->next;
	else befo->next=p->next;
	page_store_ref_table=get_addr_from_physpage(beg);
	memset(page_store_ref_table,0,last*PAGE_SIZE);

	/* 空き検索用テーブルを連続領域で割り当てる（横取不可） */
	last=ROUNDUP(page_store_size/PAGE_SIZE/BYTE_BIT,PAGE_SIZE)/PAGE_SIZE;
	for(p=free_mem_top.next,befo=NULL;;befo=r)
	{
		beg=p;
		if(p==NULL)return -ENOMEM;
		for(i=0,q=p;p==q;r=p,p=p->next,++q)
			if(++i==last)goto EXIT2;
	}
EXIT2:
	if(befo==NULL)free_mem_top.next=p->next;
	else befo->next=p->next;
	page_store_use_table=get_addr_from_physpage(beg);
	memset(page_store_use_table,0,last*PAGE_SIZE);

	return 0;
}


/*
 * Except of page fault
 * parameters : Exception frame
 */
void page_fault(EXCEPT_FRAME frame)
{

}


/*
 * Buck up used page into page store device
 * parameters : physical address
 * returns : 0 or Error number
 */
int buckup_used_page(void *addr)
{
	PHYS_PAGE *phys_page;
	PAGE_TBL_ADD *p,*beg;
	int store_num;
	int cpu;
	int i,j,last;


	phys_page=get_physpage_from_addr(addr);
	cpu=get_current_cpu();

	if((beg=phys_page->page_tbl_add)==NULL)return -ENOMEM;
	p=beg;
	do
	{
		/* page tableを不在に設定 */
		enter_spinlock(&p->proc->pagedir_gate);
		{
			*p->page_table&=~PAGE_PRESEN;
		}
		exit_spinlock(&p->proc->pagedir_gate);

		/* processが実行中ならpage tableをTBLからフラッシュする */
		if((p->proc->cpu!=cpu)&&(cputask[p->proc->cpu].current_task==p->proc))
			issue_interrupt_to_cpu(p->proc->cpu,FLASH_PAGEDIR_VECT);
	}while((p=p->next)!=beg);

	/* Search store space from page_store_use_table */
	last=page_store_size/PAGE_SIZE/INT_BIT;
	enter_spinlock(&page_sotre_gate);
	{
		for(i=unuse_store_table;page_store_use_table[i]!=~0;)
		{
			if(++i>=last)i=0;
			if(i==unuse_store_table)
			{
				exit_spinlock(&page_sotre_gate);
				return -ENOMEM;
			}
		}
		for(j=0;page_store_use_table[i]&(1<<j);++j);
		page_store_use_table[i]|=(1<<j);
		store_num=i*INT_BIT+j;

		unuse_store_table=i;
	}
	exit_spinlock(&page_sotre_gate);

	/* Store page to store device */
	/* ??????????????????????????? */

	return 0;
}


/*
 * Set pagetable
 * parameters : Load page address,Sorce page address,Page flag,Load process
 * return : 0 or Error number
 */
int load_user_page(void *dist,int flag,PROC *proc)
{
	void *page;
	uint *pagetable;


	enter_spinlock(&proc->pagedir_gate);
	{
		pagetable=(uint*)proc->pagedir[(uint)dist/PAGEDIR_ALLOC_SIZE];

	    if(pagetable==0)
	    {
	    	/* page tableを割り当てる */
	    	if((pagetable=(uint*)alloc_kernel_page())==NULL)goto ERROR;
	    	memset(pagetable,0,PAGE_SIZE);
	    	proc->pagedir[(uint)dist/PAGEDIR_ALLOC_SIZE]=(uint)pagetable|PAGE_PRESEN|PAGE_USER|PAGE_RW;
	    }
	    else pagetable=(uint*)((uint)pagetable&PAGE_ROUNDDOWN);

		/* pageを割り当てる */
		if((page=alloc_user_page(&pagetable[((uint)dist&(PAGEDIR_ALLOC_SIZE-1))/PAGE_SIZE]))==NULL)goto ERROR;
		pagetable[((uint)dist&(PAGEDIR_ALLOC_SIZE-1))/PAGE_SIZE]=(uint)page|PAGE_PRESEN|PAGE_USER|flag;
	}
	exit_spinlock(&proc->pagedir_gate);

	return 0;

ERROR:
	exit_spinlock(&proc->pagedir_gate);

	return -ENOMEM;
}


/*
 * page tableを開放する
 * text sectionとstack以外のdata sectionを開放する。user stackは1pageだけ残して開放する。
 * parameters : process
 */
void free_user_page(PROC *proc)
{
	uint *page;
	int i,j;


	enter_spinlock(&proc->pagedir_gate);
	{
		/* user stackの最初のpageだけ残して開放する */
		page=(uint*)(proc->pagedir[USER_STACK_BEG/PAGEDIR_ALLOC_SIZE]&PAGE_ROUNDDOWN);
	    for(j=0;j<PAGE_SIZE/sizeof(uint)-1;++j)
	    {
	    	if(page[j]==0)continue;
	    	else if(page[j]&PAGE_PRESEN)
	    		unlink_page((void*)page[j]);
			else
			{
				if(--page_store_ref_table[page[j]>>1]==0)
					clear_page_store_use(page[j]>>1);
			}

			page[j]=0;
		}

		/* 残りのuser pageをすべて開放する */
		for(i=USER_BEG/PAGEDIR_ALLOC_SIZE;i<USER_STACK_BEG/PAGEDIR_ALLOC_SIZE;++i)
	    	if(proc->pagedir[i]&PAGE_PRESEN)
	    	{
	    		page=(uint*)(proc->pagedir[i]&PAGE_ROUNDDOWN);
	    		for(j=0;j<PAGE_SIZE/sizeof(uint);++j)
	    		{
	    			if(page[j]==0)continue;
	    			else if(page[j]&PAGE_PRESEN)unlink_page((void*)page[j]);
					else
					{
						if(--page_store_ref_table[page[j]>>1]==0)
							clear_page_store_use(page[j]>>1);
					}
				}

				/* page tableを開放する */
				unlink_page(page);

				/* page direstoryを0クリアー */
				proc->pagedir[i]=0;
			}
	}
	exit_spinlock(&proc->pagedir_gate);
}


/***********************************************************************************
 *
 * IOキャッシュ用関数
 *
 ***********************************************************************************/

#ifdef IO_CASH
/*
 * Init cache
 * return : 0 or Error number
 */
int init_cache()
{
	int i;


	/* Init cache address */
	for(i=0;i<CACHE_NUM;++i)
	{
		if((cache_tbl[i].addr=alloc_kernel_page())==NULL)return -ENOMEM;
		cache_tbl[i].next=&cache_tbl[i+1];
		cache_tbl[i].prev=&cache_tbl[i-1];
		cache_tbl[i].hprev=&cache_tbl[i];
		cache_tbl[i].hnext=NULL;
		cache_tbl[i].wait_queue.gate=0;
		cache_tbl[i].wait_queue.flag=0;
		cache_tbl[i].wait_queue.wait_next=(PROC*)&cache_tbl[i].wait_queue;
		cache_tbl[i].wait_queue.wait_prev=NULL;
	}
	cache_tbl[CACHE_NUM-1].next=&cache_tbl[0];
	cache_tbl[0].prev=&cache_tbl[CACHE_NUM-1];

	/* Init hash table */
	for(i=0;i<CACHE_NUM/2;++i)cache_hash[i]=NULL;

	return 0;
}


/*
 * Read from cache
 * parameters : Device number,Device block
 * return : Cache address or NULL
 */
void *read_cache(int dev,uint block)
{
	int hash;
	int read_dev=0;		/* Read device flag */
	CACHE_TABLE *p;


	hash=(block/4)%HASH_NUM;

	enter_spinlock(&cache_tbl_gate);
	{
		/* Search in hash table */
	    for(p=cache_hash[hash];;p=p->hnext)
	    {
	    	/* キャッシュになし */
	    	if(p==NULL)
	    	{
				/* 一番古いキャッシュを選択 */
        		p=cache_tbl_old;
        		cache_tbl_old=p->next;

        		/* Delete old hash link */
        		p->hprev->hnext=p->hnext;
        		if(p->hnext!=NULL)p->hnext->hprev=p->hprev;

        		/* Add new hash link */
        		p->hnext=cache_hash[hash];
        		p->hprev=(CACHE_TABLE*)&cache_hash[hash];
        		if(cache_hash[hash]!=NULL)cache_hash[hash]->hprev=p;
        		cache_hash[hash]=p;

        		p->dev=dev;
        		p->block=block;

				wait_proc(&p->wait_queue);

				read_dev=1;
				break;
	    	}

	    	/* キャッシュにあり */
	    	if((p->block==block)&&(p->dev==dev))
	    	{
	    		/* キャッシュリンクの先頭に移動 */
	    		p->next->prev=p->prev;
	    		p->prev->next=p->next;
	    		p->prev=cache_tbl_old->prev;
	    		p->next=cache_tbl_old;
	    		cache_tbl_old->prev=p;
	    		p->prev->next=p;

	    		break;
	    	}
	    }
	}
	exit_spinlock(&cache_tbl_gate);

	if(read_dev)
	{

		if(device[dev]->read(p->addr,CACHE_BLOCK_SIZE,block)<0)
		{
			wake_proc(&p->wait_queue);
			return NULL;
		}
	}
	else wait_proc(&p->wait_queue);

	wake_proc(&p->wait_queue);

	return p->addr;
}


/*
 * Write to cache
 * parameters : Device number,Device block,Sorce user buffer,Write bytes(in cache size),Begin offset in cache
 * return : Write bytes or error=-1
 */
int write_cache(int dev,uint block,void *buf,int bytes,size_t begin)
{
	int hash;
	int read_dev=0;		/* Read device flag */
	int error;
	CACHE_TABLE *p;


	if(bytes<=0)return 0;
	if(bytes+begin>CACHE_BLOCK_SIZE)return -EINVAL;

	hash=(block/4)%HASH_NUM;

	enter_spinlock(&cache_tbl_gate);
	{
		/* Search in hash table */
	    for(p=cache_hash[hash];;p=p->hnext)
	    {
	    	/* キャッシュになし */
	    	if(p==NULL)
	    	{
				/* 一番古いキャッシュを選択 */
        		p=cache_tbl_old;
        		cache_tbl_old=p->next;

        		/* Delete old hash link */
        		p->hprev->hnext=p->hnext;
        		if(p->hnext!=NULL)p->hnext->hprev=p->hprev;

        		/* Add new hash link */
        		p->hnext=cache_hash[hash];
        		p->hprev=(CACHE_TABLE*)&cache_hash[hash];
        		if(cache_hash[hash]!=NULL)cache_hash[hash]->hprev=p;
        		cache_hash[hash]=p;

        		p->dev=dev;
        		p->block=block;

				read_dev=1;

				break;
	    	}

	    	/* キャッシュにあり */
	    	if((p->block==block)&&(p->dev==dev))
	    	{
	    		/* キャッシュリンクの先頭に移動 */
	    		p->next->prev=p->prev;
	    		p->prev->next=p->next;
	    		p->prev=cache_tbl_old->prev;
	    		p->next=cache_tbl_old;
	    		cache_tbl_old->prev=p;
	    		p->prev->next=p;

	    		break;
	    	}
	    }

	    wait_proc(&p->wait_queue);
	}
	exit_spinlock(&cache_tbl_gate);

	if(read_dev)
		if(device[dev]->read(p->addr,CACHE_BLOCK_SIZE,block)<0)
		{
			wake_proc(&p->wait_queue);
			return -1;
		}
	memcpy_from_user((void*)((uint)p->addr+begin),buf,bytes);

	wake_proc(&p->wait_queue);

	if((error=device[dev]->write(p->addr,CACHE_BLOCK_SIZE/SECTER_SIZE,block))<0)return error;

	return (bytes<=error)?bytes:error;
}
#endif /* IO_CASH */


/***********************************************************************************
 *
 * システムコール用関数
 *
 ***********************************************************************************/


/*
 * forkによりメモリーコンテキストをコピーする
 * page directory、page tableのコピー、子プロセススタックポインタのアドレス
 * parameters : Destination page directory,Return stack offset
 * return : New Page directory or NULL
 */
uint *fork_page(PROC *org,PROC *proc,void **stack)
{
	uint *dir,*org_page,*page;
	int i,j;


	/* Page directoryをコピーする */
	if((dir=(uint*)alloc_kernel_page())==NULL)return NULL;
	memcpy(dir,org->pagedir,PAGE_SIZE);

	/* Kernel stackを割り当てる */
	if((page=(uint*)alloc_kernel_page())==NULL)goto ERROR1;

	dir[KERNEL_STACK_BEG/PAGEDIR_ALLOC_SIZE]=(uint)page|PAGE_PRESEN|PAGE_RW;
	org_page=(uint*)(org->pagedir[KERNEL_STACK_BEG/PAGEDIR_ALLOC_SIZE]&PAGE_ROUNDDOWN);
	if((*stack=alloc_kernel_page())==NULL)goto ERROR2;
	memcpy(*stack,(void*)(org_page[1023]&PAGE_ROUNDDOWN),PAGE_SIZE);
	page[1023]=(uint)*stack|PAGE_PRESEN|PAGE_RW;
	memset(page,0,1023*sizeof(uint));

	/* User領域をコピーオンライトに設定する */
	for(i=USER_BEG/PAGEDIR_ALLOC_SIZE;i<USER_END/PAGEDIR_ALLOC_SIZE;++i)
		if(dir[i]!=0)
		{
			org_page=(uint*)(dir[i]&PAGE_ROUNDDOWN);
			enter_spinlock(&org->pagedir_gate);
			{
				clear_rwflag(org_page);
			}
			exit_spinlock(&org->pagedir_gate);

			for(j=0;j<PAGE_NUM;++j)
			{
				if(org_page[j]&PAGE_PRESEN)link_page((void*)org_page[j]);
				else if(org_page[j]!=0)
					/* Increase reference count of Swap table */
					++page_store_ref_table[org_page[j]>>1];
			}
		}

	return dir;


ERROR2:
	unlink_page((void*)page[1023]);
	unlink_page(page);
ERROR1:
	unlink_page(dir);

	return NULL;
}
