/*
    JPC: An x86 PC Hardware Emulator for a pure Java Virtual Machine
    Release Version 2.4

    A project from the Physics Dept, The University of Oxford

    Copyright (C) 2007-2010 The University of Oxford

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 2 as published by
    the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
    Details (including contact information) can be found at: 

    jpc.sourceforge.net
    or the developer website
    sourceforge.net/projects/jpc/

    Conceived and Developed by:
    Rhys Newman, Ian Preston, Chris Dennis

    End of licence header
*/

package org.jpc.emulator.pci.peripheral;

import org.jpc.emulator.pci.*;
import org.jpc.emulator.motherboard.IOPortHandler;
import org.jpc.emulator.AbstractHardwareComponent;

import java.io.*;
import java.net.*;
import java.util.Random;
import java.util.logging.*;
import org.jpc.support.*;

/** Realtek 8029 (AS) Emulation
 * 
 * based on the Bochs ne2000 emulation
 * @author Chris Dennis
 * @author Ian Preston
 */
public class EthernetCard extends AbstractPCIDevice
{

    private static final Logger LOGGING = Logger.getLogger(EthernetCard.class.getName());
    private static final int IRQ = 9;
    //Static Device Constants
    private static final int MAX_ETH_FRAME_SIZE = 1514;
    private static final int E8390_CMD = 0x00; // The command register (for all pages) */
    /* Page 0 register offsets. */
    private static final int EN0_CLDALO = 0x01; // Low byte of current local dma addr  RD */
    private static final int EN0_STARTPG = 0x01; // Starting page of ring bfr WR */
    private static final int EN0_CLDAHI = 0x02; // High byte of current local dma addr  RD */
    private static final int EN0_STOPPG = 0x02; // Ending page +1 of ring bfr WR */
    private static final int EN0_BOUNDARY = 0x03; // Boundary page of ring bfr RD WR */
    private static final int EN0_TSR = 0x04; // Transmit status reg RD */
    private static final int EN0_TPSR = 0x04; // Transmit starting page WR */
    private static final int EN0_NCR = 0x05; // Number of collision reg RD */
    private static final int EN0_TCNTLO = 0x05; // Low  byte of tx byte count WR */
    private static final int EN0_FIFO = 0x06; // FIFO RD */
    private static final int EN0_TCNTHI = 0x06; // High byte of tx byte count WR */
    private static final int EN0_ISR = 0x07; // Interrupt status reg RD WR */
    private static final int EN0_CRDALO = 0x08; // low byte of current remote dma address RD */
    private static final int EN0_RSARLO = 0x08; // Remote start address reg 0 */
    private static final int EN0_CRDAHI = 0x09; // high byte, current remote dma address RD */
    private static final int EN0_RSARHI = 0x09; // Remote start address reg 1 */
    private static final int EN0_ID0 = 0x0a; //RTL 8029 ID0
    private static final int EN0_ID1 = 0x0b; //RTL 8029 ID1
    private static final int EN0_RCNTLO = 0x0a; // Remote byte count reg WR */
    private static final int EN0_RCNTHI = 0x0b; // Remote byte count reg WR */
    private static final int EN0_RSR = 0x0c; // rx status reg RD */
    private static final int EN0_RXCR = 0x0c; // RX configuration reg WR */
    private static final int EN0_TXCR = 0x0d; // TX configuration reg WR */
    private static final int EN0_COUNTER0 = 0x0d; // Rcv alignment error counter RD */
    private static final int EN0_DCFG = 0x0e; // Data configuration reg WR */
    private static final int EN0_COUNTER1 = 0x0e; // Rcv CRC error counter RD */
    private static final int EN0_IMR = 0x0f; // Interrupt mask reg WR */
    private static final int EN0_COUNTER2 = 0x0f; // Rcv missed frame error counter RD */
    private static final int EN1_PHYS = 0x11;
    private static final int EN1_CURPAG = 0x17;
    private static final int EN1_MULT = 0x18;
    /*  Register accessed at EN_CMD, the 8390 base addr.  */
    private static final byte E8390_STOP = (byte) 0x01; // Stop and reset the chip */
    private static final byte E8390_START = (byte) 0x02; // Start the chip, clear reset */
    private static final byte E8390_TRANS = (byte) 0x04; // Transmit a frame */
    private static final byte E8390_RREAD = (byte) 0x08; // Remote read */
    private static final byte E8390_RWRITE = (byte) 0x10; // Remote write  */
    private static final byte E8390_NODMA = (byte) 0x20; // Remote DMA */
    private static final byte E8390_PAGE0 = (byte) 0x00; // Select page chip registers */
    private static final byte E8390_PAGE1 = (byte) 0x40; // using the two high-order bits */
    private static final byte E8390_PAGE2 = (byte) 0x80; // Page 3 is invalid. */
    /* Bits in EN0_ISR - Interrupt status register */
    private static final byte ENISR_RX = (byte) 0x01; // Receiver, no error */
    private static final byte ENISR_TX = (byte) 0x02; // Transmitter, no error */
    private static final byte ENISR_RX_ERR = (byte) 0x04; // Receiver, with error */
    private static final byte ENISR_TX_ERR = (byte) 0x08; // Transmitter, with error */
    private static final byte ENISR_OVER = (byte) 0x10; // Receiver overwrote the ring */
    private static final byte ENISR_COUNTERS = (byte) 0x20; // Counters need emptying */
    private static final byte ENISR_RDC = (byte) 0x40; // remote dma complete */
    private static final byte ENISR_RESET = (byte) 0x80; // Reset completed */
    private static final byte ENISR_ALL = (byte) 0x3f; // Interrupts we will enable */
    /* Bits in received packet status byte and EN0_RSR*/
    private static final byte ENRSR_RXOK = (byte) 0x01; // Received a good packet */
    private static final byte ENRSR_CRC = (byte) 0x02; // CRC error */
    private static final byte ENRSR_FAE = (byte) 0x04; // frame alignment error */
    private static final byte ENRSR_FO = (byte) 0x08; // FIFO overrun */
    private static final byte ENRSR_MPA = (byte) 0x10; // missed pkt */
    private static final byte ENRSR_PHY = (byte) 0x20; // physical/multicast address */
    private static final byte ENRSR_DIS = (byte) 0x40; // receiver disable. set in monitor mode */
    private static final byte ENRSR_DEF = (byte) 0x80; // deferring */
    /* Transmitted packet status, EN0_TSR. */
    private static final byte ENTSR_PTX = (byte) 0x01; // Packet transmitted without error */
    private static final byte ENTSR_ND = (byte) 0x02; // The transmit wasn't deferred. */
    private static final byte ENTSR_COL = (byte) 0x04; // The transmit collided at least once. */
    private static final byte ENTSR_ABT = (byte) 0x08; // The transmit collided 16 times, and was deferred. */
    private static final byte ENTSR_CRS = (byte) 0x10; // The carrier sense was lost. */
    private static final byte ENTSR_FU = (byte) 0x20; // A "FIFO underrun" occurred during transmit. */
    private static final byte ENTSR_CDH = (byte) 0x40; // The collision detect "heartbeat" signal was lost. */
    private static final byte ENTSR_OWC = (byte) 0x80; // There was an out-of-window collision. */
    private static final int NE2000_PMEM_SIZE = (32 * 1024);
    private static final int NE2000_PMEM_START = (16 * 1024);
    private static final int NE2000_PMEM_END = (NE2000_PMEM_SIZE + NE2000_PMEM_START);
    private static final int NE2000_MEM_SIZE = NE2000_PMEM_END;

    //Instance (State) Properties
    private byte command;
    private int start;
    private int stop;
    private byte boundary;
    private byte tsr;
    private byte tpsr;
    private byte txcr;
    private short tcnt;
    private short rcnt;
    private int rsar;
    private volatile int rxcr;
    private byte rsr;
    private volatile byte isr;
    private byte dcfg;
    private volatile byte imr;
    private byte phys[]; /* mac address */

    private byte curpag;
    private byte mult[]; /* multicast mask array */
    //public volatile int ethPacketsWaiting = 0;

    EthernetOutput outputDevice;
    private byte[] memory;
    private EthernetIORegion ioRegion;

    public EthernetCard()
    {
        this(null);
    }

    public EthernetCard(EthernetOutput output)
    {
        setIRQIndex(IRQ);

        putConfigWord(PCI_CONFIG_VENDOR_ID, (short) 0x10ec); // Realtek
        putConfigWord(PCI_CONFIG_DEVICE_ID, (short) 0x8029); // 8029
        putConfigWord(PCI_CONFIG_CLASS_DEVICE, (short) 0x0200); // ethernet network controller
        putConfigByte(PCI_CONFIG_HEADER, (byte) 0x00); // header_type
        putConfigByte(PCI_CONFIG_INTERRUPT_PIN, (byte) 0x01); // interrupt pin 0

        ioRegion = new EthernetIORegion();
        outputDevice = output;
        if (outputDevice == null)
            outputDevice = new EthernetProxy();
        memory = new byte[NE2000_MEM_SIZE];
        phys = new byte[6];
        mult = new byte[8];
        //generate random MAC address
        Random random = new Random();
        random.nextBytes(phys);
        phys[0] = (byte) 0x4a;
        phys[1] = (byte) 0x50;
        phys[2] = (byte) 0x43;
        System.arraycopy(phys, 0, memory, 0, phys.length);

        internalReset();
    }

    public void saveState(DataOutput output) throws IOException
    {
        output.writeByte(command);
        output.writeInt(start);
        output.writeInt(stop);
        output.writeByte(boundary);
        output.writeByte(tsr);
        output.writeByte(tpsr);
        output.writeShort(tcnt);
        output.writeShort(rcnt);
        output.writeInt(rsar);
        output.writeByte(rsr);
        output.writeByte(isr);
        output.writeByte(dcfg);
        output.writeByte(imr);
        output.writeInt(phys.length);
        output.write(phys);
        output.writeByte(curpag);
        output.writeInt(mult.length);
        output.write(mult);
        output.writeInt(memory.length);
        output.write(memory);
        ioRegion.saveState(output);
    //dump output device
    //let's ignore it for now
    }

    public void loadState(DataInput input) throws IOException
    {
        command = input.readByte();
        start = input.readInt();
        stop = input.readInt();
        boundary = input.readByte();
        tsr = input.readByte();
        tpsr = input.readByte();
        tcnt = input.readShort();
        rcnt = input.readShort();
        rsar = input.readInt();
        rsr = input.readByte();
        isr = input.readByte();
        dcfg = input.readByte();
        imr = input.readByte();
        int len = input.readInt();
        phys = new byte[len];
        input.readFully(phys, 0, len);
        curpag = input.readByte();
        len = input.readInt();
        mult = new byte[len];
        input.readFully(mult, 0, len);
        len = input.readInt();
        memory = new byte[len];
        input.readFully(memory, 0, len);
        ioRegion.loadState(input);
    //load output device
    //apparently this is another whole kettle of fish... so let's ignore it
    }

    public void checkForPackets() {
        receivePacket(outputDevice.getPacket());
    }

    public void setOutputDevice(EthernetOutput out) {
        this.outputDevice = out;
    }

    public void loadIOPorts(IOPortHandler ioportHandler, DataInput input) throws IOException
    {
        loadState(input);
        ioportHandler.registerIOPortCapable(ioRegion);
    }

    public void reset()
    {
        putConfigWord(PCI_CONFIG_VENDOR_ID, (short) 0x10ec); // Realtek
        putConfigWord(PCI_CONFIG_DEVICE_ID, (short) 0x8029); // 8029
        putConfigWord(PCI_CONFIG_CLASS_DEVICE, (short) 0x0200); // ethernet network controller
        putConfigByte(PCI_CONFIG_HEADER, (byte) 0x00); // header_type
        putConfigByte(PCI_CONFIG_INTERRUPT_PIN, (byte) 0x01); // interrupt pin 0

        memory = new byte[NE2000_MEM_SIZE];
//        phys = new byte[6];
//        mult = new byte[8];

        internalReset();

        super.reset();
    }

    private void internalReset()
    {
        isr = ENISR_RESET;
        memory[0x0e] = (byte) 0x57;
        memory[0x0f] = (byte) 0x57;
        System.arraycopy(phys, 0, memory, 0, phys.length);

        for (int i = 15; i >= 0; i--)
        {
            memory[2 * i] = memory[i];
            memory[2 * i + 1] = memory[i];
        }
    }

    private void updateIRQ()
    {
        int interruptService = isr & imr;
        if (interruptService != 0) {
            this.getIRQBouncer().setIRQ(this, IRQ, 1);
        }
        else
            this.getIRQBouncer().setIRQ(this, IRQ, 0);
    }

    private int canReceive()
    {
        if (command == E8390_STOP)
            return 1;
        return 0;
    }

    //PCIDevice Methods
    //IOPort Registration Aids
    public IORegion[] getIORegions()
    {
        return new IORegion[]
                {
                    ioRegion
                };
    }

    public IORegion getIORegion(int index)
    {
        if (index == 0)
            return ioRegion;
        else
            return null;
    }

    class EthernetIORegion extends AbstractHardwareComponent implements IOPortIORegion
    {

        private int address;

        public EthernetIORegion()
        {
            address = -1;
        }

        public void saveState(DataOutput output) throws IOException
        {
            output.writeInt(address);
        }

        public void loadState(DataInput input) throws IOException
        {
            address = input.readInt();
        }

        //IORegion Methods
        public int getAddress()
        {
            return address;
        }

        public long getSize()
        {
            return 0x100;
        }

        public int getType()
        {
            return PCI_ADDRESS_SPACE_IO;
        }

        public int getRegionNumber()
        {
            return 0;
        }

        public void setAddress(int address)
        {
            this.address = address;
            LOGGING.log(Level.FINE, "Ethernet IO address is "+Integer.toHexString(address));
        }

        //IODevice Methods
        public void ioPortWrite8(int address, int data)
        {
            switch (address - this.getAddress())
            {
                case 0x00:
                case 0x01:
                case 0x02:
                case 0x03:
                case 0x04:
                case 0x05:
                case 0x06:
                case 0x07:
                case 0x08:
                case 0x09:
                case 0x0a:
                case 0x0b:
                case 0x0c:
                case 0x0d:
                case 0x0e:
                case 0x0f:
                    EthernetCard.this.ioPortWrite(address, (byte) data);
                    break;
                case 0x10:
                    // May do a 16 bit write, so must only narrow to short
                    EthernetCard.this.asicIOPortWriteByte(address, (short) data);
                    break;
                case 0x1f:
                    //this.resetIOPortWrite(address); //end of reset pulse
                    break;
                default: //this is invalid, but happens under win 95 device detection
                    break;
            }
        }

        public void ioPortWrite16(int address, int data)
        {
            switch (address - this.getAddress())
            {
                case 0x10:
                case 0x11:
                    EthernetCard.this.asicIOPortWriteWord(address, (short) data);
                    break;
                default:
                    // should do two byte access
                    break;
            }
        }

        public void ioPortWrite32(int address, int data)
        {
            switch (address - this.getAddress())
            {
                case 0x10:
                case 0x11:
                case 0x12:
                case 0x13:
                    EthernetCard.this.asicIOPortWriteLong(address, data);
                    break;
                default:
                    break;
            }
        }

        public int ioPortRead8(int address)
        {
            switch (address - this.getAddress())
            {
                case 0x00:
                case 0x01:
                case 0x02:
                case 0x03:
                case 0x04:
                case 0x05:
                case 0x06:
                case 0x07:
                case 0x08:
                case 0x09:
                case 0x0a:
                case 0x0b:
                case 0x0c:
                case 0x0d:
                case 0x0e:
                case 0x0f:
                    return EthernetCard.this.ioPortRead(address);
                case 0x10:
                    return 0xffff & EthernetCard.this.asicIOPortReadByte(address);
                case 0x1f:
                    return EthernetCard.this.resetIOPortRead(address);
                default:
                    return (byte) 0xff;
            }
        }

        public int ioPortRead16(int address)
        {
            switch (address - this.getAddress())
            {
                case 0x10:
                case 0x11:
                    return EthernetCard.this.asicIOPortReadWord(address);
                default:
                    return (short) 0xffff; //should do two byte access
            }
        }

        public int ioPortRead32(int address)
        {
            switch (address - this.getAddress())
            {
                case 0x10:
                case 0x11:
                case 0x12:
                case 0x13:
                    return EthernetCard.this.asicIOPortReadLong(address);
                default:
                    return 0xffffffff;
            }
        }

        public int[] ioPortsRequested()
        {
            int addr = this.getAddress();
            int[] temp = new int[32];
            for (int i = 0; i < 32; i++)
                temp[i] = addr + i;
            return temp;
        }
    }

    private void ioPortWrite(int address, byte data)
    {
        address &= 0xf;
        if (address == E8390_CMD)
        {
            /* control register */
            if ((data & 0x38) == 0) {
                System.out.println("Invalid DMA command");
                data |= 0x20;
            }

            //test for s/w reset
            if ((data & 0x1) != 0) {
                isr |= ENISR_RESET;
                command |= E8390_STOP;
            } else {
                command &= ~E8390_STOP;
            }

            //update remote DMA command
            command = (byte) ((data & 0x38) | (command & ~0x38));
            if (((command & E8390_START) == 0) && (0 != (data & E8390_START)))
            {
                isr = (byte) (isr & ~ENISR_RESET);
            }
            //set start, and page select
            command = (byte) ((command & ~0xc2) | (data & 0xc2));
            
            //check for send packet command
            if ((command & 0x38) == 0x18) {
                //setup dma read from receive ring
                rsar = ((short) boundary) << 8;
                rcnt = (short) (2);
                System.out.println("After send packet command, setting rcnt to " + 2);
            }

            //check for start tx
            if ((0 != (data & E8390_TRANS)) && ((txcr & 0x6) != 0)) {
                int loop_control = (txcr & 0x6);
                if (loop_control != 2) {
                    System.out.println("ETH: Loop mode " + (loop_control >> 1) + " not supported.");
                } else {
                    byte[] packet = new byte[tcnt];
                    System.arraycopy(memory, (tpsr & 0xff ) << 8, packet, 0, tcnt);
                    receivePacket(packet);
                }
            } else if (0 != (data & E8390_TRANS)) {
                if ((0 != (command & E8390_STOP)) || ((0 == (command & E8390_START)) && (!initialised()))) {
                    if (tcnt == 0) {
                        return;
                    }
                    throw new IllegalStateException("ETH0: command write, tx start, device in reset");
                }
                if (tcnt == 0) {
                    throw new IllegalStateException("ETH0: command - tx start, tx bytes == 0");
                }
                //now send the packet
                command |= 0x4;
                int index = ((tpsr & 0xFF) << 8);
                outputDevice.sendPacket(memory, index, tcnt);
                /* signal end of transfer */
                tsr = ENTSR_PTX;
                isr = (byte) (isr | ENISR_TX);
                this.updateIRQ();

                //linux probes for an interrupt by setting up a remote dma read of 0 bytes
                //with remote dma completion interrupts enabled
                if ((rcnt == 0) && (0 != (command & E8390_START)) && ((command & 0x38) == 0x8)) {
                    isr = (byte) (isr |ENISR_RDC);
                    this.updateIRQ();
                }
            }
//                /* test specific case: zero length transfer */
//                if ((0 != (data & (E8390_RREAD | E8390_RWRITE))) && (rcnt == 0))
//                { // check operators
//                    isr = (byte) (isr | ENISR_RDC);
//                    this.updateIRQ();
//                }
//                if (0 != (data & E8390_TRANS))
//                {
//                    int index = ((tpsr & 0xFF) << 8);
//                    outputDevice.sendPacket(memory, index, tcnt);
//                    /* signal end of transfer */
//                    tsr = ENTSR_PTX;
//                    isr = (byte) (isr | ENISR_TX);
//                    this.updateIRQ();
//                }
        }
        else
        {
            int page = command >> 6;
            int offset = address | (page << 4);
            switch (offset)
            {
                case EN0_STARTPG:
                    start = data & 0xFF;
                    break;
                case EN0_STOPPG:
                    stop = data & 0xFF;
                    break;
                case EN0_BOUNDARY:
                    boundary = data;
                    break;
                case EN0_TPSR:
                    tpsr = data;
                    break;
                case EN0_TCNTLO:
                    tcnt = (short) ((tcnt & 0xff00) | data);
                    break;
                case EN0_TCNTHI:
                    tcnt = (short) ((tcnt & 0x00ff) | (((short) data) << 8));
                    break;
                case EN0_ISR:
                    isr = (byte) (isr & ~(data & 0x7f));
                    this.updateIRQ();
                    break;
                case EN0_RSARLO:
                    rsar = ((rsar & 0xff00) | data);
                    break;
                case EN0_RSARHI:
                    rsar = ((rsar & 0x00ff) | (data << 8));
                    break;
                case EN0_RCNTLO:
                    rcnt = (short) ((rcnt & 0xff00) | data);
                    break;
                case EN0_RCNTHI:
                    rcnt = (short) ((rcnt & 0x00ff) | (((short) data) << 8));
                    break;
                case EN0_RXCR:
                    if ((data & 0xc0) != 0)
                        System.out.println("ETH: Reserved bits of rxcr set");
                    rxcr = data;
                    break;
                case EN0_TXCR:
                    if ((data & 0xe0) != 0)
                        System.out.println("ETH: Reserved bits of txcr set");
                    //test loop mode (not supported)
                    if ((data & 0x6) != 0) {
                        System.out.println("ETH: Loop mode " + ((data & 0x6) >> 1) + " not supported.");
                        txcr |= (data & 0x6);
                    } else {
                        txcr &= ~0x6;
                    }
                    //stop CRC
                    if ((data & 0x1) != 0)
                        throw new IllegalStateException("ETH: TCR write - CRC not supported");
                    //stop auto-transmit disable
                    if ((data & 0x8) != 0)
                        throw new IllegalStateException("ETH: TCR write - auto transmit disable not supported");
                    // should set txcr bit 4 here, but we don't use it
                    break;
                case EN0_DCFG:
                    dcfg = data;
                    break;
                case EN0_IMR:
                    imr = data;
                    this.updateIRQ();
                    break;
                case EN1_PHYS:
                case EN1_PHYS + 1:
                case EN1_PHYS + 2:
                case EN1_PHYS + 3:
                case EN1_PHYS + 4:
                case EN1_PHYS + 5:
                    phys[offset - EN1_PHYS] = data;
                    if (offset == EN1_PHYS + 5)
                        System.out.println("ETH: MAC address set to: " + Integer.toHexString(phys[0] & 0xFF)
                                + Integer.toHexString(phys[1] & 0xFF)
                                + Integer.toHexString(phys[2] & 0xFF)
                                + Integer.toHexString(phys[3] & 0xFF)
                                + Integer.toHexString(phys[4] & 0xFF)
                                + Integer.toHexString(phys[5] & 0xFF));
                    break;
                case EN1_CURPAG:
                    curpag = data;
                    break;
                case EN1_MULT:
                case EN1_MULT + 1:
                case EN1_MULT + 2:
                case EN1_MULT + 3:
                case EN1_MULT + 4:
                case EN1_MULT + 5:
                case EN1_MULT + 6:
                case EN1_MULT + 7:
                    mult[offset - EN1_MULT] = data;
                    break;
                default:
                    throw new IllegalStateException("ETH: invalid write address: " + Integer.toHexString(address) + "page: " + page);
            }
        }
    }

    private void asicIOPortWriteByte(int address, short data)
    {
        if (rcnt == 0)
            return;
        if (0 != (dcfg & 0x01))
        {
            /* 16 bit access */
            this.memoryWriteWord(rsar, data);
            this.dmaUpdate(2);
        }
        else
        {
            /* 8 bit access */
            this.memoryWriteByte(rsar, (byte) data);
            this.dmaUpdate(1);
        }
    }

    private void asicIOPortWriteWord(int address, short data)
    {
        if (rcnt == 0)
            return;
        if (0 != (dcfg & 0x01))
        {
            /* 16 bit access */
            this.memoryWriteWord(rsar, data);
            this.dmaUpdate(2);
        }
        else
        {
            /* 8 bit access */
            this.memoryWriteByte(rsar, (byte) data);
            this.dmaUpdate(1);
        }
    }

    private void asicIOPortWriteLong(int address, int data)
    {
        if (rcnt == 0)
            return;
        this.memoryWriteLong(rsar, data);
        this.dmaUpdate(4);
    }

    private byte ioPortRead(int address)
    {
        address &= 0xf;
        if (address == E8390_CMD)
            return command;
        int page = command >> 6;
        int offset = address | (page << 4);
        switch (offset)
        {
            case EN0_BOUNDARY:
                return boundary;
            case EN0_TSR:
                return tsr;
            case EN0_ISR:
                return isr;
            case EN0_RSARLO:
                return (byte) (rsar & 0x00ff);
            case EN0_RSARHI:
                return (byte) (rsar >> 8);
            case EN0_ID0:
                if (initialised())
                    return (byte) 0x50;
                else
                    return (byte) 0xFF;
            case EN0_ID1:
                if (initialised())
                    return (byte) 0x43;
                else
                    return (byte) 0xFF;
            case EN0_RSR:
                return rsr;
            case EN1_PHYS:
            case EN1_PHYS + 1:
            case EN1_PHYS + 2:
            case EN1_PHYS + 3:
            case EN1_PHYS + 4:
            case EN1_PHYS + 5:
                return phys[offset - EN1_PHYS];
            case EN1_CURPAG:
                return curpag;
            case EN1_MULT:
            case EN1_MULT + 1:
            case EN1_MULT + 2:
            case EN1_MULT + 3:
            case EN1_MULT + 4:
            case EN1_MULT + 5:
            case EN1_MULT + 6:
            case EN1_MULT + 7:
                return mult[offset - EN1_MULT];
            default:
                return 0x00;
        }
    }

    // this is the high 16 bytes of IO space - the low 16 are for the DS8390
    private short asicIOPortReadByte(int address)
    {
        short ret;
        if (0 != (dcfg & 0x01))
        {
            /* 16 bit access */
            ret = this.memoryReadWord(rsar);
            this.dmaUpdate(2);
        }
        else
        {
            /* 8 bit access */
            ret = (short) this.memoryReadByte(rsar);
            ret &= 0xff;
            this.dmaUpdate(1);
        }
        return ret;
    }

    private short asicIOPortReadWord(int address)
    {
        short ret;
        if (0 != (dcfg & 0x01))
        {
            /* 16 bit access */
            ret = this.memoryReadWord(rsar);
            this.dmaUpdate(2);
        }
        else
        {
            /* 8 bit access */
            ret = (short) this.memoryReadByte(rsar);
            ret &= 0xff;
            this.dmaUpdate(1);
        }
        return ret;
    }

    private int asicIOPortReadLong(int address)
    {
        int ret = this.memoryReadLong(rsar);
        this.dmaUpdate(4);
        return ret;
    }

    private byte resetIOPortRead(int address)
    {
        this.internalReset();
        return 0x00;
    }

    private void dmaUpdate(int length)
    {
        rsar += length;
        if (rsar == stop)
            rsar = start;

        if (rcnt <= length)
        {
            rcnt = (short) 0;
            /* signal end of transfer */
            isr = (byte) (isr | ENISR_RDC);
            this.updateIRQ();
        }
        else
            rcnt = (short) (rcnt - length);
    }

    private void memoryWriteByte(int address, byte data)
    {
        if (address >= NE2000_PMEM_START && address < NE2000_MEM_SIZE)
            memory[address] = data;
        else {
            System.out.println("Out of bounds ETH chip memory write: " + Integer.toHexString(address));
        }
    }

    private void memoryWriteWord(int address, short data)
    {
        address &= ~1;
        if (address >= NE2000_PMEM_START && address < NE2000_MEM_SIZE)
        {
            memory[address] = (byte) data;
            memory[address + 1] = (byte) (data >> 8);
        } else {
            System.out.println("Out of bounds ETH chip memory write: " + Integer.toHexString(address));
        }
    }

    private void memoryWriteLong(int address, int data)
    {
        address &= ~1;
        if (address >= NE2000_PMEM_START && address < NE2000_MEM_SIZE)
        {
            memory[address] = (byte) data;
            memory[address + 1] = (byte) (data >> 8);
            memory[address + 2] = (byte) (data >> 16);
            memory[address + 3] = (byte) (data >> 24);
        } else {
            System.out.println("Out of bounds ETH chip memory write: " + Integer.toHexString(address));
        }
    }

    private byte memoryReadByte(int address)
    {
        if (address < 32 || (address >= NE2000_PMEM_START && address < NE2000_MEM_SIZE)) {
            return memory[address];
        } else {
            System.out.println("Out of bounds ETH chip memory read: " + Integer.toHexString(address));
            return (byte) 0xff;
        }
    }

    private short memoryReadWord(int address)
    {
        address &= ~1;
        if (address < 32 || (address >= NE2000_PMEM_START && address < NE2000_MEM_SIZE))
        {
            short val = (short) (0xff & memory[address]);
            val |= memory[address + 1] << 8;
            return val;
        }
        else {
            System.out.println("Out of bounds ETH chip memory read: " + Integer.toHexString(address));
            return (short) 0xffff;
        }
    }

    private int memoryReadLong(int address)
    {
        address &= ~1;
        if (address < 32 || (address >= NE2000_PMEM_START && address < NE2000_MEM_SIZE))
        {
            int val = (0xff & memory[address]);
            val |= (0xff & memory[address + 1]) << 8;
            val |= (0xff & memory[address + 2]) << 16;
            val |= (0xff & memory[address + 3]) << 24;

            return val;
        }
        else {
            System.out.println("Out of bounds ETH chip memory read: " + Integer.toHexString(address));
            return 0xffffffff;
        }
    }

    public void testPacket()
    {
        imr = (byte) 0xff;
        isr = (byte) (isr | ENISR_RX);
        this.updateIRQ();
    }

    private int computeCRC(byte[] buf)
    {
        //based on FreeBSD
        int crc, carry, index = 0;
        byte b;
        int POLYNOMIAL = 0x04c11db6;

        crc = 0xFFFFFFFF;
        for (int i = 0; i < 6; i++)
        {
            b = buf[index++];
            for (int j = 0; j < 8; j++)
            {
                carry = (((crc & 0x80000000L) != 0) ? 1 : 0) ^ (b & 0x01);
                crc = crc << 1;
                b = (byte) (b >>> 1);
                if (carry > 0)
                    crc = ((crc ^ POLYNOMIAL) | carry);
            }
        }
        return crc >>> 26;
    }

    public static void printPacket(byte[] oldpacket, int offset, int length) {
        byte[] packet =new byte[length];
        System.arraycopy(oldpacket, offset, packet, 0, length);
        for (int j = 0; j< packet.length / 16; j++) {
            for (int i=0; i< 16; i++)
                System.out.print(Integer.toHexString(packet[16*j + i] & 0xFF));
            System.out.println();
        }
        int remainder = packet.length % 16;
        if (remainder != 0) {
            for (int i=packet.length-remainder; i< packet.length; i++)
                System.out.print(Integer.toHexString(packet[i] & 0xFF));
            System.out.println();
        }
    }

    public void receivePacket(byte[] packet)
    {
        if (packet == null)
            return;
        int totalLen, index, mcastIdx;

        if ((command & E8390_STOP) == 1)
        {
            System.out.print("ETH told to stop");
            return;
        }

        //check this
        if ((rxcr & 0x10) != 0)
        {
            System.out.println("Receiving packet in prom mode");
            //promiscuous: receive all
        }
        else if ((packet[0] == 0xFF) && (packet[1] == 0xFF) && (packet[2] == 0xFF) && (packet[3] == 0xFF) && (packet[4] == 0xFF) && (packet[5] == 0xFF))
        {
            //broadcast address
            if ((rxcr & 0x04) == 0)
                return;
        }
        else if ((packet[0] & 1) != 0)
        {
            //multicast
            //if ((rxcr & 0x08) == 0)
            //    return;
            //mcastIdx = computeCRC(packet);
            //if ((mult[mcastIdx >>> 3] & (1 << (mcastIdx & 7))) == 0)
            //    return;
        }
        else if ((memory[0] == packet[0]) && (memory[2] == packet[1]) && (memory[4] == packet[2]) && (memory[6] == packet[3]) && (memory[8] == packet[4]) && (memory[10] == packet[5]))
        {
            //this is us!
        } else {
            System.out.println("Weird ETH packet recieved");
            printPacket(packet, 0, packet.length);
            return;
        }

        //if buffer is too small expand it!!!!!!!!!!!!
//        System.out.println("Packet got through");
        index = (curpag & 0xFF) << 8;
        //4 bytes for header
        totalLen = packet.length + 4;
        /* address for next packet (4 bytes for CRC) */
        int pages = (totalLen + 4 + 255)/256;
        int next = curpag + pages;
//        int avail;
        //don't emulate partial receives
//        if (avail < pages)
//            return;

        if (next >= stop)
            next -= (stop - start);
        //prepare packet header
        rsr = ENRSR_RXOK; // receive status
        //check this
        if ((packet[0] & 1) != 0)
            rsr |= ENRSR_PHY;
        memory[index] = 1;// (was rsr)
        if ((packet[0] & 1) != 0)
            memory[index] |= 0x20;
        memory[index + 1] = (byte) (next);
        memory[index + 2] = (byte) totalLen;
        memory[index + 3] = (byte) (totalLen >>> 8);
        index += 4;

        //write packet data
        if ((next > curpag) || (curpag + pages == stop))
        {
            System.arraycopy(packet, 0, memory, index, packet.length);
            System.arraycopy(phys, 0, memory, index, 6);
        } else
        {
            int endSize = (stop - curpag) << 8;
            System.arraycopy(packet, 0, memory, index, endSize - 4);
            int startIndex = start * 256;
            System.arraycopy(packet, endSize -4, memory, startIndex, packet.length + 4 - (endSize - 4));
        }
        curpag = (byte) next;
        //signal that we have a packet
        isr |= ENISR_RX;
        updateIRQ();
    }

    private class DefaultOutput extends EthernetOutput
    {
        DataOutputStream dos;

        public void sendPacket(byte[] data, int offset, int length)
        {
            LOGGING.log(Level.FINE, "Sent packet on default output");
            try
            {
                if (length <= 0)
                    return;
                File file = new File("ethernetout.bin");
                FileOutputStream fos = new FileOutputStream(file);
                dos = new DataOutputStream(fos);
                dos.write(data, offset, length);
                dos.close();
            }
            catch (IOException e)
            {
                LOGGING.log(Level.INFO, "Error sending packet", e);
            }
        }

        public byte[] getPacket() {
            return null;
        }
    }
}
