#include "clib.h"
#include "i386.h"
#include "globals.h"
#include "sched.h"
#include "phmem.h"
#include "excpt.h"
#include "systab.h"
#include "paging.h"
#include "vm86.h"
/*
 * qui sono riportati i task che vengono attivati dalle eccezioni :
 * 01 : debug
 * 03 : debug
 * 08 : double fault
 * 0a : invalid TSS
 * 0c : stack overflow
 * 0d : general fault
 * 0e : page fault
 */



#define EXCP08 tss_excp08
#define EXCP0A tss_excp0a
#define EXCP0C tss_excp0c
#define EXCP0D tss_excp0d
#define EXCP0E tss_excp0e
#define ECODE(tss) *((word*)((tss).esp - 4))


typedef struct {
	dword	cs;
        dword	eip;
        dword	eax;
        dword	ecx;
        dword	edx;
        dword	ebx;
        dword	esp;
        dword	ebp;
        dword	esi;
        dword	edi;
        dword	ds;
        dword	es;
} exparam_t;


void trap_00(void)
{
	DEBUG_FATAL("trap 00");
}

void trap_02(void)
{
	DEBUG_FATAL("trap 02");
}

void trap_04(void)
{
	DEBUG_FATAL("trap 04");
}

void trap_05(void)
{
	DEBUG_FATAL("trap 00");
}

void trap_06(exparam_t *p) _fastcall;

void trap_06(exparam_t *p)
{
	kprintf("esp %x",p);
	kprintf("opcode at %x:%x %x\n",p->cs,p->eip,p->eax);
	DEBUG_FATAL("trap 06");
}

void trap_07(void)
{
	DEBUG_FATAL("trap 07");
}
void trap_0b(void)
{
	DEBUG_FATAL("trap 0b");
}

void trap_16(void)
{
	DEBUG_FATAL("trap 16");
}




void excp_01(void)
{
	kprintf("debug");
}

void excp_03(void)
{
	kprintf("debug");
}


void excp_08(void)
{
        task_dump(EXCP08.backlink);
	DEBUG_FATAL("Double Fault \n");
}

void excp_0a(void)
{
	int i;
        gate_t g;

        task_dump(EXCP0A.backlink);
        for (i = 0; i < 2; i++) {
        	get_idt_gate(0xf0+i, &g);
                kprintf("gate %x sel %xh\n",i,g.selector);
        }


	DEBUG_FATAL("Invalid TSS code %xh\n", ECODE(EXCP0A));
}

void excp_0c(void)
{
        task_dump(EXCP0C.backlink);
	DEBUG_FATAL("stack Overflow");
}

//
//
//
//

void excp_0d(void)
{
	tss_t* tss;

        tss = sel2tss(EXCP0D.backlink);

//        kprintf("EXCP cs:eip %xh:%xh, task %s\n",tss->cs,tss->eip,tss->name);
        {
        	static int i = 0;
                i++;
//                if (i > 16) exit(9);

        }
        if (tss->eflags & (1 shl 17)) {
             int r;
             r = handle_v86_gpfault(tss);
             if (r == 0) return;
             goto fatal;
        } else {

        }
fatal:
;
        task_dump(EXCP0D.backlink);
        kprintf("code %xh\n", ECODE(EXCP0D));
        DEBUG_FATAL("General Protection Fault");
}

///////////////////////
///////////////////////
///////////////////////

static inline
addr_t get_cr2(void)
{
	addr_t r;
        asm ("movl %%cr2, %%eax" : "=a"(r) :);
        return r;
}

void excp_0e(void)
{
	tss_t* tss = sel2tss(EXCP0E.backlink);
        pte_t* pte;
        int code = ECODE(EXCP0E);
        addr_t cr2 = (addr_t)((u32)get_cr2() & 0xFFFFF000);
        int i;
        kprintf("#PF at %xh task %s (%xh) code %xh\n", get_cr2(),
        	tss->name, EXCP0E.backlink, code);


        if (code & 1) goto dump;
        else {

                i = get_pte_addr(tss, cr2, &pte);
                if (i == EFAULT) goto dump;

                else {
                	pte->addr = (dword)alloc_free_page() shr 12;
                        ASSERT(pte->addr != 0);
                        pte->present = 1;
                }
        }
        return;
dump:
        task_dump(EXCP0E.backlink);
        kprintf("cr2 %xh\n", get_cr2());
        DEBUG_FATAL("Page Fault code %xh at %xh pte %xh *pte %xh\n",
        code, cr2, pte, *pte);
}





static
void set_tss(tss_t* tss, const char*name, void *eip, int no, tss_t *k)
{
	descriptor_t d;
        gate_t g;
        int i;
	ASSERT(tss != NULL);

        i = alloc_gdt_descriptor();
        ASSERT(i != 0);
        set_system_descriptor(&d, (dword)tss, sizeof(tss_t)-1,
                        	SDT_A386TSS + SDT_PRESENT);
        set_gdt_descriptor(i, &d);

        memset(tss, 0, sizeof(tss));
        tss->tss_sel = i shl 3;
	tss->cr3 = k->cr3;
        tss->cs = KERNEL_CS;
        tss->ss = tss->ds = tss->es = KERNEL_DS;
        tss->eip = eip;
        tss->index = -1; // indice nell'array dei task --> nessuno
        tss->status.ignore = 1;
        strncpy(tss->name, name, MAX_TASK_NAME_LENGTH);

        if (no > 0) {
        	set_gate(&g, tss->tss_sel, 0, GT_TASKGATE + GT_PRESENT);
        	set_idt_gate(no, &g);
        }

}

static
void _set_gate(int no, void *addr)
{
	gate_t g;
        set_gate(&g, KERNEL_CS, (dword)addr, GT_386TRAPGATE + GT_PRESENT);
        set_idt_gate(no, &g);
}

tss_t tss_kernel;
tss_t tss_debug;
tss_t tss_debugbp;
tss_t tss_excp08;
tss_t tss_excp0a;
tss_t tss_excp0b;
tss_t tss_excp0c;
tss_t tss_excp0d;
tss_t tss_excp0e;


/*
 * questa proc crea i task base, ovvero quello del kernel e
 * quelli per le eccezioni che hanno bisogno di un task switch.
 * per completare la proc sono necessari circa 30K se la cpu
 * ha le pagine da 4M, altrimenti un po' di + (~ 40K).
 */

void base_tasks_setup(void)
{
	phaddr_t pa;
	int r;

        extern void _excp_01(void);
        extern void _excp_03(void);
	extern void _excp_08(void);
	extern void _excp_0a(void);
	extern void _excp_0c(void);
	extern void _excp_0d(void);
       	extern void _excp_0e(void);

        extern void _trap_06(void);
        extern void _trap_07(void);

        /*
         * creo il task del kernel e quelli che servono per
         * le eccezioni.
         *
         * NB: per le eccezioni che hanno il codice di errore
         * e` necessario allocare subito lo stack, mentre per
         * le eccezioni di debug alloco + stack, ma con valloc.
         */

        set_tss(&tss_kernel, "Kernel", NULL, -1, &tss_kernel);

        /*
         * alloca directory di pagina
         */
        tss_kernel.cr3 = alloc_free_0page();
        ASSERT(tss_kernel.cr3 != 0);

        pa = get_fault_address();  // pa = &prima pagina non utilizzabile

        /* mappa memoria fisica */

        r = map_memory4M(&tss_kernel, 0, 0, ((u32)pa + 0x3fffff) shr 22, pfWritable);
        if (r == ENOSYS) {
        	// usa le pagine da 4K su 386 e 486
        	r = map_memory(&tss_kernel, 0, 0, ((u32)pa + 0x3ff) shr 12, pfWritable);
        }
        ASSERT(r > 0);

        {
        	u32 txt = (u32)&end_texts & 0x3FFFFF;

        // mappa memoria kernel
        r = map_memory(&tss_kernel, VA_KERNEL_BASE, stub_stat.kernel_base,
        	txt shr 12, pfGlobal);

        r = map_memory(&tss_kernel, VA_KERNEL_BASE+txt, stub_stat.kernel_base+txt,
        	((((u32)&end_bsss & 0x3FFFFF) - txt) shr 12), pfWritable+pfGlobal);
        }

        /* crea task ausiliari */
        set_tss(&tss_debug, "DebugFault", _excp_01, 0x01, &tss_kernel);
        set_tss(&tss_debugbp, "DebugBreak", _excp_03, 0x03, &tss_kernel);
        set_tss(&tss_excp08, "DoubleFault", _excp_08, 0x08, &tss_kernel);
        set_tss(&tss_excp0a, "InvalidTSS", _excp_0a, 0x0a, &tss_kernel);
        set_tss(&tss_excp0c, "StackOverflow", _excp_0c, 0x0c, &tss_kernel);
        set_tss(&tss_excp0d, "GeneralFault", _excp_0d, 0x0d, &tss_kernel);
        set_tss(&tss_excp0e, "PageFault", _excp_0e, 0x0e, &tss_kernel);

        /* stack per i task */
        /* FIXME: visto che 4K di stack sono troppi per alcuni task (08,0a,0c)
         *        si potrebbe allocare una unica pagina comune
         *        (1360 byte ognuno)
         */
        tss_excp08.esp = (u32)alloc_free_page() + 4096;
        tss_excp0a.esp = (u32)alloc_free_page() + 4096;
        tss_excp0c.esp = (u32)alloc_free_page() + 4096;
        tss_excp0d.esp = (u32)alloc_free_page() + 4096;
        tss_excp0e.esp = (u32)alloc_free_page() + 4096;
        tss_debug.esp = (u32)valloc(4) + 4096*4;
        tss_debugbp.esp = (u32)valloc(1) + 4096;

        /* trap gate */

        _set_gate(7, _trap_07);
}