#include "clib.h"
#include "i386.h"
#include "paging.h"
#include "vm86.h"
#include "kmalloc.h"
#include "phmem.h"



#define LINEAR(seg,ofs) (addr_t)(tss->seg*16L+((word)(dword)tss->ofs))


/*
 * questo e` il gestore dei #GPF generati dai task V86.
 * praticamente il suo scopo e` di emulare le istruzioni.
 *
 * BUG: le istruzioni non tengono conto del prefisso 66h,
 *      le istruzioni di stringa OUTS/INS non ci sono
 *      mancano le istruzioni per trappare le istruzioni quando
 *      cr4[VME] e` 0.
 */

int handle_v86_gpfault(tss_t* tss)
{
	v86_tss* v86= (v86_tss*)tss;
	u8 opcodes[4];
        dword dw;

        bool db66 = false;

	addr_t va;
        va = LINEAR(cs,eip);
        read_from_task(tss, va, opcodes, sizeof(opcodes));
//        kprintf("VME: opcode %x at %x:%x\n", *(dword*)opcodes, tss->cs,tss->eip);
        if (opcodes[0] == 0x66) {
		*(dword*)opcodes = *(dword*)((u8*)opcodes+1);
                DEBUG_FATAL("DB 66h opcode in v86 task!");
        }
        switch (opcodes[0]) {
                /* INT */
        	case 0xCD:
                        execute_v86_int(tss, opcodes[1], true);
                	return 0;

                case 0xD8 ... 0xDF:
                	kprintf("FPU instruction! at cs:ip %x:%x\n", tss->cs, tss->eip);
                        DEBUG_FATAL("FPU ...");
                	asm("clts");
                	return 0;

                /* in al,8 */
                case 0xE4:
                //	kprintf("IN AL,%x\n", opcodes[1]);
                	dw = inportb(opcodes[1]);
                	tss->eax |= dw;
                        tss->eip += 2;
                        return 0;

                /* in ax,8 */
                case 0xE5:
  	       //         kprintf("IN AX,%x\n", opcodes[1]);
                	dw = inportw(opcodes[1]);
                	tss->eax |= dw;
                        tss->eip += 2;
                        return 0;


                /* out 8,AL */
                case 0xE6:
                // 	kprintf("OUT %x,AL\n", opcodes[1]);
                	if (opcodes[1] != 0x20 && opcodes[1] != 0xA0) {
                        outportb(opcodes[1], tss->eax);
                        }
                        tss->eip += 2;

                        return 0;

                /* out 8,Ax */
                case 0xE7:
                        outportw(opcodes[1], tss->eax);
                        tss->eip += 2;
                        return 0;

                /* in al,dx */
                case 0xEC:
                	dw = inportb(tss->edx);
                	tss->eax |= dw;
                        tss->eip++;
                        return 0;

                /* in ax,dx */
                case 0xED:
                	dw = inportw(tss->edx);
                	tss->eax |= dw;
                        tss->eip++;
                        return 0;

                /* out dx,al */
                case 0xEE:
                  //      kprintf("OUT DX(%x),AL\n", (word)tss->edx);
                        outportb(tss->edx, tss->eax);
                        tss->eip++;
                        return 0;


                /* out dx,ax */
                case 0xEF:
		//	kprintf("OUT DX(%x),AL\n", (word)tss->edx);
                        outportw(tss->edx, tss->eax);
                        tss->eip++;
                        return 0;

                /* sti */
                case 0xFB:
                /* iret */
                case 0xcf:
                /* popf */
                case 0x9d:

                	if (tss->eflags & F_VIP) {
                        	int i;
                                for (i = 0; i < 0x10; i++) {
                                	if (v86->irqs & (1 shl i)) {
                                        //	kprintf("VIP IRQ %x\n",i);
                                        	if (i < 8) {
                                                        execute_v86_int(tss,8+i,false);
                                                } else {
                                                	execute_v86_int(tss, 0x68+i,false);
                                                }
                                        }
                                }
                                v86->irqs = 0;
                                tss->eflags &= ~F_VIP;
                        }
                      //  tss->eflags &= ~F_VIP;
                        if (opcodes[0] == 0xFB) tss->eip++;
                	return 0;
#if 0
                /* iret */
                case 0xcf:
                	{
                        word buf[3];

                	read_from_task(tss, LINEAR(ss,esp)-2, buf, 6);
                        kprintf("IRET: ip %xh, cs%xh, fl %xh\n",
                        	buf[0], buf[1], buf[2]);
                        if (tss->eflags & F_VIP) {
                        	kprintf("IRET col VIP\n");
                        } else kprintf("IRET senza VIP??\n");

                	return 1;
                        }
#endif
                /* lock */
                case 0xf0:
                        tss->eip++;
                        return 0;

                /* hlt */
                case 0xf4:
                	exit(0xf4);


                default :
                	kprintf("unhandled VM86 opcode %x (%x)\n",
                		opcodes[0], *(dword*)opcodes);
                return 1;
        }
        return 1;
}

/*
 * CX = # word to copy
 * ES:SI = ptr
 */
static
void emulate_int15ah87(tss_t* tss)
{
	byte buf[14];
        addr_t from;
        addr_t to;
        DEBUG_FATAL("int 15!");
        read_from_task(tss, LINEAR(es,esi) + 0x10, buf, 14);
        from = (addr_t)(buf[2] + (buf[3] shl 8)+ (buf[4] shl 16));
        to = (addr_t)(buf[10] + (buf[11] shl 8)+ (buf[12] shl 16));
        kprintf("INT15ax8701 from %x(%x) to %x(%x)\n", from, buf[5], to, buf[13]);

        /*
         *FIXME: non si dovrebbe usare memcpy!!
         *
         */

        memcpy(to, from, ((word)tss->ecx)*2);
        tss->eflags &= ~1;
        tss->eip += 2;
        tss->eax &= 0xFFFF00FF;
}

void execute_v86_int(tss_t* tss, int intno, bool inc_eip)
{
	word buf[4];

//       	kprintf("VMM86 INT %x ax %xh bx %xh cx %xh dx %xh cs %xh ip %xh\n",
//       		intno, tss->eax, tss->ebx, tss->ecx, tss->edx, tss->cs, tss->eip);

        if (intno == 0x15 && ((word)tss->eax shr 8)== 0x87) {
        	emulate_int15ah87(tss);
                return;
        }


        (word)tss->esp -= 6;
        if (inc_eip) buf[0] = ((word)(dword)tss->eip)+2;
        else buf[0] = ((word)(dword)tss->eip);
        buf[1] = (word)tss->cs;
        buf[2] = (word)tss->eflags;
        write_to_task(tss, LINEAR(ss, esp), buf, 6);
        read_from_task(tss, (addr_t)(intno*4), buf, 4);
        tss->cs = buf[1];
        (dword)tss->eip = buf[0];
        tss->eflags &= ~F_VIF;
}


void execute_vm86_irq(tss_t* tss, int irqno)
{
        v86_tss* v86 = (v86_tss*)tss;

	if (tss->eflags & F_VIF) {
        	if (irqno >= 8) irqno+=0x68;
                else irqno+=8;
//                if (irqno != 8) kprintf("VIF INT %x\n", irqno);
        	execute_v86_int(tss, irqno, false);
        } else {
  //      	if (irqno != 0) kprintf("METTO il VIP IRQ %x\n",irqno);
        	tss->eflags |= F_VIP;
                v86->irqs |= 1 shl irqno;
        }
}



v86_tss* alloc_v86_task(const char *name)
{
	v86_tss *tss = NULL;
        addr_t va;
        int r;
        pte_t*pte;

        tss = (v86_tss*)kmalloc(sizeof(v86_tss));
        if (tss == NULL) goto _ret;

        /*
         * riempio con 0xFF perche` l'ultimo byte della bitmap
         * deve essere 0xFF, e io non so come il gcc mi allinea
         * tutti i campi. inoltre assegno un valore di default
         * alle bitmap.
         */
        memset(tss, 0xFF, sizeof(v86_tss));

        tss->tss.limit = sizeof(v86_tss) - 1;

        if (alloc_tss(&tss->tss, name) == NULL) goto _ret;

        tss->tss.eflags = F_VM | F_VIF | F_IF;
        tss->tss.ioofs = (dword)&tss->iobitmap - (dword)&tss->tss;
        tss->irqs = 0;

        alloc_standard_task_pd(&tss->tss);
        if (tss->tss.cr3 == NULL) goto _ret;

	if (set_memory_limit(&tss->tss, 0, 0x110000) < 0x110000) goto _ret;
        if (alloc_vpages(&tss->tss, 0, 0x110, pfUser + pfWritable) != 0) goto _ret;

        /*
         * FIXME: c'e` da mettere a posto per bene le pagine,
         * non c'e` bisogno di copiarle tutte (video,rom &c)
         * per fare una cosa fatta bene ci vuole in copy-on-write
         */

	if (write_to_task(&tss->tss, 0, 0, 0x110000) != 0) goto _ret;
        return tss;

_ret:
;
	if (tss->tss.cr3 != NULL) free_task_pgtable(&tss->tss);
	if (tss) kfree(tss);
        WARNING("return NULL");
        return NULL;

}