/*
 * except_i386.c
 *
 * Copyright 2002, Minoru Murashima. All rights reserved.
 * Distributed under the terms of the BSD License.
 *
 * 例外ハンドラ
 */


#include<lib.h>
#include<proc.h>
#include<interrupt.h>
#include<segment.h>
#include<signal.h>
#include<mm.h>
#include<lock.h>
#include<except_i386.h>


/************************************************************************************************
 *
 * デバッグ
 *
 ************************************************************************************************/


//================================== PRIVATE ============================================


/*
 * デバッグレジスタへの読み書き。
 */
static inline uint readDr0()
{
	uint rest;

	asm volatile(
		"movl	%%dr0,%%eax"
		:"=a"(rest):
	);

	return rest;
}

static inline void writeDr0(uint value)
{
	asm volatile(
		"movl	%%eax,%%dr0"
		::"a"(value)
	);
}

static inline void writeDr6(uint value)
{
	enum{DR6_RESERVE_BIT=0xffff0ff0,};	/* DR6予約ビット。 */

	value|=DR6_RESERVE_BIT;
	asm volatile(
		"movl	%%eax,%%dr6"
		::"a"(value)
	);
}

static inline void writeDr7(uint value)
{
	enum{DR7_RESERVE_BIT=0x400,};		/* DR7予約ビット。 */

	value|=DR7_RESERVE_BIT;
	asm volatile(
		"movl	%%eax,%%dr7"
		::"a"(value)
	);
}


//================================== PUBLIC =============================================


/*
 * デバッグの設定。1個のブレークポイントのみ設定。
 * parameters : ブレークポイントリニアアドレス,ブレーク条件
 */
void setDebug(uint break_addr, int condition)
{
	enum{
		DR7_LEGE_BIT=0x300,			/* DR7 LE・GEビット。 */
		DR7_LEN0_1BYTE=0x0,			/* DR7 LEN0 1バイト長。 */
		DR7_LEN0_4BYTE=0xc0000,		/* DR7 LEN0 4バイト長。 */
		DR7_G0_BIT=0x2,				/* DR7 DR0 global break enableビット。 */
	};

	cli();

	/* ブレークポイントの設定。 */
	writeDr0(break_addr);

	/* 条件の設定。 */
	switch (condition){
		case DEBUG_EXEC:
			condition <<= 16;
			condition |= DR7_LEN0_1BYTE;
		case DEBUG_WRITE:
		case DEBUG_RW:
			condition <<= 16;
			condition |= DR7_LEN0_4BYTE;
		default:
			return;
	}
	condition |= DR7_LEGE_BIT | DR7_G0_BIT;
	writeDr7(condition);

	sti();
}


/************************************************************************************************
 *
 * デバイス使用不可例外
 *
 ************************************************************************************************/


typedef struct{
	PROC *proc;
	uint saveAddr;
}BEFORE_FPU_PROC;


//================================== PROTECTED ==========================================


// 最近のデバイス使用不可例外を起こしたプロセス
static BEFORE_FPU_PROC beforeFpuProc[MAX_CPU];


//================================== PUBLIC =============================================


// 最近のデバイス使用不可例外を起こしたプロセスをクリアーする
void clearFpuBeforeProc()
{
	int cpu = get_current_cpu();
	int eflag;
	
	enterCli(&eflag);
	if (get_current_task() == beforeFpuProc[cpu].proc){
		beforeFpuProc[cpu].proc = NULL;
	}
	exitCli(&eflag);
}


// ユーザーハンドラを呼び出した時のFPUセーブアドレスの切り替え。
// プロセス構造体にあるFPUセーブフラグも切り替える。
void setFpuSaveAddr(uint saveAddr)
{
	int cpu = get_current_cpu();
	PROC *proc = get_current_task();
	int eflag;

	enterCli(&eflag);
	{
		if (beforeFpuProc[cpu].proc == proc){
			beforeFpuProc[cpu].saveAddr = saveAddr;
		}
		proc->fpu.saveBefore = proc->fpu.save;
		proc->fpu.save = 0;
	}
	exitCli(&eflag);
}


// ユーザーハンドラを呼び出した時のFPUセーブアドレスと、
// プロセス構造体にあるFPUセーブフラグをリセットする。
void resetFpuSaveAddr()
{
	int cpu = get_current_cpu();
	PROC *proc = get_current_task();
	int eflag;

	enterCli(&eflag);
	{
		beforeFpuProc[cpu].saveAddr = KERNEL_ESP_BEG - FPU_SAVE_SIZE;
		proc->fpu.save = proc->fpu.saveBefore;
		proc->fpu.saveBefore = 0;
	}
	exitCli(&eflag);
}


/************************************************************************************************
 *
 * 例外ハンドラ
 *
 ************************************************************************************************/


//================================== PRIVATE ============================================


/*
 * 共通例外ハンドラ。
 * parameters : メッセージ,シグナル,例外フレームアドレス
 */
static void exception(const char *message, int signal, INTERRUPT_FRAME *frame)
{
	printk("\nException! %s.\n",message);
	if ((frame->cs == KERNEL_CODE_DES) || (frame->cs == APM_CODE_DES)){
		uint *stack = &frame->user_esp;

		printk("error=0x%x eip=0x%x cs=0x%x esp=0x%x proc=0x%x\n",
			frame->error, frame->eip, frame->cs, &frame->user_esp, get_current_task());
		printk("stack:0x%x 0x%x 0x%x 0x%x 0x%x\n", stack[0], stack[1], stack[2], stack[3], stack[4]); 
	}
	else{
		printk("error=0x%x eip=0x%x cs=0x%x esp=0x%x,ss=0x%x\n",frame->error,frame->eip,frame->cs,frame->user_esp,frame->user_ss);
	}

	forceSendSignal(get_current_task(), signal, TASK_SIGNAL_WAIT);
}


/*
 * 仮の例外ハンドラ
 * スタックに積まれた値を表示してアイドルにジャンプする
 */
void tmp_except(const char *message, INTERRUPT_FRAME *frame)
{
	printk("\nException! %s.\n",message);
	if((frame->cs==KERNEL_CODE_DES)||(frame->cs==APM_CODE_DES)){
		printk("error=0x%x eip=0x%x cs=0x%x esp=0x%x proc=0x%x cpu=%d\n",
			frame->error,frame->eip,frame->cs,&frame->user_esp, get_current_task(), get_current_cpu());
	}
	else{
		printk("error=0x%x eip=0x%x cs=0x%x esp=0x%x,ss=0x%x\n",frame->error,frame->eip,frame->cs,frame->user_esp,frame->user_ss);
	}

	idle();
}


//================================== PUBLIC =============================================


// 除算エラー。
void excDivideError(INTERRUPT_FRAME frame)
{
	exception("Divide error",SIGFPE,&frame);
}


// デバッグ。
void debug(INTERRUPT_FRAME frame)
{
	if(frame.cs==KERNEL_CODE_DES)
		printk("\nBreak point trap! : break point=0x%x,eip=0x%x,cs=0x%x,esp=0x%x,process=0x%x\n",
			readDr0(),frame.eip,frame.cs,&frame.user_esp,get_current_task());
	else
		printk("\nBreak point trap! : break point=0x%x,eip=0x%x,cs=0x%x,esp=0x%x,process=0x%x\n",
			readDr0(),frame.eip,frame.cs,frame.user_esp,get_current_task());

	/* デバッグレジスタのリセット。 */
	writeDr7(0);
	writeDr6(0);

	idle();
}


// NMI割り込み。
void excNonmaskableInterrupt(INTERRUPT_FRAME frame)
{
	char value;


	printk("\nException! Nonmaskable interrupt.\n");

	value=inb(0x61);		/* System control portB. */
	if(value&0x80)
		printk("Memory parity error.\n");
	if(value&0x40)
		printk("IO channel parity error.\n");
	if((value&0xc0)==0)
		printk("Unknown NMI error.\n");

	/* Reset NMI interrupt. */
	outb(0x70,0x8f);
	inb(0x71);
	outb(0x70,0x0f);
	inb(0x71);
}


// ブレークポイント。
void excBreakpoint(INTERRUPT_FRAME frame)
{
	exception("Breakpoint",SIGTRAP,&frame);
}


// オーバーフロー。
void excOverflow(INTERRUPT_FRAME frame)
{
	exception("Overflow",SIGSEGV,&frame);
}


// BOUNDの範囲外。
void excBoundsCheck(INTERRUPT_FRAME frame)
{
	exception("Bounds check",SIGSEGV,&frame);
}


// 無効オペコード。
void excInvalidOpcode(INTERRUPT_FRAME frame)
{
	exception("Invalid opcode",SIGILL,&frame);
}


// デバイス使用不可例外
void excCoprocessorNotAvailable(INTERRUPT_FRAME frame)
{
	int cpu = get_current_cpu();
	PROC *proc = get_current_task();

	cli();
	{
		// TSフラグをクリアー
		clearTs();

		// 直前のFPU使用プロセスのセーブ領域にfpuレジスタをセーブする。
		if (beforeFpuProc[cpu].proc != NULL){
			if ((beforeFpuProc[cpu].proc != proc) || (beforeFpuProc[cpu].saveAddr != KERNEL_ESP_BEG - FPU_SAVE_SIZE)){
				uint saveAddr = getPhysicalFromLogicalAddr(beforeFpuProc[cpu].proc, beforeFpuProc[cpu].saveAddr);
				fpuSave(saveAddr);
				if (beforeFpuProc[cpu].saveAddr == KERNEL_ESP_BEG - FPU_SAVE_SIZE){
					beforeFpuProc[cpu].proc->fpu.save = 1;
				}
				else{
					// ユーザーハンドラ実行前FTPセーブフラグを設定する。
					beforeFpuProc[cpu].proc->fpu.saveBefore = 1;
				}
			}
			else{
				beforeFpuProc[cpu].proc->fpu.save = 0;
			}
		}

		beforeFpuProc[cpu].proc = proc;
		beforeFpuProc[cpu].saveAddr = KERNEL_ESP_BEG - FPU_SAVE_SIZE;
	}
	sti();

	// fpuレジスタのロード
	if (proc->fpu.save){
		fpuStore(KERNEL_ESP_BEG - FPU_SAVE_SIZE);
		proc->fpu.save = 0;
	}
}


// ダブルフォルト。
void excDoubleFault(INTERRUPT_FRAME frame)
{
	exception("Double fault",SIGSEGV,&frame);
}


// コプロセッサオーバーラン。
void excCopressorOverrun(INTERRUPT_FRAME frame)
{
	exception("Copressor segment overrun",SIGFPE,&frame);
}


// 無効TSS。
void excInvalidTss(INTERRUPT_FRAME frame)
{
	exception("Invalid TSS",SIGSEGV,&frame);
}


// セグメント不在。
void excSegmentNotPresent(INTERRUPT_FRAME frame)
{
	exception("Segment not present",SIGBUS,&frame);
}


// スタックセグメントフォルト。
void excStackException(INTERRUPT_FRAME frame)
{
	exception("Stack exception",SIGBUS,&frame);
}


// 一般保護。
void excGeneralProtection(INTERRUPT_FRAME frame)
{
	exception("General protection",SIGSEGV,&frame);
}


// FPU浮動小数点エラー。
void excFpuError(INTERRUPT_FRAME frame)
{
	exception("FPU error",SIGFPE,&frame);
}


// アライメントチェック。
void excAlignmentCheck(INTERRUPT_FRAME frame)
{
	exception("Alignment check",SIGBUS,&frame);
}


// マシンチェック。
void excMachineCheck(INTERRUPT_FRAME frame)
{
	/* 未実装 */
	tmp_except("Machine check", &frame);
}


// SIMD浮動小数点例外。
void excSimdFpuError(INTERRUPT_FRAME frame)
{
	exception("SIMD FPU error",SIGFPE,&frame);
}


// 予約済例外
void excReserve(INTERRUPT_FRAME frame)
{
	tmp_except("15", &frame);
}


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


//================================== PUBLIC =============================================


void init_except()
{
	set_idt(0,except0,IDT_TRAP);
	set_idt(1,except1,IDT_INTR);
	set_idt(2,except2,IDT_TRAP);
	set_idt(3,except3,IDT_INTR);
	set_idt(4,except4,IDT_TRAP);
	set_idt(5,except5,IDT_TRAP);
	set_idt(6,except6,IDT_TRAP);
	set_idt(7,except7,IDT_TRAP);
	set_idt(8,except8,IDT_TRAP);
	set_idt(9,except9,IDT_TRAP);
	set_idt(10,except10,IDT_TRAP);
	set_idt(11,except11,IDT_TRAP);
	set_idt(12,except12,IDT_TRAP);
	set_idt(13,except13,IDT_TRAP);
	set_idt(14,except14,IDT_INTR);
	set_idt(15,except15,IDT_TRAP);
	set_idt(16,except16,IDT_TRAP);
	set_idt(17,except17,IDT_TRAP);
	set_idt(18,except18,IDT_TRAP);
}
