
#include <string.h>

#include <arch/interrupt.h>
#include <arch/i386.h>
#include <arch/delay.h>

#include <kernel/console.h>
#include <kernel/kmalloc.h>
#include <kernel/ne2000.h>

#include <net/network.h>
#include <kernel/net/arp.h>
#include <kernel/net/eth.h>
#include <kernel/net/ip.h>


enum {
    NE_CMD    = 0x0,
    NE_PSTART = 0x1, // write
    NE_PSTOP  = 0x2, // write
    NE_BNDRY  = 0x3,

    NE_TSR    = 0x4, // read

    NE_TPSR   = 0x4, // write
    NE_TBCR0  = 0x5, // write
    NE_TBCR1  = 0x6, // write
    
    NE_ISR    = 0x7,

    NE_RSAR0  = 0x8, // write
    NE_RSAR1  = 0x9, // write
    NE_RBCR0  = 0xa, // write
    NE_RBCR1  = 0xb, // write
    NE_RCR    = 0xc, // write
    NE_TCR    = 0xd, // write
    NE_DCR    = 0xe, // write

    NE_IMR    = 0xf, // write

    NE_PAR    = 0x1, // page 1, really 0x1 - 0x6
    NE_CURR   = 0x7, // page 1
    NE_MAR    = 0x8, // page 1, really 0x8 - 0xf

    NE_DATA   = 0x10 // not a control register
};

#define PAGE_TX   0x40
#define PAGE_RX   0x50  // 4k should be enough for TX
#define PAGE_STOP 0x80  // end of memory

///////////////////////////////////////////////////////
#define FAE_TALLY               13

/* Some generic ethernet register configurations. */
#define E8390_TX_IRQ_MASK 0xa /* For register EN0_ISR */
#define E8390_RX_IRQ_MASK 0x5
#define E8390_RXCONFIG    0x4 /* EN0_RXCR: broadcasts, no multicast,errors */
#define E8390_RXOFF       0x20  /* EN0_RXCR: Accept no packets */
#define E8390_TXCONFIG    0x00  /* EN0_TXCR: Normal transmit mode */
#define E8390_TXOFF       0x02  /* EN0_TXCR: Transmitter off */

/*  Register accessed at EN_CMD, the 8390 base addr.  */
#define E8390_STOP  0x01  /* Stop and reset the chip */
#define E8390_START 0x02  /* Start the chip, clear reset */
#define E8390_TRANS 0x04  /* Transmit a frame */
#define E8390_RREAD 0x08  /* Remote read */
#define E8390_RWRITE  0x10  /* Remote write  */
#define E8390_NODMA 0x20  /* Remote DMA */
#define E8390_PAGE0 0x00  /* Select page chip registers */
#define E8390_PAGE1 0x40  /* using the two high-order bits */
#define E8390_PAGE2 0x80  /* Page 3 is invalid. */



static uint32_t nePortList[] = 
	{0x300, 0x280, 0x320, 0x340, 0x360, 0x380, 0};

static int neProbePort(uint16_t ioAddress)
{
	int reg0 = inportb(ioAddress);
	int regd;

	if(reg0 == 0xff)
		return -1;
	
	outportb(ioAddress + NE_CMD, E8390_NODMA + E8390_PAGE1 + E8390_STOP);
	regd = inportb(ioAddress + 0x0d);
	outportb(ioAddress + 0x0d, 0xff);
	outportb(ioAddress + NE_CMD, E8390_NODMA + E8390_PAGE0);
	inportb(ioAddress + FAE_TALLY);

	if(inportb(ioAddress + FAE_TALLY) != 0)
	{
		outportb(ioAddress, reg0);
		outportb(ioAddress + 0x0d, regd);
		return -1;
	}
	return 0;
}

static struct ne2000_t *neProbeISA(void)
{
	int i;
	struct ne2000_t *ne;

	for(i=0; nePortList[i] != 0; i++)
	{
		if(neProbePort(nePortList[i]) == 0)
		{
			ne = (struct ne2000_t *)kmalloc(sizeof(struct ne2000_t),GFP_KERNEL);
			if(!ne)
				return NULL;
			
			// FIXME: This IRQ is hardcoded into bochsrc right now
			//        We need to detect the IRQ somehow.
			ne->base = nePortList[i];
			return ne;
		}
	}
	return NULL;
}


void ne2k_send(struct ne2000_t* ne2000, uint8_t* p, int len) 
{

    if(len > 0xffff) kprintf("ne2k_send: over 64k packet, eh?");

    // block until can send.. this is posted by ISR so it's ok..

    // disable interrupts to be safe.. receiver runs without
    // interrupts anyway so no need for fancier locking..

	////////////////////////////////////////////////
    //unsigned istate = interrupts_suspend();

    // set length and address for Remote DMA write
    outportb(ne2000->base + NE_RSAR0, 0);
    outportb(ne2000->base + NE_RSAR1, PAGE_TX);
    outportb(ne2000->base + NE_RBCR0, len > 64 ? len & 0xff : 64);
    outportb(ne2000->base + NE_RBCR1, len >> 8);

    outportb(ne2000->base + NE_CMD, 0x12); // WRITE + START

    unsigned short * data = (unsigned short*) p;
    int i;
    for(i = 0; i + 1 < len; i += 2) {
        outportw(ne2000->base + NE_DATA, data[i/2]);
    }
    // odd byte..
    if(len & 1) {
        if(len < 64) {
            // technically buffer overflow, but known to be safe
            outportw(ne2000->base + NE_DATA, data[i/2] & 0xff);
            i += 2;
        } else {
            outportb(ne2000->base + NE_DATA, data[i/2] & 0xff);
            i ++;
        }
    }
    // do padding if necessary
    for(;i < 64; i += 2) outportw(ne2000->base + NE_DATA, 0);
    
    // wait until completion
    while(!(inportb(ne2000->base + NE_ISR) & 0x40)) ; //iodelay();

    outportb(ne2000->base + NE_ISR, 0x40);
        
    // then tell the NIC to transmit that data
    outportb(ne2000->base + NE_TBCR0, len > 64 ? len & 0xff : 64);
    outportb(ne2000->base + NE_TBCR1, len >> 8);
    outportb(ne2000->base + NE_TPSR,  PAGE_TX);
    outportb(ne2000->base + NE_CMD, 0x26); // TX + START + NO DMA

///////////////////////////////////////////
   // interrupts_restore(istate);
}

void ne2k_read(struct ne2000_t * ne2000) {
        
    while(1) {
        unsigned char ibits;
        //int docount = 0;

        // loop in case we get another interrupt while handling the previous
        while((ibits = inportb(ne2000->base + NE_ISR)) & 1) {

            // first ACK the interrupt, so we get another if we spend
            // too much time fooling around here..
            outportb(ne2000->base + NE_ISR, 0x01);

            // then fetch the current counter..
            outportb(ne2000->base + NE_CMD, 0x61); // go to page1
            unsigned char current = inportb(ne2000->base + NE_CURR);
            outportb(ne2000->base + NE_CMD, 0x21); // back to page0

            // Now read packets until we hit 'current' which we just read.
            while(ne2000->next_packet != current) {

                // Read the a few words from the beginning,
                // as we need length to know how much to read.
                outportb(ne2000->base + NE_RSAR0, 0);
                outportb(ne2000->base + NE_RSAR1, ne2000->next_packet);
                outportb(ne2000->base + NE_RBCR0, 4);
                outportb(ne2000->base + NE_RBCR1, 0);
                outportb(ne2000->base + NE_CMD, 0x0A); // READ + START

                unsigned short status = inportw(ne2000->base + NE_DATA);
                unsigned short length = inportw(ne2000->base + NE_DATA);

                unsigned short * packet = (unsigned short*) kmalloc(length-4,GFP_KERNEL);

				////////////////////////////////////////////////////
                // Check for status, and set a new read for rest of the packet
                while(!(inportb(ne2000->base + NE_ISR) & 0x40)) ; ///iodelay();

                outportb(ne2000->base + NE_ISR, 0x40);

                outportb(ne2000->base + NE_RSAR0, 4);
                outportb(ne2000->base + NE_RSAR1, ne2000->next_packet);
                outportb(ne2000->base + NE_RBCR0, (length-4) & 0xff);
                outportb(ne2000->base + NE_RBCR1, (length-4) >> 8);
                outportb(ne2000->base + NE_CMD, 0x0A); // READ + START

                int i, words = (length-4)/2;
                for(i = 0; i < words; ++i) {
                    packet[i] = inportw(ne2000->base + NE_DATA);
                }
                // special case the odd packet length (not really necessary)
                if(length & 1) {
                    packet[length-1] = inportb(ne2000->base + NE_DATA);
                }

                // Check for status again..
                while(!(inportb(ne2000->base + NE_ISR) & 0x40)) ;//////////iodelay();
                outportb(ne2000->base + NE_ISR, 0x40);

                // Set next packet, and tell NIC new boundary..
                ne2000->next_packet = status >> 8;
                outportb(ne2000->base + NE_BNDRY,
                        (ne2000->next_packet == PAGE_RX)
                        ? (PAGE_STOP - 1) : (ne2000->next_packet - 1));

                // Device must be safe (and interrupts enabled) before dispatch
                // since we might call transmit code directly from this thread.

                /////eth_receive((eth_dev*)dev, (eth_packet*)packet, length - 4);
				to_eth_layer((ethernet_t*)packet, length - 4);
                kfree(packet);
                //++docount;
            }
        }
        //printk("NE2k: received %d packets.\n", docount);

        if(ibits & 0x5) {
            kprintf("NE2k: Warning: packet reception errors (ignoring)\n");
            outportb(ne2000->base + NE_ISR, 0x05); // clear it
        }
            
        outportb(ne2000->base + NE_IMR, 0x3F); // unmask the interrupts

    }
}

void ne2k_irq_handler(irq_context_t *c) 
{

    // just check ISR if it was us
    // mask interrupts + wake device if so
    unsigned char ibits = inportb(ne2000->base + NE_ISR);
    
    // Do receptions first success(1) or failed (4)
    if(ibits & 0x05) {
        // mask the reception interrupts, NEVER mask transmits
        // also leave the errors unmasked, we'll deal with them here.
        outportb(ne2000->base + NE_IMR, 0x3A);

        // post the reception semaphore if necessary
        //while(!semaphore_trywait(&dev->wakeup));
        //semaphore_post(&dev->wakeup);
		ne2k_read(ne2000);
    }

    // If transmit completed (2) or failed (8), free transmit again.
    if(ibits & 0x0A) {
        if(inportb(ne2000->base + NE_ISR) & 8) {
            kprintf("NE2k: Warning: failed transmit with TSR=%2x\n",
                    inportb(ne2000->base + NE_TSR));
        }

        // No need to mask the transmission interrupts since
        // we send on "hope-it-goes" basis. Just clear the status.
        outportb(ne2000->base + NE_ISR, 0x0A);

        // post transmit semaphore if necessary
        //while(!semaphore_trywait(&dev->transmit));
        //semaphore_post(&dev->transmit);
    }

    if(ibits & 0x10) {
        kprintf("NE2k-ISR: Warning: receive buffer overflow\n");
        // FIXME: actually we need reset here, I believe?
        outportb(ne2000->base + NE_ISR, 0x10);
    }

    if(ibits & 0x20) {
        kprintf("NE2k-ISR: Warning: counter overflow (?!?)\n");
        // FIXME: actually we need reset here, I believe?
        outportb(ne2000->base + NE_ISR, 0x20);
    }

}


void ne2k_start(unsigned short base, int irq) {

    kprintf(" - NE2k found [io:%x, irq:%d]", base, irq);

    outportb(base+NE_CMD,    0x21); // reset

    outportb(base+NE_DCR,    0x09); // normal operation, 16-bit transfer
    outportb(base+NE_RCR,    0x20); // set to monitor to avoid receive
    outportb(base+NE_TCR,    0x02); // and loopback just in case

    outportb(base+NE_ISR,    0xFF); // clear interrupts
    outportb(base+NE_IMR,    0);    // no interrupts for now

    // Now get the MAC from the PROM first
    outportb(base+NE_RSAR0,  0);
    outportb(base+NE_RSAR1,  0);

    outportb(base+NE_RBCR0,  32); // read 32 bytes
    outportb(base+NE_RBCR1,  0);

    outportb(base+NE_CMD,    0x0a); // remote read + STOP (don't want traffic)

    unsigned short prom[16];
    int i;
    for(i = 0; i < 16; i++) {
        prom[i] = inportw(base+NE_DATA);
    }

    // Set physical address on page 1
    kprintf(" [mac");
    outportb(base+NE_CMD,    0x61);
    for(i = 0; i < 6; i++) {
        kprintf(":%2x", prom[i] & 0xff);
        outportb(base+NE_PAR+i, prom[i] & 0xff);
    }
    kprintf("]\n");
    
    // Reset current (still on page1), leave one extra page to please
    // QEMU it doesn't support "send packet" anyway, so can't use that.
    // Also set our packet pointer..
    outportb(base+NE_CURR,   PAGE_RX+1);

    // ok we should be more or less done, take the NIC into normal mode
    outportb(base+NE_CMD,    0x21);

    // must set these here, if set above, stuff goes wrong if packet
    // arrives while we're initializing..
    outportb(base+NE_PSTART, PAGE_RX);   // start
    outportb(base+NE_BNDRY,  PAGE_RX);   // same as start
    outportb(base+NE_PSTOP,  PAGE_STOP); // stop

    outportb(base+NE_RCR,    0x04); // accept broadcast
    outportb(base+NE_TCR,    0x00); // default everything
    
    ne2000 = (struct ne2000_t*) kmalloc(sizeof(struct ne2000_t*),GFP_KERNEL);
    ne2000->base      = base;
    ne2000->next_packet = PAGE_RX+1; // same as CURR above


    for(i = 0; i < 6; i++) {
        ne2000->mac_addr[i] = prom[i] & 0xff;
    }
    
    //irq_attach(irq, (irq_fn) ne2k_irq_handler, dev, IRQ_CAN_SHARE);
    install_trap_handler(NIC_IRQ,(void*)&ne2k_irq_handler);
 

    outportb(base+NE_ISR,    0xFF); // clear interrupts again (just in case)
    outportb(base+NE_IMR,    0x3F); // enable all interrupts

    // start a thread
   // thread_create((void(*)())ne2k_thread, dev);
    
    // start NIC operation
    outportb(base+NE_CMD,    0x22);
    
}

void ne2k_init() 
{

  	ne2000 = neProbeISA();
	if(ne2000 == NULL)
	{
		kprintf("Not find a ne2k network card\n");
		return ;
	}
	ne2k_start(ne2000->base,NIC_IRQ);
}
