;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                                    ;;
;;  PCNET32.INC                                                       ;;
;;                                                                    ;;
;;  Ethernet driver for Menuet OS                                     ;;
;;                                                                    ;;
;;  Version 1.0  31 July 2004                                         ;;
;;                                                                    ;;
;;  This driver is based on the PCNet32 driver from                   ;;
;;  the etherboot 5.0.6 project. The copyright statement is           ;;
;;                                                                    ;;
;;          GNU GENERAL PUBLIC LICENSE                                ;;
;;             Version 2, June 1991                                   ;;
;;                                                                    ;;
;;  remaining parts Copyright 2004 Jarek Pelczar,                     ;;
;;   jpelczar@interia.pl                                              ;;
;;                                                                    ;;
;;  See file COPYING for details                                      ;;
;;                                                                    ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;macro PutStr X
;{
; local .__xyz1
; local .__xyz2
; push esi
; mov esi,.__xyz1
; call sys_msg_board_str
; push eax
; mov eax,1
; call delay_hs
; pop eax
; jmp .__xyz2
;.__xyz1:
; db X
; db 13,10,0
;.__xyz2:
; pop esi
;}
PCNET32_PORT_AUI      equ 0x00
PCNET32_PORT_10BT     equ 0x01
PCNET32_PORT_GPSI     equ 0x02
PCNET32_PORT_MII      equ 0x03
PCNET32_PORT_PORTSEL  equ 0x03
PCNET32_PORT_ASEL     equ 0x04
PCNET32_PORT_100      equ 0x40
PCNET32_PORT_FD       equ 0x80
PCNET32_DMA_MASK      equ 0xffffffff
PCNET32_LOG_TX_BUFFERS  equ 1
PCNET32_LOG_RX_BUFFERS  equ 2
PCNET32_TX_RING_SIZE            equ (1 shl PCNET32_LOG_TX_BUFFERS)
PCNET32_TX_RING_MOD_MASK        equ (PCNET32_TX_RING_SIZE-1)
PCNET32_TX_RING_LEN_BITS        equ 0
PCNET32_RX_RING_SIZE            equ (1 shl PCNET32_LOG_RX_BUFFERS)
PCNET32_RX_RING_MOD_MASK        equ (PCNET32_RX_RING_SIZE-1)
PCNET32_RX_RING_LEN_BITS        equ (PCNET32_LOG_RX_BUFFERS shl 4)
PCNET32_PKT_BUF_SZ              equ 1544
PCNET32_PKT_BUF_SZ_NEG          equ 0xf9f8
pcnet32_txb equ (eth_data_start)
pcnet32_rxb equ ((pcnet32_txb+(PCNET32_PKT_BUF_SZ*PCNET32_TX_RING_SIZE)+0xf) and 0xfffffff0)
pcnet32_tx_ring equ ((pcnet32_rxb+(PCNET32_PKT_BUF_SZ*PCNET32_RX_RING_SIZE)+0xf) and 0xfffffff0)
pcnet32_rx_ring equ ((pcnet32_tx_ring+(16*PCNET32_TX_RING_SIZE)+0xf) and 0xfffffff0)
virtual at ((pcnet32_rx_ring+(16*PCNET32_RX_RING_SIZE)+0xf) and 0xfffffff0)
pcnet32_private:
.mode                   dw ?
.tlen_rlen              dw ?
.phys_addr              db ?,?,?,?,?,?
.reserved               dw ?
.filter                 dd ?,?
.rx_ring                dd ?
.tx_ring                dd ?
.cur_rx                 dd ?
.cur_tx                 dd ?
.dirty_rx               dd ?
.dirty_tx               dd ?
.tx_full                db ?
.options                dd ?
.full_duplex            db ?
.chip_version           dd ?
.mii                    db ?
.ltint                  db ?
.dxsuflo                db ?
.fset                   db ?
.fdx                    db ?
end virtual
virtual at 0
pcnet32_rx_head:
.base           dd ?
.buf_length     dw ?
.status         dw ?
.msg_length     dd ?
.reserved       dd ?
end virtual
virtual at 0
pcnet32_tx_head:
.base           dd ?
.length         dw ?
.status         dw ?
.misc           dd ?
.reserved       dd ?
end virtual

uglobal
pcnet32_access:
.read_csr               dd ?
.write_csr              dd ?
.read_bcr               dd ?
.write_bcr              dd ?
.read_rap               dd ?
.write_rap              dd ?
.reset                  dd ?
endg

iglobal
pcnet32_options_mapping:
dd PCNET32_PORT_ASEL    ;  0 Auto-select
dd PCNET32_PORT_AUI     ;  1 BNC/AUI
dd PCNET32_PORT_AUI     ;  2 AUI/BNC
dd PCNET32_PORT_ASEL    ;  3 not supported
dd PCNET32_PORT_10BT or PCNET32_PORT_FD ;  4 10baseT-FD
dd PCNET32_PORT_ASEL    ;  5 not supported
dd PCNET32_PORT_ASEL    ;  6 not supported
dd PCNET32_PORT_ASEL    ;  7 not supported
dd PCNET32_PORT_ASEL    ;  8 not supported
dd PCNET32_PORT_MII     ;  9 MII 10baseT
dd PCNET32_PORT_MII or PCNET32_PORT_FD  ; 10 MII 10baseT-FD
dd PCNET32_PORT_MII     ; 11 MII (autosel)
dd PCNET32_PORT_10BT    ; 12 10BaseT
dd PCNET32_PORT_MII or PCNET32_PORT_100 ; 13 MII 100BaseTx
dd PCNET32_PORT_MII or PCNET32_PORT_100 or PCNET32_PORT_FD      ; 14 MII 100BaseTx-FD
dd PCNET32_PORT_ASEL    ; 15 not supported
endg

PCNET32_WIO_RDP         equ 0x10
PCNET32_WIO_RAP         equ 0x12
PCNET32_WIO_RESET       equ 0x14
PCNET32_WIO_BDP         equ 0x16
PCNET32_DWIO_RDP        equ 0x10
PCNET32_DWIO_RAP        equ 0x14
PCNET32_DWIO_RESET      equ 0x18
PCNET32_DWIO_BDP        equ 0x1C
PCNET32_TOTAL_SIZE      equ 0x20
; ebx - index
; return:
; eax - data
pcnet32_wio_read_csr:
    push edx
    lea edx,[ebp+PCNET32_WIO_RAP]
    mov ax,bx
    out dx,ax
    lea edx,[ebp+PCNET32_WIO_RDP]
    in ax,dx
    and eax,0xffff
    pop edx
    ret
; eax - data
; ebx - index
pcnet32_wio_write_csr:
    push edx
    lea edx,[ebp+PCNET32_WIO_RAP]
    xchg eax,ebx
    out dx,ax
    xchg eax,ebx
    lea edx,[ebp+PCNET32_WIO_RDP]
    out dx,ax
    pop edx
    ret
; ebx - index
; return:
; eax - data
pcnet32_wio_read_bcr:
    push edx
    lea edx,[ebp+PCNET32_WIO_RAP]
    mov ax,bx
    out dx,ax
    lea edx,[ebp+PCNET32_WIO_BDP]
    in ax,dx
    and eax,0xffff
    pop edx
    ret
; eax - data
; ebx - index
pcnet32_wio_write_bcr:
    push edx
    lea edx,[ebp+PCNET32_WIO_RAP]
    xchg eax,ebx
    out dx,ax
    xchg eax,ebx
    lea edx,[ebp+PCNET32_WIO_BDP]
    out dx,ax
    pop edx
    ret
pcnet32_wio_read_rap:
    push edx
    lea edx,[ebp+PCNET32_WIO_RAP]
    in ax,dx
    and eax,0xffff
    pop edx
    ret
; eax - val
pcnet32_wio_write_rap:
    push edx
    lea edx,[ebp+PCNET32_WIO_RAP]
    out dx,ax
    pop edx
    ret
pcnet32_wio_reset:
    push edx
    push eax
    lea edx,[ebp+PCNET32_WIO_RESET]
    in ax,dx
    pop eax
    pop edx
    ret
pcnet32_wio_check:
    push edx
    mov ax,88
    lea edx,[ebp+PCNET32_WIO_RAP]
    out dx,ax
    nop
    nop
    in ax,dx
    cmp ax,88
    sete al
    pop edx
    ret

iglobal
pcnet32_wio:
    dd pcnet32_wio_read_csr
    dd pcnet32_wio_write_csr
    dd pcnet32_wio_read_bcr
    dd pcnet32_wio_write_bcr
    dd pcnet32_wio_read_rap
    dd pcnet32_wio_write_rap
    dd pcnet32_wio_reset
endg

; ebx - index
; return:
; eax - data
pcnet32_dwio_read_csr:
    push edx
    lea edx,[ebp+PCNET32_DWIO_RAP]
    mov ebx,eax
    out dx,eax
    lea edx,[ebp+PCNET32_DWIO_RDP]
    in eax,dx
    and eax,0xffff
    pop edx
    ret
; ebx - index
; eax - data
pcnet32_dwio_write_csr:
    push edx
    lea edx,[ebp+PCNET32_DWIO_RAP]
    xchg eax,ebx
    out dx,eax
    lea edx,[ebp+PCNET32_DWIO_RDP]
    xchg eax,ebx
    out dx,eax
    pop edx
    ret
; ebx - index
; return:
; eax - data
pcnet32_dwio_read_bcr:
    push edx
    lea edx,[ebp+PCNET32_DWIO_RAP]
    mov ebx,eax
    out dx,eax
    lea edx,[ebp+PCNET32_DWIO_BDP]
    in eax,dx
    and eax,0xffff
    pop edx
    ret
; ebx - index
; eax - data
pcnet32_dwio_write_bcr:
    push edx
    lea edx,[ebp+PCNET32_DWIO_RAP]
    xchg eax,ebx
    out dx,eax
    lea edx,[ebp+PCNET32_DWIO_BDP]
    xchg eax,ebx
    out dx,eax
    pop edx
    ret
pcnet32_dwio_read_rap:
    push edx
    lea edx,[ebp+PCNET32_DWIO_RAP]
    in eax,dx
    and eax,0xffff
    pop edx
    ret
; eax - val
pcnet32_dwio_write_rap:
    push edx
    lea edx,[ebp+PCNET32_DWIO_RAP]
    out dx,eax
    pop edx
    ret
pcnet32_dwio_reset:
    push edx
    push eax
    lea edx,[ebp+PCNET32_DWIO_RESET]
    in eax,dx
    pop eax
    pop edx
    ret
pcnet32_dwio_check:
    push edx
    lea edx,[PCNET32_DWIO_RAP]
    mov eax,88
    out dx,eax
    nop
    nop
    in eax,dx
    and eax,0xffff
    cmp eax,88
    sete al
    pop edx
    ret

iglobal
pcnet32_dwio:
    dd pcnet32_dwio_read_csr
    dd pcnet32_dwio_write_csr
    dd pcnet32_dwio_read_bcr
    dd pcnet32_dwio_write_bcr
    dd pcnet32_dwio_read_rap
    dd pcnet32_dwio_write_rap
    dd pcnet32_dwio_reset
endg

pcnet32_init_ring:
    mov [pcnet32_private.tx_full],0
    mov [pcnet32_private.cur_rx],0
    mov [pcnet32_private.cur_tx],0
    mov [pcnet32_private.dirty_rx],0
    mov [pcnet32_private.dirty_tx],0
    mov edi,pcnet32_rx_ring
    mov ecx,PCNET32_RX_RING_SIZE
    mov ebx,pcnet32_rxb
.rx_init:
    mov [edi+pcnet32_rx_head.base],ebx
    mov [edi+pcnet32_rx_head.buf_length],word PCNET32_PKT_BUF_SZ_NEG
    mov [edi+pcnet32_rx_head.status],word 0x8000
    add ebx,PCNET32_PKT_BUF_SZ
;    inc ebx
    add edi,16
    loop .rx_init
    mov edi,pcnet32_tx_ring
    mov ecx,PCNET32_TX_RING_SIZE
.tx_init:
    mov [edi+pcnet32_tx_head.base],dword 0
    mov [edi+pcnet32_tx_head.status],word 0
    add edi,16
    loop .tx_init
    mov [pcnet32_private.tlen_rlen],(PCNET32_TX_RING_LEN_BITS or PCNET32_RX_RING_LEN_BITS)
    mov esi,node_addr
    mov edi,pcnet32_private.phys_addr
    cld
    movsd
    movsw
    mov dword [pcnet32_private.rx_ring],pcnet32_rx_ring
    mov dword [pcnet32_private.tx_ring],pcnet32_tx_ring
    ret
pcnet32_reset:
    ; Reset PCNET32
    mov ebp,[io_addr]
    call dword [pcnet32_access.reset]
    ; set 32bit mode
    mov ebx,20
    mov eax,2
    call dword [pcnet32_access.write_bcr]
    ; set/reset autoselect bit
    mov ebx,2
    call dword [pcnet32_access.read_bcr]
    and eax,not 2
    test [pcnet32_private.options],PCNET32_PORT_ASEL
    jz .L1
    or eax,2
.L1:
    call dword [pcnet32_access.write_bcr]
    ; Handle full duplex setting
    cmp byte [pcnet32_private.full_duplex],0
    je .L2
    mov ebx,9
    call dword [pcnet32_access.read_bcr]
    and eax,not 3
    test [pcnet32_private.options],PCNET32_PORT_FD
    jz .L3
    or eax,1
    cmp [pcnet32_private.options],PCNET32_PORT_FD or PCNET32_PORT_AUI
    jne .L4
    or eax,2
    jmp .L4
.L3:
    test [pcnet32_private.options],PCNET32_PORT_ASEL
    jz .L4
    cmp [pcnet32_private.chip_version],0x2627
    jne .L4
    or eax,3
.L4:
    mov ebx,9
    call dword [pcnet32_access.write_bcr]
.L2:
    ; set/reset GPSI bit
    mov ebx,124
    call dword [pcnet32_access.read_csr]
    mov ecx,[pcnet32_private.options]
    and ecx,PCNET32_PORT_PORTSEL
    cmp ecx,PCNET32_PORT_GPSI
    jne .L5
    or eax,0x10
.L5:
    call dword [pcnet32_access.write_csr]
    cmp [pcnet32_private.mii],0
    je .L6
    test [pcnet32_private.options],PCNET32_PORT_ASEL
    jnz .L6
    mov ebx,32
    call dword [pcnet32_access.read_bcr]
    and eax,not 0x38
    test [pcnet32_private.options],PCNET32_PORT_FD
    jz .L7
    or eax,0x10
.L7:
    test [pcnet32_private.options],PCNET32_PORT_100
    jz .L8
    or eax,0x08
.L8:
    call dword [pcnet32_access.write_bcr]
    jmp .L9
.L6:
    test [pcnet32_private.options],PCNET32_PORT_ASEL
    jz .L9
    mov ebx,32
;    PutStr "ASEL, enable auto-negotiation"
    call dword [pcnet32_access.read_bcr]
    and eax,not 0x98
    or eax,0x20
    call dword [pcnet32_access.write_bcr]
.L9:
    cmp [pcnet32_private.ltint],0
    je .L10
    mov ebx,5
    call dword [pcnet32_access.read_csr]
    or eax,(1 shl 14)
    call dword [pcnet32_access.write_csr]
.L10:
    mov eax,[pcnet32_private.options]
    and eax,PCNET32_PORT_PORTSEL
    shl eax,7
    mov [pcnet32_private.mode],ax
    mov [pcnet32_private.filter],dword 0xffffffff
    mov [pcnet32_private.filter+4],dword 0xffffffff
    call pcnet32_init_ring
    mov ebx,1
    mov eax,pcnet32_private
    and eax,0xffff
    call dword [pcnet32_access.write_csr]
    mov eax,pcnet32_private
    mov ebx,2
    shr eax,16
    call dword [pcnet32_access.write_csr]
    mov ebx,4
    mov eax,0x0915
    call dword [pcnet32_access.write_csr]
    mov ebx,0
    mov eax,1
    call dword [pcnet32_access.write_csr]
    mov ecx,100
.L11:
    xor ebx,ebx
    call dword [pcnet32_access.read_csr]
    test ax,0x100
    jnz .L12
    loop .L11
.L12:
;    PutStr "hardware reset"
    xor ebx,ebx
    mov eax,0x0002
    call dword [pcnet32_access.write_csr]
    xor ebx,ebx
    call dword [pcnet32_access.read_csr]
;    PutStr "PCNET reset complete"
    ret
pcnet32_adjust_pci_device:
   ;*******Get current setting************************
   mov     al, 2                                        ;read a word
   mov     bh, [pci_dev]
   mov     ah, [pci_bus]
   mov     bl, 0x04                                 ;from command Register
   call    pci_read_reg
   ;******see if its already set as bus master********
   mov      bx, ax
   and      bx,5
   cmp      bx,5
   je       pcnet32_adjust_pci_device_Latency
   ;******Make card a bus master*******
   mov      cx, ax                              ;value to write
   mov     bh, [pci_dev]
   mov     al, 2                                ;write a word
   or       cx,5
   mov     ah, [pci_bus]
   mov     bl, 0x04                             ;to command register
   call    pci_write_reg
   ;******Check latency setting***********
pcnet32_adjust_pci_device_Latency:
   ;*******Get current latency setting************************
;   mov     al, 1                                       ;read a byte
;   mov     bh, [pci_dev]
;   mov     ah, [pci_bus]
;   mov     bl, 0x0D                                ;from Lantency Timer Register
;   call    pci_read_reg
   ;******see if its aat least 64 clocks********
;   cmp      ax,64
;   jge      pcnet32_adjust_pci_device_Done
   ;******Set latency to 32 clocks*******
;   mov     cx, 64                              ;value to write
;   mov     bh, [pci_dev]
;   mov     al, 1                               ;write a byte
;   mov     ah, [pci_bus]
;   mov     bl, 0x0D                            ;to Lantency Timer Register
;   call    pci_write_reg
   ;******Check latency setting***********
pcnet32_adjust_pci_device_Done:
   ret
pcnet32_probe:
    mov ebp,[io_addr]
    call pcnet32_wio_reset
    xor ebx,ebx
    call pcnet32_wio_read_csr
    cmp eax,4
    jne .try_dwio
    call pcnet32_wio_check
    and al,al
    jz .try_dwio
;    PutStr "Using WIO"
    mov esi,pcnet32_wio
    jmp .L1
.try_dwio:
    call pcnet32_dwio_reset
    xor ebx,ebx
    call pcnet32_dwio_read_csr
    cmp eax,4
    jne .no_dev
    call pcnet32_dwio_check
    and al,al
    jz .no_dev
;    PutStr "Using DWIO"
    mov esi,pcnet32_dwio
    jmp .L1
.no_dev:
;    PutStr "PCNET32 not found"
    ret
.L1:
    mov edi,pcnet32_access
    mov ecx,7
    cld
    rep movsd
    mov ebx,88
    call dword [pcnet32_access.read_csr]
    mov ecx,eax
    mov ebx,89
    call dword [pcnet32_access.read_csr]
    shl eax,16
    or eax,ecx
    mov ecx,eax
    and ecx,0xfff
    cmp ecx,3
    jne .no_dev
    shr eax,12
    and eax,0xffff
    mov [pcnet32_private.chip_version],eax
;    PutStr "PCNET32 chip version OK"
    mov [pcnet32_private.fdx],0
    mov [pcnet32_private.mii],0
    mov [pcnet32_private.fset],0
    mov [pcnet32_private.dxsuflo],0
    mov [pcnet32_private.ltint],0
    mov eax,[pcnet32_private.chip_version]
    cmp eax,0x2420
    je .L2
    cmp eax,0x2430
    je .L3
    cmp eax,0x2621
    je .L4
    cmp eax,0x2623
    je .L5
    cmp eax,0x2624
    je .L6
    cmp eax,0x2625
    je .L7
    cmp eax,0x2626
    je .L8
    cmp eax,0x2627
    je .L9
;    PutStr "Invalid chip rev"
    jmp .no_dev
.L2:
;    PutStr "PCnet/PCI 79C970"
    jmp .L10
.L3:
;    PutStr "PCnet/PCI 79C970"
    jmp .L10
.L4:
;    PutStr "PCnet/PCI II 79C970A"
    mov [pcnet32_private.fdx],1
    jmp .L10
.L5:
;    PutStr "PCnet/FAST 79C971"
    mov [pcnet32_private.fdx],1
    mov [pcnet32_private.mii],1
    mov [pcnet32_private.fset],1
    mov [pcnet32_private.ltint],1
    jmp .L10
.L6:
;    PutStr "PCnet/FAST+ 79C972"
    mov [pcnet32_private.fdx],1
    mov [pcnet32_private.mii],1
    mov [pcnet32_private.fset],1
    jmp .L10
.L7:
;    PutStr "PCnet/FAST III 79C973"
    mov [pcnet32_private.fdx],1
    mov [pcnet32_private.mii],1
    jmp .L10
.L8:
;    PutStr "PCnet/Home 79C978"
    mov [pcnet32_private.fdx],1
    mov ebx,49
    call dword [pcnet32_access.read_bcr]
    call dword [pcnet32_access.write_bcr]
    jmp .L10
.L9:
;    PutStr "PCnet/FAST III 79C975"
    mov [pcnet32_private.fdx],1
    mov [pcnet32_private.mii],1
.L10:
    cmp [pcnet32_private.fset],1
    jne .L11
    mov ebx,18
    call dword [pcnet32_access.read_bcr]
    or eax,0x800
    call dword [pcnet32_access.write_bcr]
    mov ebx,80
    call dword [pcnet32_access.read_csr]
    and eax,0xc00
    or eax,0xc00
    call dword [pcnet32_access.write_csr]
    mov [pcnet32_private.dxsuflo],1
    mov [pcnet32_private.ltint],1
.L11:
    ; read MAC
    mov edi,node_addr
    mov edx,ebp
    mov ecx,6
.Lmac:
    in al,dx
    stosb
    inc edx
    loop .Lmac
;    PutStr "MAC read"
    call pcnet32_adjust_pci_device
;    PutStr "PCI done"
    mov eax,PCNET32_PORT_ASEL
    mov [pcnet32_private.options],eax
    mov [pcnet32_private.mode],word 0x0003
    mov [pcnet32_private.tlen_rlen],word (PCNET32_TX_RING_LEN_BITS or PCNET32_RX_RING_LEN_BITS)
    mov esi,node_addr
    mov edi,pcnet32_private.phys_addr
    cld
    movsd
    movsw
    mov [pcnet32_private.filter],dword 0
    mov [pcnet32_private.filter+4],dword 0
    mov dword [pcnet32_private.rx_ring],pcnet32_rx_ring
    mov dword [pcnet32_private.tx_ring],pcnet32_tx_ring
;    PutStr "Switching to 32"
    mov ebx,20
    mov eax,2
    call dword [pcnet32_access.write_bcr]
    mov ebx,1
    mov eax,(pcnet32_private and 0xffff)
    call dword [pcnet32_access.write_csr]
    mov ebx,2
    mov eax,(pcnet32_private shr 16) and 0xffff
    call dword [pcnet32_access.write_csr]
    mov ebx,0
    mov eax,1
    call dword [pcnet32_access.write_csr]
    mov esi,1
    call delay_ms
    call pcnet32_reset
    mov eax, [pci_data]
    mov [eth_status], eax
    ret
pcnet32_poll:
    xor eax,eax
    mov [eth_rx_data_len],ax
    mov eax,[pcnet32_private.cur_rx]
    and eax,PCNET32_RX_RING_MOD_MASK
    mov ebx,eax
    imul esi,eax,PCNET32_PKT_BUF_SZ
    add esi,pcnet32_rxb
    shl ebx,4
    add ebx,pcnet32_rx_ring
    mov cx,[ebx+pcnet32_rx_head.status]
    test cx,0x8000
    jnz .L1
    cmp ch,3
    jne .L1
;    PutStr "PCNETRX"
    mov ecx,[ebx+pcnet32_rx_head.msg_length]
    and ecx,0xfff
    sub ecx,4
    mov [eth_rx_data_len],cx
    push ecx
    shr ecx,2
    mov edi,Ether_buffer
    cld
    rep movsd
    pop ecx
    and ecx,3
    rep movsb
    mov [ebx+pcnet32_rx_head.buf_length],word PCNET32_PKT_BUF_SZ_NEG
    or [ebx+pcnet32_rx_head.status],word 0x8000
    inc [pcnet32_private.cur_rx]
.L1:
    ret
;         Pointer to 48 bit destination address in edi
;         Type of packet in bx
;         size of packet in ecx
;         pointer to packet data in esi
pcnet32_xmit:
    push edi
    push esi
    push ebx
    push ecx
;    PutStr "PCNETTX"
    mov esi,edi
    mov edi,[pcnet32_private.cur_tx]
    imul edi,PCNET32_PKT_BUF_SZ
    add edi,pcnet32_txb ; edi=ptxb
    mov eax,edi
    cld         ; copy MAC
    movsd
    movsw
    mov esi,node_addr
    cld
    movsd
    movsw
    mov [edi],bx
    add edi,2
    mov esi,[esp+8]
    mov ecx,[esp]
    push ecx
    shr ecx,2
    cld
    rep movsd
    pop ecx
    and ecx,3
    rep movsb
;    mov ecx,[esp]
;    add ecx,14 ; ETH_HLEN
;    xor eax,eax
; pad to min length (60=ETH_ZLEN)
;    cmp ecx,60
;    jae .L1
;    sub ecx,60
;    cld
;    rep stosb
;.L1:
    mov edi,pcnet32_tx_ring+0   ; entry=0
    mov ecx,[esp]
    add ecx,14
    cmp cx,60
    jae .L1
    mov cx,60
.L1:
    neg cx
    mov [edi+pcnet32_tx_head.length],cx
    mov [edi+pcnet32_tx_head.misc],dword 0
    mov [edi+pcnet32_tx_head.base],eax
    mov [edi+pcnet32_tx_head.status],word 0x8300
    ; trigger an immediate send poll
    mov ebx,0
    mov eax,0x0008      ; 0x0048
    mov ebp,[io_addr]
    call dword [pcnet32_access.write_csr]
    mov dword [pcnet32_private.cur_tx],0
    ; wait for TX to complete
    mov ecx,[timer_ticks];[0xfdf0]
    add ecx,100
.L2:
    mov ax,[edi+pcnet32_tx_head.status]
    test ax,0x8000
    jz .L3
    cmp ecx,[timer_ticks];[0xfdf0]
    jb .L4
    mov esi,10
    call delay_ms
    jnz .L2
.L4:
;    PutStr "PCNET: Send timeout"
.L3:
    mov dword [edi+pcnet32_tx_head.base],0
    pop ecx
    pop ebx
    pop esi
    pop edi
    ret
