/*
 * proc.c
 *
 * Copyright 2002, Minoru Murashima. All rights reserved.
 * Distributed under the terms of the BSD License.
 *
 * プロセス関係
 */


#include"types.h"
#include"mm.h"
#include"errno.h"
#include"interrupt.h"
#include"lock.h"
#include"segment.h"
#include"time.h"
#include"proc.h"
/******************************/
#include"lib.h"
#include"test.h"
/*******************************/


/* tss格納構造体(104バイト) */
typedef struct{
	uint link,esp0,ss0,esp1,ss1,esp2,ss2,cr3,eip,eflags,eax,ecx,
		edx,ebx,esp,ebp,esi,edi,es,cs,ss,ds,fs,gs,ldts,tflag_iomap;
}TSS;

/* sys_fork用register待避構造体 */
typedef struct{
	uint ebx;
	uint ebp;
	uint esi;
	uint edi;
}SAVE_REG;

/* sys_fork用コンテキスト保存スタック構造体 */
typedef struct{
	uint before_esp;		/* ネスト割り込み用前の割り込み時のesp値 */
	uint ds;
	uint es;
	uint edi;
	uint esi;
	uint ebp;
	uint esp;
	uint ebx;
	uint edx;
	uint ecx;
	uint eax;
	uint eip;
	uint cs;
	uint eflags;
}INTR_FRAME;


CPU cputask[MAX_CPU];		/* Task infomations for each cpu */

/* TSS、タスキング時のスタックスイッチに必要 */
static TSS kernel_tss={
	0,
	KERNEL_ESP_BEG,	/* esp0 */
	KERNEL_DATA_DES,	/* ss0 */
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
};
static PROC *idle_proc[MAX_CPU];	/* Idle process */
static CPU guard[2]={				/* Guard for link */
	{0,NULL,0,NULL,NULL},
	{0,NULL,~0,NULL,NULL}
};
static int cputask_gate=0;			/* Lock gate for cputask[] */
static int few_task_cpu=0;			/* タスク数の一番少ないcpu */
static int page_table_gate=0;		/* Lock gate for page table */


static void add_proc(PROC*);
static int change_cpu_link(int);


/******************************************************************************************************
 *
 * タスクスケジュール関係
 *
 *******************************************************************************************************/


/*
 * Add task to shedule
 * parameters : Process
 */
void add_to_schedule(PROC *proc)
{
	int cpu;


	cpu=proc->cpu;

	cli();
	enter_spinlock(&cputask[cpu].gate);
	{
		if(cputask[cpu].current_task==idle_proc[cpu])
		{
			proc->next=proc;
			proc->prev=proc;
			idle_proc[cpu]->next=proc;
		}
		else
		{
			/*現在リンクされているところから削除する。 */
			proc->prev->next=proc->next;
			proc->next->prev=proc->prev;

			/* カレントプロセスの次に加える */
			proc->prev=cputask[cpu].current_task;
			proc->next=cputask[cpu].current_task->next;
			cputask[cpu].current_task->next=proc;
			proc->next->prev=proc;
		}
	}
	exit_spinlock(&cputask[cpu].gate);
	sti();
}


/*
 * Delete process
 * parameters : process
 */
void del_from_schedule(PROC *proc)
{
	int cpu;


	cpu=proc->cpu;

	/* Delete current task */
	cli();
	enter_spinlock(&cputask[cpu].gate);
	{
		if(proc->next==proc)
		{
			idle_proc[cpu]->next=idle_proc[cpu];
			idle_proc[cpu]->prev=idle_proc[cpu];
			proc->next=idle_proc[cpu];
		}
		else
		{
			proc->prev->next=proc->next;
			proc->next->prev=proc->prev;
		}
	}
	exit_spinlock(&cputask[cpu].gate);
	sti();
}


/*
 * Process switch
 * parameters : 今回の割り込み時のセーブスタックトップesp値,前の割り込み時のesp値を保存するスタック
 * returns : Page directory
 */
uint *switch_task(volatile uint esp,volatile uint before_esp)
{
	int cpu;
	PROC *current;


	cpu=get_current_cpu();
	current=cputask[cpu].current_task;
	before_esp=current->esp;
	current->esp=esp;

	current=current->next;
	esp=(uint)&current->esp;
	cputask[cpu].current_task=current;

	return current->pagedir;
}


/******************************************************************************************************
 *
 * プロセス操作
 *
 *******************************************************************************************************/


/*
 * プロセスを実行可能にする
 * parameters : process
 */
void add_proc(PROC *proc)
{
	int cpu;


	if(proc==NULL)return;

	cpu=few_task_cpu;
	proc->cpu=cpu;
	add_to_schedule(proc);
	++cputask[cpu].proc_num;

	/* Change cpu in which minimum numbers of task */
	few_task_cpu=change_cpu_link(cpu);
}


/*
 * Change cpu struct link to previous
 * parameters : cpu number
 * retuuns : Top link cpu number
 */
int change_cpu_link(int cpu)
{
	CPU *p;


	enter_spinlock(&cputask_gate);
	{
		/* to previous */
		if(cputask[cpu].proc_num<cputask[cpu].prev->proc_num)
		{
			for(p=cputask[cpu].prev->prev;cputask[cpu].proc_num<p->proc_num;p=p->prev);
			cputask[cpu].prev->next=cputask[cpu].next;
			cputask[cpu].next->prev=cputask[cpu].prev;
			cputask[cpu].prev=p;
			cputask[cpu].next=p->next;
			p->next->prev=&cputask[cpu];
			p->next=&cputask[cpu];
		}
		/* to next */
		else if(cputask[cpu].proc_num>cputask[cpu].next->proc_num)
		{
			for(p=cputask[cpu].next->next;cputask[cpu].proc_num>p->proc_num;p=p->next);
			cputask[cpu].prev->next=cputask[cpu].next;
			cputask[cpu].next->prev=cputask[cpu].prev;
			cputask[cpu].prev=p->prev;
			cputask[cpu].next=p;
			p->prev->next=&cputask[cpu];
			p->prev=&cputask[cpu];
		}
	}
	exit_spinlock(&cputask_gate);

	return guard[0].next->cpu;
}


/******************************************************************************************************
 *
 * 初期化
 *
 *******************************************************************************************************/


/*
 * Init taskcpu
 * cpu順にシリアルに設定する必要がある
 * parameters : number of cpu
 * returns : NOERROR
 */
int init_cputask(int cpu)
{
	cputask[cpu].cpu=cpu;
	cputask[cpu].current_task=NULL;
	cputask[cpu].proc_num=0;
	cputask[cpu].gate=0;
	cputask[cpu].next=&guard[1];
	guard[1].prev=&cputask[cpu];
	if(cpu==0)
	{
		cputask[cpu].prev=&guard[0];
		guard[0].next=&cputask[cpu];
	}
	else
	{
		cputask[cpu].prev=&cputask[cpu-1];
		cputask[cpu-1].next=&cputask[cpu];
	}

	return 0;
}


/*
 * TSSディスクリプタを設定す
 * 引数 : ディスクリプタ値、TSSアドレス
 */
void set_tss(int des)
{
	set_gdt(des,&kernel_tss,104,TYPE_TSS);

	asm volatile(
		"movl	%0,%%eax\n"
		"ltr	%%ax"
		::"m"(des)
	);
}


/*
 * Set idle process table
 * parameters : Page directory
 * returns : 0 or Error number
 */
int set_idle_proc(int cpu,uint *pagedir)
{
	/* Set process table */
	if((idle_proc[cpu]=(PROC*)kmalloc(sizeof(PROC)))==NULL)return -ENOMEM;
	idle_proc[cpu]->next=idle_proc[cpu];
	idle_proc[cpu]->prev=idle_proc[cpu];
	idle_proc[cpu]->pagedir=pagedir;
	idle_proc[cpu]->pagedir_gate=0;
	idle_proc[cpu]->cpu=cpu;

	/* add to schedule queue */
	cputask[cpu].current_task=idle_proc[cpu];

	return 0;
}


/******************************************************************************************************
 *
 * Task wait
 *
 *******************************************************************************************************/


/*
 * Wait process
 * 単方向リンク
 * parameters : Wait queue
 */
void wait_proc(WAIT_QUEUE *queue)
{
	PROC *proc;


	enter_spinlock(&queue->gate);
	{
		if(queue->flag==0)
		{
			queue->flag=1;
			exit_spinlock(&queue->gate);
			return;
		}
		else
		{
			proc=cputask[get_current_cpu()].current_task;

			/* Add process to wait queue */
			queue->wait_prev->wait_next=proc;
			proc->wait_next=(PROC*)queue;
			queue->wait_prev=proc;

			del_from_schedule(proc);
		}
	}
	exit_spinlock(&queue->gate);

	wait_task();
}


/*
 * Wake up process
 * parameters : Wait queue
 */
void wake_proc(WAIT_QUEUE *queue)
{
	PROC *proc;


	/* Delete process from wait queue */
	enter_spinlock(&queue->gate);
	{
		if(queue->wait_prev==(PROC*)queue)queue->flag=0;
		else
		{
			proc=queue->wait_next;
			queue->wait_next=proc->wait_next;
			if(queue->wait_prev==proc)queue->wait_prev=(PROC*)queue;

			/* Add process to schedule */
			add_to_schedule(proc);
		}
	}
	exit_spinlock(&queue->gate);
}


/*
 * タイマー割り込み待ち
 * parameters : タイマー割り込み構造体,タイム(ms)
 * return : 成功0,タイムアウト-1
 */
int wait_intr(WAIT_INTR *queue,int time)
{
	queue->proc=cputask[get_current_cpu()].current_task;

	cli();
	{
		if(queue->flag==1)
		{
			queue->flag=0;
			sti();
			return 0;
		}
		del_from_schedule(queue->proc);
		queue->flag=-1;
	}
	sti();

	sleep_task(time);

	if(queue->flag==1)queue->flag=0;

	return queue->flag;
}


/*
 * タイマー割り込み待ちから復帰する。
 * parameters : タイマー割り込み構造体
 * return : タスクスイッチ必要1,タスクスイッチしない0
 */
int wake_intr(WAIT_INTR *queue)
{
	if(queue->flag==-1)
	{
		add_to_schedule(queue->proc);
		del_timer(queue->proc);
	}
	queue->flag=1;

	return 1;
}


/******************************************************************************************************
 *
 * System call interface
 *
 *******************************************************************************************************/


/*
 * 子プロセスを生成し、稼動させる
 * return : Parent process return process ID,Child process return 0
 */
int sys_fork(SYSCALL0_FRAME frame)
{
	void *stack;
	uint *preturn=&frame.ds-1;
	PROC *parent,*child;
	INTR_FRAME *context_frame;
	SAVE_REG reg;


	/* 子プロセスのコンテキスト作成用に保存の必要なレジスタを待避する */
	asm volatile(
		"movl	%%ebp,%3"
		:"=b"(reg.ebx),"=S"(reg.esi),"=D"(reg.edi):"g"(reg.ebp)
	);

	/* PROC構造体を割り当てる */
	if((child=(PROC*)kmalloc(sizeof(PROC)))==NULL)return -ENOMEM;
	parent=cputask[get_current_cpu()].current_task;
	child->timer_count=0;
	child->parent=parent;
	child->child=NULL;
	child->fstruct=parent->fstruct;
	enter_spinlock(&page_table_gate);
	{
		child->brother=parent->child;
		parent->child=child;
	}
	exit_spinlock(&page_table_gate);
	child->pagedir_gate=0;

	/* Page tableを割り当てる */
	if((child->pagedir=fork_page(parent,child,&stack))==NULL)
	{
		kfree(child);
		return -ENOMEM;
	}

	/* 子プロセスのstackにコンテキストを設定する */
	context_frame=(INTR_FRAME*)(((uint)stack&PAGE_ROUNDDOWN)|(((uint)preturn&(PAGE_SIZE-1))-sizeof(INTR_FRAME)+sizeof(uint)));
	context_frame->before_esp=0;
	context_frame->ds=KERNEL_DATA_DES;
	context_frame->es=KERNEL_DATA_DES;
	context_frame->edi=reg.edi;
	context_frame->esi=reg.esi;
	context_frame->ebp=reg.ebp;
	context_frame->ebx=reg.ebx;
	context_frame->edx=0;
	context_frame->ecx=0;
	context_frame->eax=0;				/* Return 0 from sys_fork in child process */
	context_frame->eip=*preturn;
	context_frame->cs=KERNEL_CODE_DES;
	context_frame->eflags=0x202;
	child->esp=(uint)preturn-sizeof(INTR_FRAME)+sizeof(uint);

	/* Add child process to schedule queue */
	add_proc(child);

	return (int)child;
}


/*
 * プロセスを終了させる
 *
 */
int sys_exit()
{
	return 0;
}


/***********************************************************************/
/*
 * テストプログラムのロード
 */
int sys_exec(char *path,char *argv[],SYSCALL2_FRAME frame)
{
	PROC *proc;


	/* Get current process */
	proc=cputask[get_current_cpu()].current_task;

	/* pathからファイルを探しヘッダー情報を得る */

	/* User page tableを開放する */
	free_user_page(proc);

	/* 新しいプログラムをロードする */
	if(test_load(proc)==-1)return -1;

	return 0;
}
/************************************************************************/
