;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                                 ;;
;; Copyright (C) KolibriOS team 2004-2017. All rights reserved.    ;;
;; Distributed under terms of the GNU General Public License       ;;
;;                                                                 ;;
;; i8255x (Intel eepro 100) driver for KolibriOS                   ;;
;;                                                                 ;;
;;    Written by hidnplayr@kolibrios.org                           ;;
;;                                                                 ;;
;;          GNU GENERAL PUBLIC LICENSE                             ;;
;;             Version 2, June 1991                                ;;
;;                                                                 ;;
;; Some parts of this driver are based on the code of eepro100.c   ;;
;;  from linux.                                                    ;;
;;                                                                 ;;
;; Intel's programming manual for i8255x:                          ;;
;; http://www.intel.com/design/network/manuals/8255x_opensdm.htm   ;;
;;                                                                 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;TODO: use more RX buffers

format PE DLL native
entry START

        CURRENT_API             = 0x0200
        COMPATIBLE_API          = 0x0100
        API_VERSION             = (COMPATIBLE_API shl 16) + CURRENT_API

        MAX_DEVICES             = 16

        TX_RING_SIZE            = 16

        DEBUG                   = 1
        __DEBUG__               = 1
        __DEBUG_LEVEL__         = 2             ; 1 = verbose, 2 = errors only

section '.flat' readable writable executable

include '../proc32.inc'
include '../struct.inc'
include '../macros.inc'
include '../fdo.inc'
include '../netdrv.inc'

; I/O registers
REG_SCB_STATUS          = 0
REG_SCB_CMD             = 2
REG_SCB_PTR             = 4
REG_PORT                = 8
REG_EEPROM              = 14
REG_MDI_CTRL            = 16

; Port commands
PORT_SOFT_RESET         = 0x0
PORT_SELF_TEST          = 0x1
PORT_SELECTIVE_RESET    = 0x2
PORT_DUMP               = 0x3
PORT_DUMP_WAKEUP        = 0x7
PORT_PTR_MASK           = 0xfffffff0

; Serial EEPROM
EE_SK                   = 1 shl 0       ; serial clock
EE_CS                   = 1 shl 1       ; chip select
EE_DI                   = 1 shl 2       ; data in
EE_DO                   = 1 shl 3       ; data out
EE_MASK                 = EE_SK + EE_CS + EE_DI + EE_DO
; opcodes, first bit is start bit and must be 1
EE_READ                 = 110b
EE_WRITE                = 101b
EE_ERASE                = 111b

; The SCB accepts the following controls for the Tx and Rx units:
CU_START                = 0x0010
CU_RESUME               = 0x0020
CU_STATSADDR            = 0x0040
CU_SHOWSTATS            = 0x0050        ; Dump statistics counters.
CU_CMD_BASE             = 0x0060        ; Base address to add CU commands.
CU_DUMPSTATS            = 0x0070        ; Dump then reset stats counters.

RX_START                = 0x0001
RX_RESUME               = 0x0002
RX_ABORT                = 0x0004
RX_ADDR_LOAD            = 0x0006
RX_RESUMENR             = 0x0007
INT_MASK                = 0x0100
DRVR_INT                = 0x0200        ; Driver generated interrupt

PHY_100a                = 0x000003E0
PHY_100c                = 0x035002A8
PHY_82555_tx            = 0x015002A8
PHY_nsc_tx              = 0x5C002000
PHY_82562_et            = 0x033002A8
PHY_82562_em            = 0x032002A8
PHY_82562_ek            = 0x031002A8
PHY_82562_eh            = 0x017002A8
PHY_82552_v             = 0xd061004d
PHY_unknown             = 0xFFFFFFFF

MAC_82557_D100_A        = 0
MAC_82557_D100_B        = 1
MAC_82557_D100_C        = 2
MAC_82558_D101_A4       = 4
MAC_82558_D101_B0       = 5
MAC_82559_D101M         = 8
MAC_82559_D101S         = 9
MAC_82550_D102          = 12
MAC_82550_D102_C        = 13
MAC_82551_E             = 14
MAC_82551_F             = 15
MAC_82551_10            = 16
MAC_unknown             = 0xFF

SCB_STATUS_RUS          = 111100b       ; RU Status
RU_STATUS_IDLE          = 0000b shl 2
RU_STATUS_SUSPENDED     = 0001b shl 2
RU_STATUS_NO_RESOURCES  = 0010b shl 2
RU_STATUS_READY         = 0100b shl 2
SCB_STATUS_FCP          = 1 shl 8       ; Flow Control Pause
SCB_STATUS_SWI          = 1 shl 10      ; Software Interrupt
SCB_STATUS_MDI          = 1 shl 11      ; MDI read/write complete
SCB_STATUS_RNR          = 1 shl 12      ; Receiver Not Ready
SCB_STATUS_CNA          = 1 shl 13      ; Command unit Not Active
SCB_STATUS_FR           = 1 shl 14      ; Frame received
SCB_STATUS_CX_TNO       = 1 shl 15      ; Command finished / Transmit Not Okay

struct  rxfd

        status          dw ?
        command         dw ?
        link            dd ?
        rx_buf_addr     dd ?
        count           dw ?
        size            dw ?
        packet          rb 1500

ends

RXFD_STATUS_RC          = 1 shl 0       ; Receive collision
RXFD_STATUS_IA          = 1 shl 1       ; IA mismatch
RXFD_STATUS_NA          = 1 shl 2       ; No address match
RXFD_STATUS_RE          = 1 shl 4       ; Receive Error
RXFD_STATUS_TL          = 1 shl 5       ; Type/length
RXFD_STATUS_FS          = 1 shl 7       ; Frame too short
RXFD_STATUS_DMA_FAIL    = 1 shl 8       ; DMA overrun failure
RXFD_STATUS_NR          = 1 shl 9       ; Out of buffer space; no resources
RXFD_STATUS_MISA        = 1 shl 10      ; Alignment error
RXFD_STATUS_CRC_ERR     = 1 shl 11      ; CRC error in aligned frame
RXFD_STATUS_OK          = 1 shl 13      ; Frame received and stored
RXFD_STATUS_C           = 1 shl 15      ; Completion of frame reception

RXFD_CMD_SF             = 1 shl 3
RXFD_CMD_H              = 1 shl 4       ; Header RFD
RXFD_CMD_SUSPEND        = 1 shl 14      ; Suspends RU after receiving the frame
RXFD_CMD_EL             = 1 shl 15      ; Last RFD in RFA

struct  txfd

        status          dw ?
        command         dw ?
        link            dd ?
        desc_addr       dd ?
        count           dd ?

        buf_addr        dd ?
        buf_size        dd ?
        virt_addr       dd ?
                        dd ?            ; alignment

ends

TXFD_CMD_IA             = 1 shl 0
TXFD_CMD_CFG            = 1 shl 1
TXFD_CMD_TX             = 1 shl 2
TXFD_CMD_TX_FLEX        = 1 shl 3
TXFD_CMD_SUSPEND        = 1 shl 14

struc   confcmd {

        .status         dw ?
        .command        dw ?
        .link           dd ?
        .data           rb 64

}

struc   lstats {

        .tx_good_frames         dd ?
        .tx_coll16_errs         dd ?
        .tx_late_colls          dd ?
        .tx_underruns           dd ?
        .tx_lost_carrier        dd ?
        .tx_deferred            dd ?
        .tx_one_colls           dd ?
        .tx_multi_colls         dd ?
        .tx_total_colls         dd ?

        .rx_good_frames         dd ?
        .rx_crc_errs            dd ?
        .rx_align_errs          dd ?
        .rx_resource_errs       dd ?
        .rx_overrun_errs        dd ?
        .rx_colls_errs          dd ?
        .rx_runt_errs           dd ?

}

struct  device          ETH_DEVICE

        io_addr         dd ?
        pci_bus         dd ?
        pci_dev         dd ?
        rx_desc         dd ?
        cur_tx          dd ?
        last_tx         dd ?
        ee_bus_width    db ?
        irq_line        db ?

        rb 0x100 - ($ and 0xff) ; align 256
        tx_ring         rb TX_RING_SIZE*sizeof.txfd

        rb 0x100 - ($ and 0xff) ; align 256
        confcmd         confcmd

        rb 0x100 - ($ and 0xff) ; align 256
        lstats          lstats

ends

;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                        ;;
;; proc START             ;;
;;                        ;;
;; (standard driver proc) ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;

proc START c, state:dword

        cmp [state], 1
        jne .exit

  .entry:

        DEBUGF 1,"Loading driver\n"
        invoke  RegService, my_service, service_proc
        ret

  .fail:
  .exit:
        xor eax, eax
        ret

endp


;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                        ;;
;; proc SERVICE_PROC      ;;
;;                        ;;
;; (standard driver proc) ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;

align 4
proc service_proc stdcall, ioctl:dword

        mov     edx, [ioctl]
        mov     eax, [edx + IOCTL.io_code]

;------------------------------------------------------

        cmp     eax, 0 ;SRV_GETVERSION
        jne     @F

        cmp     [edx + IOCTL.out_size], 4
        jb      .fail
        mov     eax, [edx + IOCTL.output]
        mov     [eax], dword API_VERSION

        xor     eax, eax
        ret

;------------------------------------------------------
  @@:
        cmp     eax, 1 ;SRV_HOOK
        jne     .fail

        cmp     [edx + IOCTL.inp_size], 3               ; Data input must be at least 3 bytes
        jb      .fail

        mov     eax, [edx + IOCTL.input]
        cmp     byte [eax], 1                           ; 1 means device number and bus number (pci) are given
        jne     .fail                                   ; other types arent supported for this card yet

; check if the device is already listed

        mov     esi, device_list
        mov     ecx, [devices]
        test    ecx, ecx
        jz      .firstdevice

;        mov     eax, [edx + IOCTL.input]                ; get the pci bus and device numbers
        mov     ax , [eax+1]                            ;
  .nextdevice:
        mov     ebx, [esi]
        cmp     al, byte[ebx + device.pci_bus]
        jne     @f
        cmp     ah, byte[ebx + device.pci_dev]
        je      .find_devicenum                         ; Device is already loaded, let's find it's device number
       @@:
        add     esi, 4
        loop    .nextdevice


; This device doesnt have its own eth_device structure yet, lets create one
  .firstdevice:
        cmp     [devices], MAX_DEVICES                  ; First check if the driver can handle one more card
        jae     .fail

        allocate_and_clear ebx, sizeof.device, .fail      ; Allocate the buffer for device structure

; Fill in the direct call addresses into the struct

        mov     [ebx + device.reset], reset
        mov     [ebx + device.transmit], transmit
        mov     [ebx + device.unload], unload
        mov     [ebx + device.name], devicename

; save the pci bus and device numbers

        mov     eax, [edx + IOCTL.input]
        movzx   ecx, byte[eax+1]
        mov     [ebx + device.pci_bus], ecx
        movzx   ecx, byte[eax+2]
        mov     [ebx + device.pci_dev], ecx

; Now, it's time to find the base io addres of the PCI device

        stdcall PCI_find_io, [ebx + device.pci_bus], [ebx + device.pci_dev]
        mov     [ebx + device.io_addr], eax

; We've found the io address, find IRQ now

        invoke  PciRead8, [ebx + device.pci_bus], [ebx + device.pci_dev], PCI_header00.interrupt_line
        mov     [ebx + device.irq_line], al

        DEBUGF  1,"Hooking into device, devfn:%x, bus:%x, irq:%x, addr:%x\n",\
        [ebx + device.pci_dev]:2,[ebx + device.pci_bus]:2,[ebx + device.irq_line]:2,[ebx + device.io_addr]:4

; Ok, the eth_device structure is ready, let's probe the device

        mov     eax, [devices]                                          ; Add the device structure to our device list
        mov     [device_list+4*eax], ebx                                ; (IRQ handler uses this list to find device)
        inc     [devices]                                               ;

        call    probe                                                   ; this function will output in eax
        test    eax, eax
        jnz     .err                                                 ; If an error occured, exit

        mov     [ebx + device.type], NET_TYPE_ETH
        invoke  NetRegDev

        cmp     eax, -1
        je      .err

        ret

; If the device was already loaded, find the device number and return it in eax

  .find_devicenum:
        DEBUGF  2,"Trying to find device number of already registered device\n"
        invoke  NetPtrToNum                                             ; This kernel procedure converts a pointer to device struct in ebx
                                                                        ; into a device number in edi
        mov     eax, edi                                                ; Application wants it in eax instead
        DEBUGF  2,"Kernel says: %u\n", eax
        ret

; If an error occured, remove all allocated data and exit (returning -1 in eax)
  .err:
        invoke  KernelFree, ebx

  .fail:
        or      eax, -1
        ret

;------------------------------------------------------
endp





;;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\;;
;;                                                                        ;;
;;        Actual Hardware dependent code starts here                      ;;
;;                                                                        ;;
;;/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\;;


unload:
        ; TODO: (in this particular order)
        ;
        ; - Stop the device
        ; - Detach int handler
        ; - Remove device from local list (device_list)
        ; - call unregister function in kernel
        ; - Remove all allocated structures and buffers the card used

        or      eax, -1
        ret


;-------------
;
; Probe
;
;-------------

align 4
probe:

        DEBUGF  1,"Probing\n"

; Make the device a bus master
        invoke  PciRead32, [ebx + device.pci_bus], [ebx + device.pci_dev], PCI_header00.command
        or      al, PCI_CMD_MASTER or PCI_CMD_PIO
        invoke  PciWrite32, [ebx + device.pci_bus], [ebx + device.pci_dev], PCI_header00.command, eax

;---------------------------
; First, identify the device

        invoke  PciRead32, [ebx + device.pci_bus], [ebx + device.pci_dev], PCI_header.vendor_id ; get device/vendor id

        DEBUGF  1,"Vendor_id=0x%x\n", ax
        cmp     ax, 0x8086
        jne     .notfound
        shr     eax, 16

        DEBUGF  1,"Device_id=0x%x\n", ax
        mov     ecx, DEVICE_IDs
        mov     edi, device_id_list
        repne   scasw
        jne     .notfound
        jmp     .found

  .notfound:
        DEBUGF  2,"Unsupported device!\n"
        or      eax, -1
        ret

  .found:



;----------
;
;  Reset
;
;----------

align 4
reset:

        movzx   eax, [ebx + device.irq_line]
        DEBUGF  1,"Attaching int handler to irq %x\n", eax:1
        invoke  AttachIntHandler, eax, int_handler, ebx
        test    eax, eax
        jnz     @f
        DEBUGF  2,"Could not attach int handler!\n"
        or      eax, -1
        ret
  @@:

        DEBUGF  1,"Resetting\n"

;----------------
; Selective reset

        set_io  [ebx + device.io_addr], 0
        set_io  [ebx + device.io_addr], REG_EEPROM
        mov     eax, PORT_SELECTIVE_RESET
        out     dx, eax

        mov     esi, 10
        invoke  Sleep

;-----------
; Soft reset

        set_io  [ebx + device.io_addr], REG_PORT
        mov     eax, PORT_SOFT_RESET
        out     dx, eax

        mov     esi, 10
        invoke  Sleep

;-------------
; Read PHY IDs

        mov     cx, 1
        mov     dx, MII_PHYSID1
        call    mdio_read
        DEBUGF  1, "PHY ID1: 0x%x\n", ax

        mov     cx, 1
        mov     dx, MII_PHYSID2
        call    mdio_read
        DEBUGF  1, "PHY ID2: 0x%x\n", ax

;---------------------
; Read MAC from eeprom

        call    ee_get_width
        call    mac_read_eeprom

;---------------------------------
; Tell device where to store stats

        lea     eax, [ebx + device.lstats.tx_good_frames]      ; lstats
        invoke  GetPhysAddr
        set_io  [ebx + device.io_addr], 0
        set_io  [ebx + device.io_addr], REG_SCB_PTR
        out     dx, eax

        set_io  [ebx + device.io_addr], REG_SCB_CMD
        mov     ax, CU_STATSADDR or INT_MASK
        out     dx, ax
        call    cmd_wait

;------------------------
; setup RX base addr to 0

        set_io  [ebx + device.io_addr], REG_SCB_PTR
        xor     eax, eax
        out     dx, eax

        set_io  [ebx + device.io_addr], REG_SCB_CMD
        mov     ax, RX_ADDR_LOAD or INT_MASK
        out     dx, ax
        call    cmd_wait

;-----------------------------
; Create RX and TX descriptors

        call    init_rx_ring
        test    eax, eax
        jz      .error

        call    init_tx_ring


;---------
; Start RX

        DEBUGF  1, "Starting RX"

        set_io  [ebx + device.io_addr], 0
        set_io  [ebx + device.io_addr], REG_SCB_PTR
        mov     eax, [ebx + device.rx_desc]
        invoke  GetPhysAddr
        add     eax, NET_BUFF.data
        out     dx, eax

        set_io  [ebx + device.io_addr], REG_SCB_CMD
        mov     ax, RX_START or INT_MASK
        out     dx, ax
        call    cmd_wait

;----------
; Set-up TX

        set_io  [ebx + device.io_addr], REG_SCB_PTR
        xor     eax, eax
        out     dx, eax

        set_io  [ebx + device.io_addr], REG_SCB_CMD
        mov     ax, CU_CMD_BASE or INT_MASK
        out     dx, ax
        call    cmd_wait

;-------------------------
; Individual address setup

        mov     [ebx + device.confcmd.command], TXFD_CMD_IA + TXFD_CMD_SUSPEND
        mov     [ebx + device.confcmd.status], 0
        lea     eax, [ebx + device.tx_ring]
        invoke  GetPhysAddr
        mov     [ebx + device.confcmd.link], eax
        lea     edi, [ebx + device.confcmd.data]
        lea     esi, [ebx + device.mac]
        movsd
        movsw

        set_io  [ebx + device.io_addr], REG_SCB_PTR
        lea     eax, [ebx + device.confcmd.status]
        invoke  GetPhysAddr
        out     dx, eax

        set_io  [ebx + device.io_addr], REG_SCB_CMD
        mov     ax, CU_START or INT_MASK
        out     dx, ax
        call    cmd_wait

;-------------
; Configure CU

        mov     [ebx + device.confcmd.command], TXFD_CMD_CFG + TXFD_CMD_SUSPEND
        mov     [ebx + device.confcmd.status], 0
        lea     eax, [ebx + device.confcmd.status]
        invoke  GetPhysAddr
        mov     [ebx + device.confcmd.link], eax

        mov     esi, confcmd_data
        lea     edi, [ebx + device.confcmd.data]
        mov     ecx, 22
        rep     movsb

        set_io  [ebx + device.io_addr], REG_SCB_PTR
        lea     eax, [ebx + device.confcmd.status]
        invoke  GetPhysAddr
        out     dx, eax

        set_io  [ebx + device.io_addr], REG_SCB_CMD
        mov     ax, CU_START                            ; expect Interrupts from now on
        out     dx, ax
        call    cmd_wait

        DEBUGF  1,"Reset complete\n"
        mov     [ebx + device.mtu], 1514

; Set link state to unknown
        mov     [ebx + device.state], ETH_LINK_UNKNOWN

        xor     eax, eax        ; indicate that we have successfully reset the card
        ret

  .error:
        or      eax, -1
        ret


align 4
init_rx_ring:

        DEBUGF  1,"Creating ring\n"

;---------------------
; build rxfd structure

        invoke  NetAlloc, 2000
        test    eax, eax
        jz      .out_of_mem
        mov     [ebx + device.rx_desc], eax
        mov     esi, eax
        invoke  GetPhysAddr
        add     eax, NET_BUFF.data
        mov     [esi + sizeof.NET_BUFF + rxfd.status], 0
        mov     [esi + sizeof.NET_BUFF + rxfd.command], RXFD_CMD_EL or RXFD_CMD_SUSPEND
        mov     [esi + sizeof.NET_BUFF + rxfd.link], eax
        mov     [esi + sizeof.NET_BUFF + rxfd.count], 0
        mov     [esi + sizeof.NET_BUFF + rxfd.size], 1528

        ret

  .out_of_mem:
        ret




align 4
init_tx_ring:

        DEBUGF  1,"Creating TX ring\n"

        lea     esi, [ebx + device.tx_ring]
        mov     eax, esi
        invoke  GetPhysAddr
        mov     ecx, TX_RING_SIZE
  .next_desc:
        mov     [esi + txfd.status], 0
        mov     [esi + txfd.command], 0
        lea     edx, [eax + txfd.buf_addr]
        mov     [esi + txfd.desc_addr], edx
        add     eax, sizeof.txfd
        mov     [esi + txfd.link], eax
        mov     [esi + txfd.count], 0x01208000          ; One buffer, 0x20 bytes of transmit threshold, end of frame
        add     esi, sizeof.txfd
        dec     ecx
        jnz     .next_desc

        lea     eax, [ebx + device.tx_ring]
        invoke  GetPhysAddr
        mov     dword[ebx + device.tx_ring + sizeof.txfd*(TX_RING_SIZE-1) + txfd.link], eax

        mov     [ebx + device.cur_tx], 0
        mov     [ebx + device.last_tx], 0

        ret


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                         ;;
;; Transmit                                ;;
;;                                         ;;
;; In: pointer to device structure in ebx  ;;
;;                                         ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

proc transmit stdcall bufferptr

        pushf
        cli

        mov     esi, [bufferptr]
        DEBUGF  1,"Transmitting packet, buffer:%x, size:%u\n", [bufferptr], [esi + NET_BUFF.length]
        lea     eax, [esi + NET_BUFF.data]
        DEBUGF  1,"To: %x-%x-%x-%x-%x-%x From: %x-%x-%x-%x-%x-%x Type:%x%x\n",\
        [eax+00]:2,[eax+01]:2,[eax+02]:2,[eax+03]:2,[eax+04]:2,[eax+05]:2,\
        [eax+06]:2,[eax+07]:2,[eax+08]:2,[eax+09]:2,[eax+10]:2,[eax+11]:2,\
        [eax+13]:2,[eax+12]:2

        cmp     [esi + NET_BUFF.length], 1514
        ja      .fail
        cmp     [esi + NET_BUFF.length], 60
        jb      .fail

        ; Get current TX descriptor
        mov     edi, [ebx + device.cur_tx]
        mov     eax, sizeof.txfd
        mul     edi
        lea     edi, [ebx + device.tx_ring + eax]

        ; Check if current descriptor is free or still in use
        cmp     [edi + txfd.status], 0
        jne     .fail

        ; Fill in status and command values
        mov     [edi + txfd.status], 0
        mov     [edi + txfd.command], TXFD_CMD_SUSPEND + TXFD_CMD_TX + TXFD_CMD_TX_FLEX
        mov     [edi + txfd.count], 0x01208000

        ; Fill in buffer address and size
        mov     [edi + txfd.virt_addr], esi
        mov     eax, esi
        add     eax, [esi + NET_BUFF.offset]
        push    edi
        invoke  GetPhysAddr
        pop     edi
        mov     [edi + txfd.buf_addr], eax
        mov     ecx, [esi + NET_BUFF.length]
        mov     [edi + txfd.buf_size], ecx

        ; Inform device of the new/updated transmit descriptor
        mov     eax, edi
        invoke  GetPhysAddr
        set_io  [ebx + device.io_addr], 0
        set_io  [ebx + device.io_addr], REG_SCB_PTR
        out     dx, eax

        ; Start the transmit
        set_io  [ebx + device.io_addr], REG_SCB_CMD
        mov     ax, CU_START
        out     dx, ax

        ; Update stats
        inc     [ebx + device.packets_tx]
        add     dword[ebx + device.bytes_tx], ecx
        adc     dword[ebx + device.bytes_tx + 4], 0

        ; Wait for command to complete
        call    cmd_wait

        inc     [ebx + device.cur_tx]
        and     [ebx + device.cur_tx], TX_RING_SIZE - 1

        DEBUGF  1,"Transmit OK\n"
        popf
        xor     eax, eax
        ret

  .fail:
        DEBUGF  2,"Transmit failed!\n"
        invoke  NetFree, [bufferptr]
        popf
        or      eax, -1
        ret

endp


;;;;;;;;;;;;;;;;;;;;;;;
;;                   ;;
;; Interrupt handler ;;
;;                   ;;
;;;;;;;;;;;;;;;;;;;;;;;

align 4
int_handler:

        push    ebx esi edi

        DEBUGF  1,"INT\n"

; find pointer of device wich made IRQ occur

        mov     ecx, [devices]
        test    ecx, ecx
        jz      .nothing
        mov     esi, device_list
  .nextdevice:
        mov     ebx, [esi]

;        set_io  [ebx + device.io_addr], 0      ; REG_SCB_STATUS = 0
        set_io  [ebx + device.io_addr], REG_SCB_STATUS
        in      ax, dx
        out     dx, ax                          ; send it back to ACK
        test    ax, ax
        jnz     .got_it
  .continue:
        add     esi, 4
        dec     ecx
        jnz     .nextdevice
  .nothing:
        pop     edi esi ebx
        xor     eax, eax

        ret                                         ; If no device was found, abort (The irq was probably for a device, not registered to this driver)

  .got_it:

        DEBUGF  1,"Device: %x Status: %x\n", ebx, ax

        test    ax, SCB_STATUS_FR               ; did we receive a frame?
        jz      .no_rx

        push    ax

        DEBUGF  1,"Receiving\n"

        push    ebx
  .rx_loop:
        pop     ebx

        mov     esi, [ebx + device.rx_desc]
        test    [esi + sizeof.NET_BUFF + rxfd.status], RXFD_STATUS_C            ; Completed?
        jz      .no_rx_
        test    [esi + sizeof.NET_BUFF + rxfd.status], RXFD_STATUS_OK           ; OK?
        jz      .not_ok

        DEBUGF  1,"rxfd status=0x%x\n", [esi + sizeof.NET_BUFF + rxfd.status]:4

        movzx   ecx, [esi + sizeof.NET_BUFF + rxfd.count]
        and     ecx, 0x3fff

        push    ebx
        push    .rx_loop
        push    esi
        mov     [esi + NET_BUFF.length], ecx
        mov     [esi + NET_BUFF.device], ebx
        mov     [esi + NET_BUFF.offset], NET_BUFF.data + rxfd.packet

; Update stats
        add     dword [ebx + device.bytes_rx], ecx
        adc     dword [ebx + device.bytes_rx + 4], 0
        inc     dword [ebx + device.packets_rx]

; allocate new descriptor

        invoke  NetAlloc, 2000
        test    eax, eax
        jz      .out_of_mem
        mov     [ebx + device.rx_desc], eax
        mov     esi, eax
        invoke  GetPhysAddr
        add     eax, NET_BUFF.data
        mov     [esi + sizeof.NET_BUFF + rxfd.status], 0
        mov     [esi + sizeof.NET_BUFF + rxfd.command], RXFD_CMD_EL or RXFD_CMD_SUSPEND
        mov     [esi + sizeof.NET_BUFF + rxfd.link], eax
        mov     [esi + sizeof.NET_BUFF + rxfd.count], 0
        mov     [esi + sizeof.NET_BUFF + rxfd.size], 1528

; restart RX

        set_io  [ebx + device.io_addr], 0
        set_io  [ebx + device.io_addr], REG_SCB_PTR
;        mov     eax, [ebx + device.rx_desc]
;        invoke  GetPhysAddr
;        add     eax, NET_BUFF.data
        out     dx, eax

        set_io  [ebx + device.io_addr], REG_SCB_CMD
        mov     ax, RX_START
        out     dx, ax
        call    cmd_wait
  .out_of_mem:

; Hand the frame over to the kernel
        jmp     [EthInput]

  .not_ok:
; Reset the FD
        mov     [esi + sizeof.NET_BUFF + rxfd.status], 0
        mov     [esi + sizeof.NET_BUFF + rxfd.command], RXFD_CMD_EL or RXFD_CMD_SUSPEND
        mov     [esi + sizeof.NET_BUFF + rxfd.count], 0

; Restart RX
        set_io  [ebx + device.io_addr], 0
        set_io  [ebx + device.io_addr], REG_SCB_PTR
        mov     eax, esi
        invoke  GetPhysAddr
        add     eax, NET_BUFF.data
        out     dx, eax

        set_io  [ebx + device.io_addr], REG_SCB_CMD
        mov     ax, RX_START
        out     dx, ax
        call    cmd_wait

        push    ebx
        jmp     .rx_loop

  .no_rx_:
        DEBUGF  1, "no more data\n"
        pop     ax

  .no_rx:

        test    ax, SCB_STATUS_CNA
        jz      .no_tx
        DEBUGF  1, "Command completed\n"

        push    eax
  .loop_tx:
        mov     edi, [ebx + device.last_tx]
        mov     eax, sizeof.txfd
        mul     eax
        lea     edi, [ebx + device.tx_ring + eax]

        cmp     [edi + txfd.status], 0
        je      .tx_done

        cmp     [edi + txfd.virt_addr], 0
        je      .tx_done

        DEBUGF  1,"Freeing buffer 0x%x\n", [edi + txfd.virt_addr]

        push    [edi + txfd.virt_addr]
        mov     [edi + txfd.virt_addr], 0
        invoke  NetFree

        inc     [ebx + device.last_tx]
        and     [ebx + device.last_tx], TX_RING_SIZE - 1

        jmp     .loop_tx
  .tx_done:
        pop     eax
  .no_tx:

        test    ax, RU_STATUS_NO_RESOURCES
        jne     .fail

        DEBUGF  2, "Out of resources!\n"

  .fail:
        pop     edi esi ebx
        xor     eax, eax
        inc     eax

        ret




align 4
cmd_wait:

        in      al, dx
        test    al, al
        jnz     cmd_wait

        ret






align 4
ee_read:        ; esi = address to read

        DEBUGF  1,"Eeprom read from 0x%x\n", esi

        set_io  [ebx + device.io_addr], 0
        set_io  [ebx + device.io_addr], REG_EEPROM

;-----------------------------------------------------
; Prepend start bit + read opcode to the address field
; and shift it to the very left bits of esi

        mov     cl, 29
        sub     cl, [ebx + device.ee_bus_width]
        shl     esi, cl
        or      esi, EE_READ shl 29

        movzx   ecx, [ebx + device.ee_bus_width]
        add     ecx, 3

        mov     ax, 0x4800 + EE_CS
        out     dx, ax
        call    udelay

;-----------------------
; Write this to the chip

  .loop:
        mov     al, EE_CS
        shl     esi, 1
        jnc     @f
        or      al, EE_DI
       @@:
        out     dx, al
        call    udelay

        or      al, EE_SK
        out     dx, al
        call    udelay

        loop    .loop

;------------------------------
; Now read the data from eeprom

        xor     esi, esi
        mov     ecx, 16

  .loop2:
        shl     esi, 1
        mov     al, EE_CS
        out     dx, al
        call    udelay

        or      al, EE_SK
        out     dx, al
        call    udelay

        in      al, dx
        test    al, EE_DO
        jz      @f
        inc     esi
       @@:

        loop    .loop2

;-----------------------
; de-activate the eeprom

        xor     al, al
        out     dx, al

        DEBUGF  1,"0x%x\n", esi:4
        ret



align 4
ee_write:       ; esi = address to write to, di = data

        DEBUGF  1,"Eeprom write 0x%x to 0x%x\n", di, esi

        set_io  [ebx + device.io_addr], 0
        set_io  [ebx + device.io_addr], REG_EEPROM

;-----------------------------------------------------
; Prepend start bit + write opcode to the address field
; and shift it to the very left bits of esi

        mov     cl, 29
        sub     cl, [ebx + device.ee_bus_width]
        shl     esi, cl
        or      esi, EE_WRITE shl 29

        movzx   ecx, [ebx + device.ee_bus_width]
        add     ecx, 3

        mov     ax, 0x4800 + EE_CS       ; enable chip
        out     dx, ax

;-----------------------
; Write this to the chip

  .loop:
        mov     al, EE_CS
        shl     esi, 1
        jnc     @f
        or      al, EE_DI
       @@:
        out     dx, al
        call    udelay

        or      al, EE_SK
        out     dx, al
        call    udelay

        loop    .loop

;-----------------------------
; Now write the data to eeprom

        mov     ecx, 16

  .loop2:
        mov     al, EE_CS
        shl     di, 1
        jnc     @f
        or      al, EE_DI
       @@:
        out     dx, al
        call    udelay

        or      al, EE_SK
        out     dx, al
        call    udelay

        loop    .loop2

;-----------------------
; de-activate the eeprom

        xor     al, al
        out     dx, al


        ret



align 4
ee_get_width:

        set_io  [ebx + device.io_addr], 0
        set_io  [ebx + device.io_addr], REG_EEPROM

        mov     ax, 0x4800 + EE_CS      ; activate eeprom
        out     dx, ax
        call    udelay

        mov     si, EE_READ shl 13
        xor     ecx, ecx
  .loop:
        mov     al, EE_CS
        shl     si, 1
        jnc     @f
        or      al, EE_DI
       @@:
        out     dx, ax
        call    udelay

        or      al, EE_SK
        out     dx, ax
        call    udelay

        inc     ecx

        cmp     ecx, 15
        jae     .give_up

        in      al, dx
        test    al, EE_DO
        jnz     .loop

        xor     al, al
        out     dx, al                  ; de-activate eeprom

        sub     cl, 3                   ; dont count the opcode bits
        mov     [ebx + device.ee_bus_width], cl
        DEBUGF  1, "Eeprom width=%u bit\n", ecx

        ret

  .give_up:
        DEBUGF  2, "Eeprom not found!\n"
        xor     al, al
        out     dx, al                  ; de-activate eeprom

        ret


; Wait a minimum of 2�s
udelay:
        pusha
        mov     esi, 1
        invoke  Sleep
        popa

        ret



; cx = phy addr
; dx = phy reg addr

; ax = data

align 4
mdio_read:

        DEBUGF  1,"MDIO read\n"

        shl     ecx, 21                 ; PHY addr
        mov     eax, ecx
        shl     edx, 16                 ; PHY reg addr
        or      eax, edx
        or      eax, 10b shl 26         ; read opcode

        set_io  [ebx + device.io_addr], 0
        set_io  [ebx + device.io_addr], REG_MDI_CTRL
        out     dx, eax

  .wait:
        call    udelay
        in      eax, dx
        test    eax, 1 shl 28           ; ready bit
        jz      .wait

        ret



; ax = data
; cx = phy addr
; dx = phy reg addr

; ax = data

align 4
mdio_write:

        DEBUGF  1,"MDIO write\n"

        and     eax, 0xffff

        shl     ecx, 21                 ; PHY addr
        shl     edx, 16                 ; PHY reg addr

        or      eax, ecx
        or      eax, edx
        or      eax, 01b shl 26         ; write opcode

        set_io  [ebx + device.io_addr], 0
        set_io  [ebx + device.io_addr], REG_MDI_CTRL
        out     dx, eax

  .wait:
        call    udelay
        in      eax, dx
        test    eax, 1 shl 28           ; ready bit
        jz      .wait

        ret


align 4
mac_read_eeprom:

        mov     esi, 0
        call    ee_read
        mov     word[ebx + device.mac], si

        mov     esi, 1
        call    ee_read
        mov     word[ebx + device.mac+2], si

        mov     esi, 2
        call    ee_read
        mov     word[ebx + device.mac+4], si


        ret


; End of code


data fixups
end data

include '../peimport.inc'

my_service      db 'I8255X', 0                    ; max 16 chars include zero
devicename      db 'Intel Etherexpress pro/100', 0

confcmd_data    db 22, 0x08, 0, 0, 0, 0x80, 0x32, 0x03, 1
                db 0, 0x2e, 0, 0x60, 0, 0xf2, 0x48, 0, 0x40, 0xf2
                db 0x80, 0x3f, 0x05                                     ; 22 bytes total


device_id_list:
dw 0x1029
dw 0x1030
dw 0x1031
dw 0x1032
dw 0x1033
dw 0x1034
dw 0x1038
dw 0x1039
dw 0x103A
dw 0x103B
dw 0x103C
dw 0x103D
dw 0x103E
dw 0x1050
dw 0x1051
dw 0x1052
dw 0x1053
dw 0x1054
dw 0x1055
dw 0x1056
dw 0x1057
dw 0x1059
dw 0x1064
dw 0x1065
dw 0x1066
dw 0x1067
dw 0x1068
dw 0x1069
dw 0x106A
dw 0x106B
dw 0x1091
dw 0x1092
dw 0x1093
dw 0x1094
dw 0x1095
dw 0x10fe
dw 0x1209
dw 0x1229
dw 0x2449
dw 0x2459
dw 0x245D
dw 0x27DC

DEVICE_IDs = ($ - device_id_list) / 2

include_debug_strings                           ; All data wich FDO uses will be included here

align 4
devices         dd 0                              ; number of currently running devices
device_list     rd MAX_DEVICES                    ; This list contains all pointers to device structures the driver is handling