/*      AMD PCNetII Driver
        Author: TomAwezome

        Driver is based on:
        -       minexew's ShrineOS PCNet.CC implementation
        -       OSDev AMD_PCNET documentation
        -       AMD PCnet(TM)-PCI datasheet
        -       any other useful sources.

        Guidelines:
        -       Magic numbers are bad. #defines are good.
        -       Understandability over LOC.
        -       Clear documentation.
*/

#define PCNET_DEVICE_ID 0x2000
#define PCNET_VENDOR_ID 0x1022

#define PCI_REG_COMMAND 0x04

#define PCNET_CMDf_IOEN 0
#define PCNET_CMDf_BMEN 2

#define PCNET_CMDF_IOEN (1 << PCNET_CMDf_IOEN)
#define PCNET_CMDF_BMEN (1 << PCNET_CMDf_BMEN)

#define PCNET_WD_RESET  0x14 // reset reg location when card is in 16-bit mode

#define PCNET_DW_RDP    0x10
#define PCNET_DW_RAP    0x14
#define PCNET_DW_RESET  0x18 // reset reg location when card is in 32-bit mode

#define PCNET_CSR_CTRLSTATUS    0
#define PCNET_CSR_INTERRUPTS    3
#define PCNET_CSR_FEATURECTRL   4
#define PCNET_CSR_LADRF0                8
#define PCNET_CSR_LADRF1                9
#define PCNET_CSR_LADRF2                10
#define PCNET_CSR_LADRF3                11
#define PCNET_CSR_PADR0                 12
#define PCNET_CSR_PADR1                 13
#define PCNET_CSR_PADR2                 14
#define PCNET_CSR_MODE                  15
#define PCNET_CSR_BADRL                 24
#define PCNET_CSR_BADRU                 25
#define PCNET_CSR_BADTL                 30
#define PCNET_CSR_BADTU                 31
#define PCNET_CSR_POLLINT               47
#define PCNET_CSR_SOFTWARESTYLE 58
#define PCNET_CSR_RXRINGLEN             76
#define PCNET_CSR_TXRINGLEN             78

#define PCNET_SWSTYLE_SELECTION 2 // (value, not bit) AMD PCNet datasheet p. 1-968

#define PCNET_SWSTYLE_SSIZE32   8 // Bit 8 of SWSTYLE

// Refer to AMD PCNet datasheet p. 1-954, 1-956, 1-957 for Interrupt Mask details.
#define PCNET_INT_BSWP          2       // Byte Swap (Big-Endian / Little-Endian)
#define PCNET_INT_IDONM         8       // Initialization Done Mask
#define PCNET_INT_TINTM         9       // Transmit Interrupt Mask
#define PCNET_INT_RINTM         10      // Receive Interrupt Mask

#define PCNET_FEATURE_APADXMT   11

#define PCNET_CTRL_INIT 0
#define PCNET_CTRL_STRT 1
#define PCNET_CTRL_STOP 2
#define PCNET_CTRL_RINT 10

#define PCNET_RX_BUFF_COUNT     32      // Linux & Shrine Driver use 32 and 8 for
#define PCNET_TX_BUFF_COUNT     8       // these, we could allow more if wanted.

#define PCNET_DESCRIPTORf_ENP   24
#define PCNET_DESCRIPTORf_STP   25
#define PCNET_DESCRIPTORf_OWN   31 // AMD PCNet datasheet p.1-992, 1-994

class CPCNet
{
        CPCIDev *pci;

        U8      mac_address[6];                 // MAC address is first 6 bytes of PCNet EEPROM (page # ? )

        I64 current_rx_de_index;        // Current Receive DE being processed.  Gets incremented, wrapped to 0 at max of PCNET_RX_BUFF_COUNT.
        I64 current_tx_de_index;        // Current Transmit DE being processed. Gets incremented, wrapped to 0 at max of PCNET_TX_BUFF_COUNT.

        U8 *rx_de_buffer;                       // Uncached-alias of pointer to the buffer of RX Descriptor Entries.
        U8 *tx_de_buffer;                       // Uncached-alias of pointer to the buffer of TX Descriptor Entries.
        U8 *rx_de_buffer_phys;          // Pointer to the buffer of RX Descriptor Entries. (Code Heap, lower 2Gb)
        U8 *tx_de_buffer_phys;          // Pointer to the buffer of TX Descriptor Entries. (Code Heap, lower 2Gb)

        U32 rx_buffer_addr;                     // Uncached-alias of address of receive buffers.
        U32 tx_buffer_addr;                     // Uncached-alias of address of transmit buffers.
        U32 rx_buffer_addr_phys;        // Physical address of actual receive buffers  (< 4 Gb)
        U32 tx_buffer_addr_phys;        // Physical address of actual transmit buffers (< 4 Gb)

} pcnet; // pcnet is the global variable we store all of this into.

class CPCNetDescriptorEntry
{/* AMD PCNet datasheet p.1-991 & p.1-994 NOTE: chart typo  on 1-994, see ONES and BCNT on 1-995.
        TX and RX DE's are the same size (16-Bytes) and structure,
        but have different registers and functions.
        The RX and TX DE buffers of the CPCNet class
        are allocated to a certain amount of these DEs. */

        U32     buffer_addr;
        U32 status1;
        U32 status2;
        U32 reserved;
};

class CPCNetBufferSetup
{
        U16 mode;
        U8      rlen;
        U8      tlen;
        U8      mac[6];
        U16 reserved;
        U8      ladr[8];
        U32 rxbuf;
        U32 txbuf;
};

CPCIDev *PCNetPCIDevFind()
{// Find and return PCNetII card as a CPCIDev pointer.

        return PCIDevFind(,, PCNET_VENDOR_ID, PCNET_DEVICE_ID);
}

U32 PCNetIOBaseGet()
{/* Return memory IO base address
        of PCNet card. Bits 0-4 are not
        for the IO base, so an AND with
        ~0x1F ignores those bits. */

        U32 io_base = pcnet.pci->base[0] & ~0x1F;
        return io_base;
}

U0 PCNetReset()
{/* Reads the 32- and 16-bit RESET registers,
        which, regardless of which mode the card is in,
        will reset it back to 16-bit mode. */

        InU32(PCNetIOBaseGet + PCNET_DW_RESET);
        InU16(PCNetIOBaseGet + PCNET_WD_RESET);
        Busy(5); // OSDev says minimum 1 uS
}

U0 PCNet32BitModeEnable()
{/* AMD PCNet datasheet p. 1-930
        Summary: A 32-bit write (while in 16-bit mode)
        to RDP  will cause 16-bit mode exit
        and immediate enter into 32-bit mode. */

        OutU32(PCNetIOBaseGet + PCNET_DW_RDP, 0);
}

U0 PCNetRAPWrite(U32 value)
{/* AMD PCNet datasheet p. 1-952
        Summary: Register Address Pointer register
        value will indicate which CSR / BCR register
        we want to access in RDP / BDP. */

        OutU32(PCNetIOBaseGet + PCNET_DW_RAP, value);
}

U0 PCNetCSRWrite(U32 csr, U32 value)
{/* AMD PCNet datasheet p. 1-952
        Summary: Control and Status Registers are
        accessed via the RDP (Register Data Port).
        Which CSR is selected is based on the value
        in the RAP. */

        PCNetRAPWrite(csr);
        OutU32(PCNetIOBaseGet + PCNET_DW_RDP, value);
}

U32 PCNetCSRRead(U32 csr)
{/* AMD PCNet datasheet p. 1-952
        Summary: Control and Status Registers are
        accessed via the RDP (Register Data Port).
        Which CSR is selected is based on the value
        in the RAP. */

        PCNetRAPWrite(csr);
        return InU32(PCNetIOBaseGet + PCNET_DW_RDP);
}

U0 PCNetSWStyleSet()
{/* AMD PCNet datasheet p. 1-968
        In CSR58 (Software Style), the 8-bit
        SWSTYLE register dictates interpretation of certain
        bits in the CSR space, and widths of descriptors and
        initialization block. In PCINet-PCI mode, CSR4 bits
        function as defined in the datasheet , and TMD1[29]
        functions as ADD_FCS. */

        U32 csr = PCNetCSRRead(PCNET_CSR_SOFTWARESTYLE);

        csr &= ~0xFF; // clears first 8 bits: SWSTYLE 8-bit register.
        csr |= PCNET_SWSTYLE_SELECTION; // set SWSTYLE to PCNet-PCI mode.
        Bts(&csr, PCNET_SWSTYLE_SSIZE32); // set SSIZE32 bit 1

        PCNetCSRWrite(PCNET_CSR_SOFTWARESTYLE, csr);
}

U0 PCNetMACGet()
{/* AMD PCNet datasheet p. 1-887, 1-931, 1-937
        MAC address stored at first 6 bytes of PCNet EEPROM.
        EEPROM addresses shadow-copied to APROM at hardware init.
        APROM accessible at first 16 bytes of PCI IO space. */

        I64 i;
        NetLog("PCNET GET MAC: Getting VM MAC.");
        for (i = 0; i < 6; i++)
        {
                pcnet.mac_address[i] = InU8(PCNetIOBaseGet + i);
                NetLog(" %02X", pcnet.mac_address[i]);
        }
}

U0 PCNetDescriptorEntryInit(CPCNetDescriptorEntry *entry, U32 buffer_address, I64 is_rx)
{
        U16 buffer_byte_count;

        entry->buffer_addr = buffer_address;

        /*      AMD PCNet datasheet p.1-991.
                BCNT is the usable buffer length, expressed as first
                12 bits of 2s-complement of desired length.
                Bits 0-11 of a DE are for the buffer byte count (BCNT),
                and bits 12-15 of a DE must be written all ones (ONES) */

        buffer_byte_count = -ETHERNET_FRAME_SIZE;               // Sets up as 2s complement of the desired length.
        buffer_byte_count &= 0x0FFF;                                    // Masks 0 over everything except bits 0-11.

        entry->status1 |= buffer_byte_count;                    // Sets BCNT reg (first 12 bits) in DE TMD1/RMD1.
        entry->status1 |= 0xF000;                                               // Sets bits 12-15 (ONES) in DE TMD1/RMD1  as all ones.

        //if this is a Receive DE, give ownership to the card so the PCNet can fill them.
        if (is_rx)
                Bts(&entry->status1, PCNET_DESCRIPTORf_OWN);
}

U0 PCNetBuffersAllocate()
{
        I64 de_index; // used in for loops for TX and RX DE access.

        /*      AMD PCNet datasheet p.1-913, p.1-990
                When SSIZE32=1, Descriptor Ring Entry Base Address
                must be on 16-byte boundary. (TDRA[3:0]=0, RDRA[3:0]=0) */

        pcnet.rx_de_buffer_phys = CAllocAligned(sizeof(CPCNetDescriptorEntry) * PCNET_RX_BUFF_COUNT,
                                                                                        16, 
                                                                                        Fs->code_heap);

        pcnet.tx_de_buffer_phys = CAllocAligned(sizeof(CPCNetDescriptorEntry) * PCNET_TX_BUFF_COUNT,
                                                                                        16,
                                                                                        Fs->code_heap);

        //Shrine does a check and returns -1 here, if the end of either buffer exceeds 0x100000000

        pcnet.rx_de_buffer = dev.uncached_alias + pcnet.rx_de_buffer_phys; // we want uncached
        pcnet.tx_de_buffer = dev.uncached_alias + pcnet.tx_de_buffer_phys; // access to these.

        pcnet.rx_buffer_addr_phys = CAlloc(ETHERNET_FRAME_SIZE * PCNET_RX_BUFF_COUNT, Fs->code_heap);

        pcnet.tx_buffer_addr_phys = CAlloc(ETHERNET_FRAME_SIZE * PCNET_TX_BUFF_COUNT, Fs->code_heap);

        //Shrine does a check and returns -1 here, if the end of either buffer exceeds 0x100000000

        pcnet.rx_buffer_addr = dev.uncached_alias + pcnet.rx_buffer_addr_phys;
        pcnet.tx_buffer_addr = dev.uncached_alias + pcnet.tx_buffer_addr_phys;

        CPCNetDescriptorEntry *entry = pcnet.rx_de_buffer;
        for (de_index = 0; de_index < PCNET_RX_BUFF_COUNT; de_index++)
        {
                PCNetDescriptorEntryInit(&entry[de_index],
                                                                 pcnet.rx_buffer_addr + de_index * ETHERNET_FRAME_SIZE,
                                                                 TRUE); // TRUE for is_rx.
        }

        entry = pcnet.tx_de_buffer;
        for (de_index = 0; de_index < PCNET_TX_BUFF_COUNT; de_index++)
        {
                PCNetDescriptorEntryInit(&entry[de_index],
                                                                 pcnet.tx_buffer_addr + de_index * ETHERNET_FRAME_SIZE,
                                                                 FALSE); // FALSE for is_rx.
        }
}

/*
U0 PCNetDirectInit()
{/* AMD PCNet datasheet p. 1-1021
        Instead of setting up initialization block,
        direct writes to the necessary CSRs can be
        used to manually initialize the PCNet card. */

        /*      AMD PCNet datasheet p.1-991
                If Logical Address Filter is set as
                all 0, all incoming logical addresses
                are rejected. Disables multicast. */
        PCNetCSRWrite(PCNET_CSR_LADRF0, 0);
        PCNetCSRWrite(PCNET_CSR_LADRF1, 0);
        PCNetCSRWrite(PCNET_CSR_LADRF2, 0);
        PCNetCSRWrite(PCNET_CSR_LADRF3, 0);

        /*      The Physical Address is the MAC.
                AMD PCNet datasheet p.1-960, 1-961
                The first 16 bits of CSRs 12-14 are
                for the Physical Address, the upper bits
                are reserved, written 0 read undefined. 

                The OR and bit-shift of 8 allows writing
                separate U8 values in the correct locations
                of the CSR. */
        NetLog("PCNetDirectInit: Write MAC to CSR: 0x%X ", pcnet.mac_address[0] | (pcnet.mac_address[1] << 8));
        PCNetCSRWrite(PCNET_CSR_PADR0, pcnet.mac_address[0] | (pcnet.mac_address[1] << 8));
        NetLog("PCNetDirectInit: Write MAC to CSR: 0x%X ", pcnet.mac_address[2] | (pcnet.mac_address[3] << 8));
        PCNetCSRWrite(PCNET_CSR_PADR1, pcnet.mac_address[2] | (pcnet.mac_address[3] << 8));
        NetLog("PCNetDirectInit: Write MAC to CSR: 0x%X ", pcnet.mac_address[4] | (pcnet.mac_address[5] << 8));
        PCNetCSRWrite(PCNET_CSR_PADR2, pcnet.mac_address[4] | (pcnet.mac_address[5] << 8));

        /*      AMD PCNet datasheet p.1-961, 1-962, 1-963
                Refer to datasheet for specifics.
                Most relevant, when setting Mode to 0,
                promiscuous mode is is disabled, TX and
                RX enabled, enable RX broadcast and unicast. */
        PCNetCSRWrite(PCNET_CSR_MODE, 0);

        /*      AMD PCNet datasheet p.1-964
                CSR 24 and 25 need to be filled
                with the lower and upper 16 bits,
                respectively, of the address of 
                the RX packet ring. Likewise for
                CSR 30 and 31 for the TX packet ring.

                0xFFFF AND on address will leave
                only lower 16 bits remaining.

                Bitshift right of 16 will replace
                first 16 bits  with upper 16 bits,
                remaining bits cleared.*/
        PCNetCSRWrite(PCNET_CSR_BADRL, pcnet.rx_buffer_addr & 0xFFFF);
        PCNetCSRWrite(PCNET_CSR_BADRU, pcnet.rx_buffer_addr >> 16);

        PCNetCSRWrite(PCNET_CSR_BADTL, pcnet.tx_buffer_addr & 0xFFFF);
        PCNetCSRWrite(PCNET_CSR_BADTU, pcnet.tx_buffer_addr >> 16);

        /*      AMD PCNet datasheet p. 1-967
                Default value at hardware init is
                all 0. Standard init block process
                sets this, but if doing directly
                it is imperative to manually set it 0. */
        PCNetCSRWrite(PCNET_CSR_POLLINT, 0);

        /*      AMD PCNet datasheet p. 1-970
                Receive and Transmit Ring Length CSRs
                bits 0-15 need to be set as the 2s complement
                of the ring length. The AND with 0xFFFF clears
                the upper Reserved bits, which are to be written
                as zeroes read undefined. */
        PCNetCSRWrite(PCNET_CSR_RXRINGLEN, -PCNET_RX_BUFF_COUNT & 0xFFFF);
        PCNetCSRWrite(PCNET_CSR_TXRINGLEN, -PCNET_TX_BUFF_COUNT & 0xFFFF);
}
*/

U8 *PCNetInitBlockSetup()
{
        U8                                      *setup   = CAlloc(sizeof(CPCNetBufferSetup), Fs->code_heap);
        CPCNetBufferSetup       *u_setup = setup + dev.uncached_alias;
        U32                                      p_setup;

        u_setup->mode           = 0;
        u_setup->rlen           = 5 << 4;
        u_setup->tlen           = 3 << 4;
        MemCopy(u_setup->mac, pcnet.mac_address, 6);
        u_setup->reserved       = 0;
        MemSet(u_setup->ladr, 0, 8);
        u_setup->rxbuf          = pcnet.rx_de_buffer_phys;
        u_setup->txbuf          = pcnet.tx_de_buffer_phys;

        p_setup = setup;
        PCNetCSRWrite(1, p_setup & 0xFFFF);
        PCNetCSRWrite(2, p_setup >> 16);
        
        return setup;
}

U0 PCNetInterruptCSRSet()
{/* AMD PCNet datasheet p.1-952, 1-953, 1-954, 1-955, 1-956, 1-957
        Refer to datasheet for specifics on the Interrupt Masks.
        Most of these, when set 0, allow interrupts to be set in CSR0.
        We set Big-Endian disabled, RX interrupts
        enabled, Init Done interrupt disabled, and TX interrupt
        disabled. */

        U32 csr = PCNetCSRRead(PCNET_CSR_INTERRUPTS);

        Btr(&csr, PCNET_INT_BSWP);
        Btr(&csr, PCNET_INT_RINTM);

        Bts(&csr, PCNET_INT_IDONM);
        Bts(&csr, PCNET_INT_TINTM);
        
        PCNetCSRWrite(PCNET_CSR_INTERRUPTS, csr);
}

U0 PCNetTXAutoPadEnable()
{/* AMD PCNet datasheet p.1-958
        Setting bit 11 (Auto Pad Transmit) allows
        shoft transmit frames to be automatically
        extended to 64 bytes. */

        U32 csr = PCNetCSRRead(PCNET_CSR_FEATURECTRL);

        Bts(&csr, PCNET_FEATURE_APADXMT);

        PCNetCSRWrite(PCNET_CSR_FEATURECTRL, csr);
}

U0 PCNetConfigModeExit()
{/* AMD PCNet datasheet p.1-954
        PCNet controller can be started
        after configuring by ensuring INIT
        and STOP are cleared and START bit
        is set, in Status and Control Register
        (CSR0). */

        U32 csr = PCNetCSRRead(PCNET_CSR_CTRLSTATUS);

        Btr(&csr, PCNET_CTRL_INIT);
        Btr(&csr, PCNET_CTRL_STOP);

        Bts(&csr, PCNET_CTRL_STRT);

        PCNetCSRWrite(PCNET_CSR_CTRLSTATUS, csr);
}

I64 PCNetDriverOwns(CPCNetDescriptorEntry* entry)
{/* Returns whether the value of the OWN bit of the
        Descriptor Entry is zero. If 0, driver owns,
        if 1, PCNet card owns it. */

        return !Bt(&entry->status1, PCNET_DESCRIPTORf_OWN);
}

I64 PCNetTransmitPacketAllocate(U8 **packet_buffer_out, I64 length)
{/* Transmits the packet at the current TX DE index. The packet_buffer_out
        is a pointer, since we modify its value, ending with returning the
        index of the DE we just processed. Length is validated to fit in BCNT.
        The increment of the current TX DE index is done by assigning it the
        value of incrementing it AND the max DE index-1. This will increment it
        as well as wrap back to 0 if we hit the max DE index. */

        U16 buffer_byte_count;
        I64 de_index = pcnet.current_tx_de_index;

        if (length > 0xFFF)
        { // Max packet length must fit into BCNT 12-bit register.
                NetErr("PCNET ALLOCATE TX PACKET: Invalid TX Packet Length");
                throw('PCNet');
        }

        CPCNetDescriptorEntry *entry = &pcnet.tx_de_buffer[de_index * sizeof(CPCNetDescriptorEntry)];

        if (!PCNetDriverOwns(entry))
        {
                NetErr("PCNET ALLOCATE TX PACKET: TX FIFO Full");
                return -1; // Positive value expected. Functions calling this must factor this in.
        }
        else
        {
                NetLog("PCNET ALLOCATE TX PACKET: Driver owns TX DE at index %d.", de_index);
        }

        Bts(&entry->status1, PCNET_DESCRIPTORf_STP);

        Bts(&entry->status1, PCNET_DESCRIPTORf_ENP);

        /*      AMD PCNet datasheet p.1-991.
                BCNT is the usable buffer length, expressed as first
                12 bits of 2s-complement of desired length.
                Bits 0-11 of a DE are for the buffer byte count (BCNT),
                and bits 12-15 of a DE must be written all ones (ONES) */
        buffer_byte_count = -length;                    // Sets up as 2s complement of the desired length.
        buffer_byte_count &= 0x0FFF;                    // Masks 0 over everything except bits 0-11.

        entry->status1 &= 0xFFFFF000;                   // Clear first 12 bits and retain other bits in DE TMD1.

        entry->status1 |= buffer_byte_count;    // Sets BCNT reg (first 12 bits) in DE TMD1.
        entry->status1 |= 0xF000;                               // Sets bits 12-15 (ONES) in DE TMD1 as all ones.

        pcnet.current_tx_de_index = (pcnet.current_tx_de_index + 1) & (PCNET_TX_BUFF_COUNT - 1);

        *packet_buffer_out = pcnet.tx_buffer_addr + de_index * ETHERNET_FRAME_SIZE;

        MemSet(*packet_buffer_out, 0, ETHERNET_FRAME_SIZE); // Clear buffer contents in advance.

        NetLog("PCNET ALLOCATE TX PACKET: de_index: %X.", de_index);
        return de_index;
}

U0 PCNetTransmitPacketFinish(I64 de_index)
{/* Release ownership of the packet to the PCNet card
        by setting the OWN bit to 1. */ 

        CPCNetDescriptorEntry *entry = &pcnet.tx_de_buffer[de_index * sizeof(CPCNetDescriptorEntry)];

        Bts(&entry->status1, PCNET_DESCRIPTORf_OWN);
        NetLog("PCNET FINISH TX PACKET: TX DE index: %X, OWN bit of entry at entry: %b.",
                                de_index, Bt(&entry->status1, PCNET_DESCRIPTORf_OWN));
}

U0 EthernetFrameFinish(I64 de_index)
{//Alias for driver Finish TX function.
        PCNetTransmitPacketFinish(de_index);
}

I64 PCNetPacketReceive(U8 **packet_buffer_out, U16 *packet_length_out)
{/* Receives the packet at the current RX DE index. Parameters
        are both pointers, since we modify the value at the packet_buffer_out,
        and at the packet_length, ending with returning the index of the DE
        we just processed.
        The MCNT is stored at the first two bytes of the RMD2. We AND with
        0xFFFF to only take in those first two bytes: that is the packet_length.
        The increment of the current RX DE index is done by assigning it the
        value of incrementing it AND the max DE index-1. This will increment it
        as well as wrap back to 0 if we hit the max DE index. */

        I64 de_index = pcnet.current_rx_de_index;
        U16 packet_length;

        NetLog("PCNET RECEIVE PACKET: Output buffer ptr, output length ptr: %X, %X ", packet_buffer_out, packet_length_out);

        CPCNetDescriptorEntry *entry = &pcnet.rx_de_buffer[de_index * sizeof(CPCNetDescriptorEntry)];

        packet_length = entry->status2 & 0xFFFF;

        NetDebug("PCNET RECEIVE PACKET: de_index = 0x%0X", de_index);
        pcnet.current_rx_de_index = (pcnet.current_rx_de_index + 1) & (PCNET_RX_BUFF_COUNT - 1);
        NetDebug("PCNET RECEIVE PACKET: de_index incremented = 0x%0X", pcnet.current_rx_de_index);

        *packet_buffer_out = pcnet.rx_buffer_addr + de_index * ETHERNET_FRAME_SIZE;
        *packet_length_out = packet_length;

        return de_index;
}

U0 PCNetReceivePacketRelease(I64 de_index)
{/* Release ownership of the packet to the PCNet card
        by setting the OWN bit to 1. */ 

        CPCNetDescriptorEntry *entry = &pcnet.rx_de_buffer[de_index * sizeof(CPCNetDescriptorEntry)];

        Bts(&entry->status1, PCNET_DESCRIPTORf_OWN);
}

interrupt U0 PCNetIRQ()
{// todo: comments explaining process...maybe reimplement interrupt handling altogether.

        U8 *packet_buffer;
        U16 packet_length;
        I64 de_index;

        U32 csr = PCNetCSRRead(PCNET_CSR_CTRLSTATUS);
//      "Interrupt Reason: %X , %b\n",csr,csr;Debug;

        CPCNetDescriptorEntry *entry = pcnet.rx_de_buffer;

        while (PCNetDriverOwns(&entry[pcnet.current_rx_de_index]))
        {
                NetLog("$BG,LTCYAN$$FG,WHITE$"
                           "==== PCNET IRQ ===="
                           "$BG$$FG$");

                NetLog("$BD,CYAN$$FD,WHITE$"
                           "PCNET IRQ: Saw owned RX DE index %d.", pcnet.current_rx_de_index);

                de_index = PCNetPacketReceive(&packet_buffer, &packet_length);

                if (de_index >= 0) // todo: necessary? check increment logic in PCNetPacketReceive.
                {
                        NetLog("PCNET IRQ: Pushing copy into Net Queue, releasing receive packet.");
                        NetQueuePush(packet_buffer, packet_length);
                        PCNetReceivePacketRelease(de_index);
                }

                Bts(&csr, PCNET_CTRL_RINT);

                PCNetCSRWrite(PCNET_CSR_CTRLSTATUS, csr);

                NetLog("PCNET IRQ: Exiting.\n"
                           "$BD,WHITE$$FD,LTGRAY$"
                           "$BG,LTCYAN$$FG,WHITE$"
                           "==================="
                           "$BG$$FG$");
        }

        *(dev.uncached_alias + LAPIC_EOI)(U32*) = 0;
}

U0 PCIInterruptsReroute(I64 base)
{ // todo: comments explaining process, maybe better var names
        I64      i;
        U8      *da = dev.uncached_alias + IOAPIC_REG;
        U32 *_d = dev.uncached_alias + IOAPIC_DATA;

        for (i = 0; i < 4; i++)
        {
                *da = IOREDTAB + i * 2 + 1;
                *_d = dev.mp_apic_ids[INT_DEST_CPU] << 24;
                *da = IOREDTAB + i * 2;
                *_d = 0x4000 + base + i;
        }
}

U0 PCNetInterruptsSetup()
{ // todo: comments explaining process
        IntEntrySet(I_PCNET0, &PCNetIRQ);
        IntEntrySet(I_PCNET1, &PCNetIRQ);
        IntEntrySet(I_PCNET2, &PCNetIRQ);
        IntEntrySet(I_PCNET3, &PCNetIRQ);
        PCIInterruptsReroute(I_PCNET0);
}

U0 PCNetInit()
{
        U8 *setup; // PCNetInitBlockSetup

        MemSet(&pcnet, 0, sizeof(CPCNet)); // pcnet global var will hold member data the driver uses often.

        pcnet.pci = PCNetPCIDevFind;
        if (!pcnet.pci)
                return; // if we don't find the card, quit.

        /*      Clear command register of PCNet
                PCI device, set IO Enable and Bus
                Master Enable bits of the register. */
        PCIWriteU16(pcnet.pci->bus,
                                pcnet.pci->dev,
                                pcnet.pci->fun,
                                PCI_REG_COMMAND,
                                PCNET_CMDF_IOEN | PCNET_CMDF_BMEN);

        PCNetReset;

        PCNet32BitModeEnable;

        U32 csr = PCNetCSRRead(PCNET_CSR_CTRLSTATUS);
        NetLog("PCNET INIT START: what is INIT ?: %d", Bt(&csr, PCNET_CTRL_INIT));
        NetLog("PCNET INIT START: what is STRT ?: %d", Bt(&csr, PCNET_CTRL_STRT));
        NetLog("PCNET INIT START: what is STOP ?: %d", Bt(&csr, PCNET_CTRL_STOP));
        NetLog("PCNET INIT START: what is RINT ?: %d", Bt(&csr, PCNET_CTRL_RINT));


        PCNetSWStyleSet;

        PCNetMACGet;
//      OSDev has code ensuring auto selected connection...

        PCNetBuffersAllocate;

//      PCNetDirectInit;
        setup = PCNetInitBlockSetup();

        PCNetInterruptCSRSet;

        PCNetTXAutoPadEnable;

        PCNetCSRWrite(0, PCNetCSRRead(0) | 1 | 1 << 6); // ?

        csr = PCNetCSRRead(PCNET_CSR_CTRLSTATUS);
        NetLog("PCNET INIT UPLOAD: what is INIT ?: %d", Bt(&csr, PCNET_CTRL_INIT));
        NetLog("PCNET INIT UPLOAD: what is STRT ?: %d", Bt(&csr, PCNET_CTRL_STRT));
        NetLog("PCNET INIT UPLOAD: what is STOP ?: %d", Bt(&csr, PCNET_CTRL_STOP));
        NetLog("PCNET INIT UPLOAD: what is RINT ?: %d", Bt(&csr, PCNET_CTRL_RINT));

        while (!(PCNetCSRRead(0) & 1 << 8)) // ?
                Yield;

        PCNetConfigModeExit;    

        Sleep(100); //? necessary?

        csr = PCNetCSRRead(PCNET_CSR_CTRLSTATUS);
        NetLog("PCNET INIT END: what is INIT ?: %d", Bt(&csr, PCNET_CTRL_INIT));
        NetLog("PCNET INIT END: what is STRT ?: %d", Bt(&csr, PCNET_CTRL_STRT));
        NetLog("PCNET INIT END: what is STOP ?: %d", Bt(&csr, PCNET_CTRL_STOP));
        NetLog("PCNET INIT END: what is RINT ?: %d", Bt(&csr, PCNET_CTRL_RINT));
        NetLog("PCNET INIT END: what is TXON ?: %d", Bt(&csr, 4));
        NetLog("PCNET INIT END: what is RXON ?: %d", Bt(&csr, 5));

        csr =  PCNetCSRRead(PCNET_CSR_POLLINT);
        NetLog("PCNET INIT END: what is POLLINT ?: %d", Bt(&csr, PCNET_CTRL_RINT));

        NetLog("PCNET INIT END: Redirecting interrupts.");
        PCNetInterruptsSetup;


        Free(setup);
}

I64 EthernetFrameAllocate(U8 **packet_buffer_out,
                                                  U8 *source_address,
                                                  U8 *destination_address,
                                                  U16 ethertype,
                                                  I64 packet_length)
{/* Allocate an Ethernet Frame for transmit. The source
        and destination addresses are copied to the Frame,
        as well as the ethertype. The packet_buffer_out
        parameter has the value at its pointer set to the
        payload of the Ethernet Frame. */

        U8 *ethernet_frame;
        I64 de_index;

        //need to see if 3 years later VirtualBox supports APAD_XMT!
        if (packet_length < ETHERNET_MIN_FRAME_SIZE)
        {
                NetWarn("ETHERNET FRAME ALLOCATE: PCNET APAD XMT TRUNCATE ? ...");
                packet_length = ETHERNET_MIN_FRAME_SIZE;
        }

        de_index = PCNetTransmitPacketAllocate(&ethernet_frame, ETHERNET_MAC_HEADER_LENGTH + packet_length);

        if (de_index < 0)
        {
                NetErr("ETHERNET FRAME ALLOCATE: Failure");
                return -1; // Positive value expected. Functions calling this must factor this in.
        }

        MemCopy(ethernet_frame,                                          destination_address, MAC_ADDRESS_LENGTH);
        MemCopy(ethernet_frame + MAC_ADDRESS_LENGTH, source_address,      MAC_ADDRESS_LENGTH);

        ethernet_frame[ETHERNET_ETHERTYPE_OFFSET]               = ethertype >> 8;
        ethernet_frame[ETHERNET_ETHERTYPE_OFFSET + 1]   = ethertype & 0xFF;

        *packet_buffer_out = ethernet_frame + ETHERNET_MAC_HEADER_LENGTH;

        return de_index;
}

U8 *EthernetMACGet()
{
        return pcnet.mac_address;
}

U0 NetStop()
{ // Halt network activity by setting STOP bit on Status CSR.
        U32 csr = PCNetCSRRead(PCNET_CSR_CTRLSTATUS);

        Bts(&csr, PCNET_CTRL_STOP);

        PCNetCSRWrite(PCNET_CSR_CTRLSTATUS, csr);

}

U0 NetStart()
{ // Continue network activity. Setting START bit clears STOP/INIT.
        U32 csr = PCNetCSRRead(PCNET_CSR_CTRLSTATUS);

        Bts(&csr, PCNET_CTRL_STRT);

        PCNetCSRWrite(PCNET_CSR_CTRLSTATUS, csr);
}

PCNetInit;