;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                                 ;;
;;  ETHERNET.INC                                                   ;;
;;                                                                 ;;
;;  Ethernet network layer for Menuet OS                           ;;
;;                                                                 ;;
;;  Version 0.4  22 September 2003                                 ;;
;;                                                                 ;;
;;  This file contains the following:                              ;;
;;      PCI bus scanning for valid devices                         ;;
;;      Table of supported ethernet drivers                        ;;
;;      Code to identify and activate a supported driver           ;;
;;      ARP handler                                                ;;
;;      Driver interface to the IP layer                           ;;
;;      Gateway support                                            ;;
;;                                                                 ;;
;;  Individual driver files are included here                      ;;
;;                                                                 ;;
;;  The PCI bus scanning code was ported from the etherboot        ;;
;;  5.0.6 project. The copyright statement for that code is        ;;
;;                                                                 ;;
;;          GNU GENERAL PUBLIC LICENSE                             ;;
;;             Version 2, June 1991                                ;;
;;                                                                 ;;
;;  remaining parts Copyright 2002 Mike Hibbett                    ;;
;;   mikeh@oceanfree.net                                           ;;
;;                                                                 ;;
;;  See file COPYING for details                                   ;;
;;                                                                 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;********************************************************************
;   Interface
;      ethernet_driver   called by stack_handler in stack.inc
;      eth_probe         called by app_stack_handler in stack.inc
;
;********************************************************************

; Some useful information on data structures

;     Ethernet Packet - ARP Request example
;
;   0                   1                   2                   3
;   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
;
;   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;   |       Dest   H/W Address                                      |
;   |                    ( 14 byte header )                         |
;   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;   |                               |     Source     H/W Address    |
;   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;   |                                                               |
;   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;   |    Protocol - ARP 08  06      |
;   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

;   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;   |  H/W Type  00           01    |  Protocol Type   08 00        |
;   |                   ( ARP Request packet )                      |
;   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;   | HLen    0x06  | PLen    0x04  |    OpCode        00   01      |
;   |               ( 0001 for request, 0002 for reply )            |
;   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;   | Source Hardware Address ( MAC Address )                       |
;   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;   |                               |  Source IP Address            |
;   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;   |                               | Destination Hardware Address  |
;   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;   |                                                               |
;   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
;   | Destination IP Address                                        |
;   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

; Include individual drivers source files at this point.
; If you create a new driver, include it below.

include "rtl8029.inc"
include "i8255x.inc"
include "rtl8139.inc"
include "3c59x.inc"
include "sis900.inc"
include "pcnet32.inc"

; DEBUGGING_STATE enables or disables output of received and transmitted
; data over the serial port
DEBUGGING_ENABLED           equ     1
DEBUGGING_DISABLED          equ     0
DEBUGGING_STATE             equ     DEBUGGING_DISABLED

; PCICards
; ========
; PCI vendor and hardware types for hardware supported by the above drivers
; If you add a driver, ensure you update this datastructure, otherwise the
; card will not be probed.
; Each driver is defined by 4 double words. These are
;   PCIVendorDevice  probeFunction ResetFunction PollFunction transmitFunction
; The last entry must be kept at all zeros, to indicate the end of the list
; As a PCI driver may support more than one hardware implementation, there may
; be several lines which refer to the same functions.
; The first driver found on the PCI bus will be the one used.

PCICARDS_ENTRY_SIZE         equ     20    ; Size of each PCICARDS entry

iglobal
PCICards:
dd  0x12098086, I8255x_probe, I8255x_reset, I8255x_poll, I8255x_transmit
dd  0x10298086, I8255x_probe, I8255x_reset, I8255x_poll, I8255x_transmit
dd  0x12298086, I8255x_probe, I8255x_reset, I8255x_poll, I8255x_transmit
dd  0x10308086, I8255x_probe, I8255x_reset, I8255x_poll, I8255x_transmit
dd  0x24498086, I8255x_probe, I8255x_reset, I8255x_poll, I8255x_transmit
dd  0x802910ec, rtl8029_probe, rtl8029_reset, rtl8029_poll, rtl8029_transmit
dd  0x12111113, rtl8029_probe, rtl8029_reset, rtl8029_poll, rtl8029_transmit
dd  0x813910ec, rtl8139_probe, rtl8139_reset, rtl8139_poll, rtl8139_transmit
dd  0x590010b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x592010b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x597010b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x595010b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x595110b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x595210b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x900010b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x900110b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x900410b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x900510b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x900610b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x900A10b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x905010b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x905110b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x905510b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x905810b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x905A10b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x920010b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x980010b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x980510b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x764610b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x505510b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x605510b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x605610b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x5b5710b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x505710b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x515710b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x525710b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x656010b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x656210b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x656410b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x450010b7, e3c59x_probe, e3c59x_reset, e3c59x_poll, e3c59x_transmit
dd  0x09001039, SIS900_probe, SIS900_reset, SIS900_poll, SIS900_transmit
dd  0x20001022, pcnet32_probe, pcnet32_reset, pcnet32_poll, pcnet32_xmit
dd  0x26251022, pcnet32_probe, pcnet32_reset, pcnet32_poll, pcnet32_xmit
dd  0x20011022, pcnet32_probe, pcnet32_reset, pcnet32_poll, pcnet32_xmit
; following card is untested
dd  0x70161039, SIS900_probe, SIS900_reset, SIS900_poll, SIS900_transmit
dd  0,0,0,0,0  ; end of list marker, do not remove
endg

; PCI Bus defines
PCI_HEADER_TYPE             equ     0x0e  ;8 bit
PCI_BASE_ADDRESS_0          equ     0x10  ;32 bit
PCI_BASE_ADDRESS_5          equ     0x24  ;32 bits
PCI_BASE_ADDRESS_SPACE_IO   equ     0x01
PCI_VENDOR_ID               equ     0x00  ;16 bit
PCI_BASE_ADDRESS_IO_MASK    equ     0xFFFFFFFC

ETHER_IP                    equ     0x0008      ; Reversed from 0800 for intel
ETHER_ARP                   equ     0x0608      ; Reversed from 0806 for intel
ETHER_RARP                  equ     0x3580
ARP_REQ_OPCODE              equ     0x0100
ARP_REP_OPCODE              equ     0x0200

uglobal
  arp_rx_count:       dd  0
  ip_rx_count:        dd  0
  dumped_rx_count:    dd  0
  ip_tx_count:        dd  0
  node_addr:          db  0,0,0,0,0,0
  eth_rx_data_len:    dw  0
  eth_status:         dd  0
  io_addr:            dd  0
  hdrtype:            db  0
  vendor_device:      dd  0
  pci_data:           dd  0
  pci_dev:            dd  0
  pci_bus:            dd  0

  ; These will hold pointers to the selected driver functions
  drvr_probe:         dd  0
  drvr_reset:         dd  0
  drvr_poll:          dd  0
  drvr_transmit:      dd  0

  ; These hold the destination Host identity for ARP responses
  remote_ip_add:      dd  0
  remote_hw_add:      db  0, 0, 0, 0, 0, 0
endg

iglobal
  broadcast_add:      db  0xff,0xff,0xff,0xff,0xff,0xff
  subnet_mask:        dd  0x00ffffff
endg

uglobal
  ; This is used by getMACfromIP
  MACAddress:         db  0,0,0,0,0,0
  gateway_ip:         db  0, 0, 0, 0
  dns_ip:             dd  0
endg

; The follow is the ARP Table.
; This table must be manually updated and the kernel recompilied if
; changes are made to it.
; ARP_TABLE_SIZE defines the size of the table
; ARP_TABLE_ENTRIES defines the number of entries in the table
; Each entry is 10 bytes: 4 Byte IP address, 6 byte MAC Address,
;                         2 bytes status, 2 bytes TTL ( in seconds )
; Empty entries are filled with zeros
; The TTL field is decremented every second, and is deleted when it
; reaches 0. It is refreshed every time a packet is received
; If the TTL field is 0xFFFF it is a permanent entry and is never deleted
; The status field can be the following values
; 0x0000  entry not used
; 0x0001  entry holds a valid mapping
; 0x0002  entry contains an IP address, awaiting ARP response
; 0x0003  No response received to ARP request.
; The last status value is provided to allow the network layer to delete
; a packet that is queued awaiting an ARP response

ARP_NO_ENTRY                equ 0
ARP_VALID_MAPPING           equ 1
ARP_AWAITING_RESPONSE       equ 2
ARP_RESPONSE_TIMEOUT        equ 3

ARP_ENTRY_SIZE              equ     14          ; Number of bytes per entry
ARP_TABLE_SIZE              equ     20          ; Size of table
ARP_TABLE_ENTRIES           equ     0           ; Inital, hardcoded entries

uglobal
  ARPTable:
  times ( ARP_TABLE_SIZE - ARP_TABLE_ENTRIES ) * ARP_ENTRY_SIZE  db 0
endg

iglobal
  NumARP:        db    ARP_TABLE_ENTRIES
endg

;***************************************************************************
;   Function
;      eth_probe
;   Description
;      Searches for an ethernet card. If found, the card is enabled and
;      the ethernet -> IP link established
;
;      This function scans the PCI bus looking for a supported device.
;      ISA bus is currently not supported.
;
;        eax is 0 if no hardware found
;***************************************************************************
eth_probe:
    ; Find a card on the PCI bus, and get it's address
    call    scan_bus                    ; Find the ethernet cards PIC address
    xor     eax, eax
    cmp     [io_addr], eax
    je      ep_00x                      ; Return 0 in eax if no cards found

    call    dword [drvr_probe]          ; Call the drivers probe function

    mov     eax, [io_addr]              ; return a non zero value

ep_00x:
    ret

;***************************************************************************
;   Function
;      ethernet_driver
;
;   Description
;       The ethernet RX and TX handler
;       This is a kernel function, called by stack_handler
;
;***************************************************************************
ethernet_driver:
    ; Do nothing if the driver is inactive
    cmp     [ethernet_active], byte 0
    je      eth_exit

    call    eth_rx
    call    eth_tx

eth_exit:
    ret

;***************************************************************************
;   Function
;      eth_rx
;
;   Description
;      Polls the ethernet card for received data. Extracts if present
;       Depending on the Protocol within the packet:
;         ARP : Pass to ARP_handler. This may result in an ARP reply
;               being tx'ed
;         IP  : Store in an IP buffer
;
;***************************************************************************
eth_rx:
    xor     ax, ax
    mov     [eth_rx_data_len], ax
    call    dword [drvr_poll]       ; Call the drivers poll function

    mov     ax, [eth_rx_data_len]
    cmp     ax, 0
    je      erx_exit

if DEBUGGING_STATE = DEBUGGING_ENABLED
    pusha
    mov     eax, 0                    ;Indicate that this is a received packet
    mov     cx, [eth_rx_data_len]
    mov     esi, Ether_buffer
    cmp     word [esi + 12], ETHER_IP
    jnz     erxd_done
;    cmp     byte [esi + 14 + 9], 0x06  ; TCP
;    jnz     erxd_done
    call    eth_dump
erxd_done:
    popa
end if

    ; Check the protocol. Call appropriate handler
    mov     eax, Ether_buffer
    add     eax, 12                  ; The address of the protocol word

    mov     ax, [eax]

    cmp     ax, ETHER_ARP
    je      erx_001                  ; It is ARP

    cmp     ax, ETHER_IP
    je      erx_002                  ; It's IP

;    inc     dword [dumped_rx_count]

    jmp     erx_exit               ; If not IP or ARP, ignore

erx_001:
    mov     eax, [arp_rx_count]
    inc     eax
    mov     [arp_rx_count], eax

    ; At this point, the packet is still in the Ether_buffer
    call    arp_handler

    jmp     erx_exit

erx_002:
    mov     eax, [ip_rx_count]
    inc     eax
    mov     [ip_rx_count], eax

    ; Check to see if the MAC address is in our arp table
    ; refresh the arp ttl if so

    mov     esi, Ether_buffer
    add     esi, 6

    call    refreshARP

    call    ether_IP_handler

    jmp     erx_exit

erx_exit:
    ret

;***************************************************************************
;   Function
;      eth_tx
;
;   Description
;      Looks at the NET1OUT_QUEUE for data to send.
;      Stores that destination IP in a location used by the tx routine
;      Looks up the MAC address in the ARP table; stores that where
;      the tx routine can get it
;      Get the length of the data. Store that where the tx routine wants it
;      Call tx
;      Places buffer on empty queue when the tx routine finished
;
;***************************************************************************
eth_tx:
    ; Look for a buffer to tx
    mov     eax, NET1OUT_QUEUE
    call    dequeue
    cmp     ax, NO_BUFFER
    je      eth_exit            ; Exit if no buffer available

    push    eax

    ; convert buffer pointer eax to the absolute address
    mov     ecx, IPBUFFSIZE
    mul     ecx
    add     eax, IPbuffs

    ; Extract the destination IP
    ; find the destination IP in the ARP table, get MAC
    ; store this MAC in 'MACAddress'
    mov     ebx, eax               ; Save buffer address
    mov     edx, [ebx + 16]        ; get destination address

    ; If the destination address is 255.255.255.255,
    ; set the MACAddress to all ones ( broadcast )
    mov     [MACAddress], dword 0xffffffff
    mov     [MACAddress + 4], word 0xffff
    cmp     edx, 0xffffffff
    je      etx_send                ; If it is broadcast, just send

    call    getMACfromIP           ; Get the MAC address.

    cmp     eax, ARP_VALID_MAPPING
    jz      etx_send

    ; No valid entry. Are we waiting for a response?
    cmp     eax, ARP_AWAITING_RESPONSE
    jne     etx_001

    ; Re-queue the packet, and exit
    pop     ebx
    mov     eax, NET1OUT_QUEUE
    call    queue
    jmp     etx_exit

etx_001:
    ; HAs the request been sent, but timed out?
    cmp     eax, ARP_RESPONSE_TIMEOUT
    jne     etx_002

    pop     eax
    call    freeBuff
    jmp     etx_exit

etx_002:
    ; There is no entry. Re queue the request, and ask ARP to send a request

    ; IP address is in edx
    push    edx
    call    arp_request
    pop     ebx

    ; Add an entry in the ARP table, awaiting response

    cmp     byte [NumARP], ARP_TABLE_SIZE
    je      etx_003            ; We cannot add a new entry in the table

    inc     byte [NumARP]

    movzx   eax, byte [NumARP]
    mov     ecx, ARP_ENTRY_SIZE
    mul     ecx
    sub     eax, ARP_ENTRY_SIZE

    mov     [eax + ARPTable], ebx
    xor     ebx, ebx
    mov     [eax + ARPTable + 4], ebx
    mov     [eax + ARPTable + 8], bx

    ; set the status field up - awaiting response
    mov     cl, 0x00
    mov     [eax + ARPTable + 10], cl
    mov     cl, 0x02
    mov     [eax + ARPTable + 11], cl

    ; Initialise the time to live field - 10s
    mov     cx, 0x000A
    mov     [eax + ARPTable + 12], cx

etx_003:
    pop     ebx                        ; Get the buffer back
    mov     eax, NET1OUT_QUEUE
    call    queue
    jmp     etx_exit

etx_send:
    xor     ecx, ecx
    mov     ch, [ebx+2]
    mov     cl, [ebx+3]          ; ; Size of IP packet to send

    mov     esi, ebx

    mov     edi, MACAddress

if DEBUGGING_STATE = DEBUGGING_ENABLED
    pusha
    mov     cx, 42
    mov     eax, 1                    ; Indicate that this is a tx packet
    call    eth_dump
    popa
end if

    mov     bx, ETHER_IP
    call    dword [drvr_transmit]       ; Call the drivers transmit function

    ; OK, we have sent a packet, so increment the count
    inc     dword [ip_tx_count]

    ; And finally, return the buffer to the free queue
    pop     eax
    call    freeBuff

etx_exit:
    ret

;***************************************************************************
;   Function
;      ether_IP_handler
;
;   Description
;      Called when an IP ethernet packet is received on the ethernet
;      Header + Data is in Ether_buffer[]
;      We just need to get a buffer from the 'free' queue, and
;      store the packet in it, then insert the packet number into the
;      IPRX queue.
;      If no queue entry is available, the packet is silently discarded
;      All registers may be destroyed
;
;***************************************************************************
ether_IP_handler:
    mov     eax, EMPTY_QUEUE
    call    dequeue
    cmp     ax, NO_BUFFER
    je      eiph00x

    ; convert buffer pointer eax to the absolute address
    push    eax
    mov     ecx, IPBUFFSIZE
    mul     ecx
    add     eax, IPbuffs

    mov     edi, eax

    ; get a pointer to the start of the DATA
    mov     esi, Ether_buffer + 14

    ; Now store it all away
    mov     ecx, IPBUFFSIZE / 4     ; Copy all of the available
                              ; data across - worse case
    cld
    rep     movsd

    ; And finally, place the buffer in the IPRX queue
    pop     ebx
    mov     eax, IPIN_QUEUE
    call    queue

eiph00x:
    ret

;***************************************************************************
;
;  ARP CODE FOLLOWS
;
;  The ARP code is used by ethernet drivers to translate an destination
;  IP address into an ethernet hardware address. Functions to broadcast
;  requests and handle response are (or will be) here.
;  The IP layer has no knowledge of ARP, as this is a network interface
;  issue
;
;***************************************************************************

;***************************************************************************
;   Function
;      arp_timer
;
;   Description
;      Called every 1s
;      It is responsible for removing expired routes
;      All registers may be destroyed
;
;***************************************************************************
arp_timer:
    ; loop through all the ARP entries, decrementing each one
    ; that doesn't have a TTL of 0xFFFF
    movzx   eax, byte [NumARP]

arp_001:
    cmp     eax, 0
    je      arp_003

    push    eax
    dec     eax
    mov     ecx, ARP_ENTRY_SIZE
    mul     ecx
    cmp     word [ eax + ARPTable + 12], 0xFFFF
    je      arp_002

    cmp     word [ eax + ARPTable + 12], 0
    je      arp_002

    dec     word [eax + ARPTable + 12]

arp_002:
    pop     eax
    dec     eax
    jmp     arp_001

    ; Now, look for entries with a TTL of 0
    ; Valid entries and response timeout entries get removed
    ; awaiting response gets converted into a response timeout, with a
    ; short life time - this allows queued packets to be flushed
arp_003:
    movzx   edx, byte [NumARP]
    cmp     edx, 0
    je      arp_exit

    ; EDX holds the # of entries to search through
    mov     eax, 0

arp_005:
    cmp     word [ eax + ARPTable + 12], 0
    jne     arp_004

    ; If it's status code is 0001 or 0003, delete the entry
    cmp     word [eax + ARPTable + 10], 0x0100
    je      arp_007
    cmp     word [eax + ARPTable + 10], 0x0300
    je      arp_007

    ; The only other valid code is 0002 - indicating a
    ; timeout while waiting for a response. Change the
    ; entry to response timed out

    mov     [eax + ARPTable + 10], word 0x0300
    mov     [eax + ARPTable + 12], word 0x000A
    jmp     arp_004

arp_007:
    ; Delete this entry
    mov     edi, ARPTable
    add     edi, eax
    mov     esi, edi
    add     esi, ARP_ENTRY_SIZE

    mov     ecx, (ARP_TABLE_SIZE - 1) * ARP_ENTRY_SIZE
    sub     ecx, eax

    rep     movsb

    dec     byte [NumARP]
    jmp     arp_006

arp_004:
    add     eax, ARP_ENTRY_SIZE
arp_006:
    dec     edx
    cmp     edx, 0
    jne     arp_005

arp_exit:
    ret

;***************************************************************************
;   Function
;      arp_request
;
;   Description
;      Sends an ARP request on the ethernet
;        The requested IP address is in edx
;      All registers may be destroyed
;
;***************************************************************************
arp_request:
    mov     ebx, Ether_buffer
    mov     ax, 0x0100
    mov     [ebx], ax
    add     ebx, 2

    mov     ax, 0x0008
    mov     [ebx], ax
    add     ebx, 2

    mov     ax, 0x0406
    mov     [ebx], ax
    add     ebx, 2

    mov     ax, 0x0100
    mov     [ebx], ax
    add     ebx, 2

    mov     ecx, node_addr
    mov     eax, [ecx]
    mov     [ebx], eax
    add     ecx, 4
    add     ebx, 4
    mov     ax, [ecx]
    mov     [ebx], ax
    add     ebx, 2
    mov     eax, [stack_ip]
    mov     [ebx], eax
    add     ebx, 4

    xor     eax, eax
    mov     [ebx], eax
    add     ebx, 4
    mov     [ebx], ax

    add     ebx, 2
    mov     [ebx], edx

    ; Now, send it!

    ; Pointer to 48 bit destination address in edi
    ; Type of packet in bx
    ; size of packet in ecx
    ; pointer to packet data in esi
    mov      edi, broadcast_add

;if DEBUGGING_STATE = DEBUGGING_ENABLED
;    pusha
;    mov     eax, 1                    ; Indicate that this is a tx packet
;    mov     ecx, 28
;   mov      esi, Ether_buffer
;    call    eth_dump
;    popa
;end if

    mov     bx, ETHER_ARP
    mov     ecx, 28
    mov     esi, Ether_buffer
    call    dword [drvr_transmit]       ; Call the drivers transmit function
    ret

;***************************************************************************
;   Function
;      arp_handler
;
;   Description
;      Called when an ARP packet is received on the ethernet
;      Header + Data is in Ether_buffer[]
;       It looks to see if the packet is a request to resolve this Hosts
;       IP address. If it is, send the ARP reply packet.
;      This Hosts IP address is in dword [stack_ip]  ( in network format )
;       This Hosts MAC address is in node_addr[6]
;      All registers may be destroyed
;
;***************************************************************************
arp_handler:
    ; Is this a REQUEST?
    ; Is this a request for My Host IP
    ; Yes - So construct a response message.
    ; Send this message to the ethernet card for transmission

    mov     ebx, Ether_buffer

    mov     edx, ebx
    add     edx, 20
    mov     ax, [edx]
    cmp     ax, ARP_REQ_OPCODE      ; Is this a request packet?
    jne     arph_resp            ; No - so test for response

    mov     edx, ebx
    add     edx, 38
    mov     eax, [edx]

    cmp     eax, [stack_ip]         ; Is it looking for my IP address?
    jne     arph_exit            ; No - so quit now

    ; OK, it is a request for my MAC address. Build the frame and send it

    ; Save the important data from the original packet
    ; remote MAC address first
    mov     ecx, remote_hw_add
    mov     edx, ebx
    add     edx, 22               ; edx points to Source h/w address
    mov     eax, [edx]
    mov     [ecx], eax
    add     edx, 4
    add     ecx, 4
    mov     ax, [edx]
    mov     [ecx],ax

    ; and also the remote IP address
    add     edx, 2
    mov     eax,[edx]
    mov     [remote_ip_add], eax

    ; So now we can reuse the packet. ebx still holds the address of
    ; the header + packet
    ; We dont need the header ( first 14 bytes )

    mov     edx, ebx
    add     edx, 20
    mov     ax, ARP_REP_OPCODE
    mov     [edx], ax
    add     edx, 2

    mov     ecx, node_addr
    mov     eax, [ecx]
    mov     [edx], eax
    add     ecx, 4
    add     edx, 4
    mov     ax, [ecx]
    mov     [edx], ax
    add     edx, 2
    mov     eax, [stack_ip]
    mov     [edx], eax
    add     edx, 4
    mov     ecx, remote_hw_add
    mov     eax, [ecx]
    mov     [edx], eax
    add     ecx, 4
    add     edx, 4
    mov     ax, [ecx]
    mov     [edx], ax

    add     edx, 2
    mov     eax, [remote_ip_add]
    mov     [edx], eax

    ; Now, send it!

    ; Pointer to 48 bit destination address in edi
    ; Type of packet in bx
    ; size of packet in ecx
    ; pointer to packet data in esi
    mov     edi, remote_hw_add

;if DEBUGGING_STATE = DEBUGGING_ENABLED
;    pusha
;    mov     eax, 1                    ; Indicate that this is a tx packet
;    mov     ecx, 28
;   mov      esi, Ether_buffer + 14
 ;   call    eth_dump
;    popa
;end if

    mov     bx, ETHER_ARP
    mov     ecx, 28
    mov     esi, Ether_buffer + 14
    call    dword [drvr_transmit]       ; Call the drivers transmit function
    jmp     arph_exit

arph_resp:
    cmp     ax, ARP_REP_OPCODE      ; Is this a replypacket?
    jne     arph_resp            ; No - so quit

    ; This was a reply, probably directed at me.
    ; save the remotes MAC & IP
    mov     ecx, remote_hw_add
    mov     edx, ebx
    add     edx, 22               ; edx points to Source h/w address
    mov     eax, [edx]
    mov     [ecx], eax
    add     edx, 4
    add     ecx, 4
    mov     ax, [edx]
    mov     [ecx],ax

    ; and also the remote IP address
    add     edx, 2
    mov     eax,[edx]
    mov     [remote_ip_add], eax

    ; Now, add an entry in the table for this IP address if it doesn't exist

    push    eax
    movzx   eax, byte [NumARP]
    mov     ecx, ARP_ENTRY_SIZE
    mul     ecx
    pop     edx
    movzx   ecx, byte [NumARP]
    cmp     ecx, 0
    je      arph_002

arph_001:
    sub     eax, ARP_ENTRY_SIZE
    cmp     [eax + ARPTable], edx
    loopnz  arph_001                      ; Return back if non match

    jnz     arph_002                   ; None found, add to end

    mov     ecx, [remote_hw_add]
    mov     [eax + ARPTable + 4], ecx
    mov     cx, [remote_hw_add+4]
    mov     [eax + ARPTable + 8], cx

    ; specify the type - a valid entry
    mov     cl, 0x00
    mov     [eax + ARPTable + 10], cl
    mov     cl, 0x01
    mov     [eax + ARPTable + 11], cl

    ; Initialise the time to live field - 1 hour
    mov     cx, 0x0E10
    mov     [eax + ARPTable + 12], cx
    jmp     arph_exit

arph_002:

    cmp     byte [NumARP], ARP_TABLE_SIZE
    je      arph_exit

    inc     byte [NumARP]

    movzx   eax, byte [NumARP]
    mov     ecx, ARP_ENTRY_SIZE
    mul     ecx
    sub     eax, ARP_ENTRY_SIZE

    mov     ecx, [remote_ip_add]
    mov     [eax + ARPTable], ecx
    mov     ecx, [remote_hw_add]
    mov     [eax + ARPTable + 4], ecx
    mov     cx, [remote_hw_add+4]
    mov     [eax + ARPTable + 8], cx

    mov     cl, 0x00
    mov     [eax + ARPTable + 10], cl
    mov     cl, 0x01
    mov     [eax + ARPTable + 11], cl

    ; Initialise the time to live field - 1 hour
    mov     cx, 0x0E10
    mov     [eax + ARPTable + 12], cx

arph_exit:
    ret

; pointer to MAC in esi
refreshARP:
    mov     ebx, [esi]
    mov     dx, [esi+4]
    push    edx
    movzx   eax, byte [NumARP]
    mov     ecx, ARP_ENTRY_SIZE
    mul     ecx
    pop     edx
    movzx   ecx, byte [NumARP]
    cmp     ecx, 0
    je      rf_exit

rf_001:
    sub     eax, ARP_ENTRY_SIZE
    cmp     [eax + ARPTable+4], ebx

    je      rf_002
    loop    rf_001
    jmp     rf_exit

rf_002:
    cmp     [eax + ARPTable+8], dx
    je      rf_gotone
    loop    rf_001
    jmp     rf_exit

rf_gotone:
    ; Initialise the time to live field - 1 hour
    mov     cx, 0x0E10
    mov     [eax + ARPTable + 12], cx

rf_exit:
    ret

;***************************************************************************
;   Function
;      getMACfromIP
;
;   Description
;       Takes an IP address in edx and scans the ARP table for
;        a matching entry
;       If a match is found, it's MAC address is stored in MACAddress.
;      Otherwise the value 0 is writen to MACAddress
;      eax holds ARP table entry status code ( ARP_ )
;      ebx unchanged
;
;***************************************************************************
getMACfromIP:
    ; first, check destination IP to see if it is on 'this' network.
    ; The test is:
    ; if ( destIP & subnet_mask == stack_ip & subnet_mask )
    ;   desitnation is local
    ; else
    ;  destination is remote, so pass to gateway

    mov     eax, edx
    and     eax, [subnet_mask]
    mov     ecx, [stack_ip]
    and     ecx, [subnet_mask]
    cmp     eax, ecx
    je      gm0

    mov     edx, [gateway_ip]
gm0:
    push    edx
    xor     eax, eax
    mov     [MACAddress], eax
    mov     [MACAddress + 4], ax

    movzx   eax, byte [NumARP]
    mov     ecx, ARP_ENTRY_SIZE
    mul     ecx

    pop     edx

    movzx   ecx, byte [NumARP]
    cmp     ecx, 0
    je      gm_none
gm1:
    sub     eax, ARP_ENTRY_SIZE
    cmp     [eax + ARPTable], edx
    loopnz  gm1                      ; Return back if non match
    jnz     gm_none                   ; Quit if none found

    ; eax holds index
    mov     ecx, [eax + ARPTable + 4]
    mov     [MACAddress], ecx
    mov     cx, [eax + ARPTable + 8]
    mov     [MACAddress+4], cx

    ; Return the entry status in eax
    mov     ch, [eax + ARPTable + 10]
    mov     cl, [eax + ARPTable + 11]
    movzx   eax, cx
    jmp     gm_exit

gm_none:
    mov     eax, ARP_NO_ENTRY

gm_exit:
    ret

;***************************************************************************
;
;  PCI CODE FOLLOWS
;
;  the following functions provide access to the PCI interface.
;  These functions are used by scan_bus, and also some ethernet drivers
;
;***************************************************************************

;***************************************************************************
;   Function
;      config_cmd
;
;   Description
;       creates a command dword  for use with the PCI bus
;       bus # in ebx
;      devfn in ecx
;       where in edx
;
;      command dword returned in eax
;       Only eax destroyed
;***************************************************************************
config_cmd:
    push    ecx
    mov     eax, ebx
    shl     eax, 16
    or      eax, 0x80000000
    shl     ecx, 8
    or      eax, ecx
    pop     ecx
    or      eax, edx
    and     eax, 0xFFFFFFFC
    ret

;***************************************************************************
;   Function
;      pcibios_read_config_byte
;
;   Description
;       reads a byte from the PCI config space
;       bus # in ebx
;      devfn in ecx
;       where in edx ( ls 16 bits significant )
;
;      byte returned in al ( rest of eax zero )
;       Only eax/edx destroyed
;***************************************************************************
pcibios_read_config_byte:
    call    config_cmd
    push    dx
    mov     dx, 0xCF8
    out     dx, eax
    pop     dx

    xor     eax, eax
    and     dx, 0x03
    add     dx, 0xCFC
;   and     dx, 0xFFC
    in      al, dx
    ret

;***************************************************************************
;   Function
;      pcibios_read_config_word
;
;   Description
;       reads a word from the PCI config space
;       bus # in ebx
;      devfn in ecx
;       where in edx ( ls 16 bits significant )
;
;      word returned in ax ( rest of eax zero )
;       Only eax/edx destroyed
;***************************************************************************
pcibios_read_config_word:
    call    config_cmd
    push    dx
    mov     dx, 0xCF8
    out     dx, eax
    pop     dx

    xor     eax, eax
    and     dx, 0x02
    add     dx, 0xCFC
;   and     dx, 0xFFC
    in      ax, dx
    ret

;***************************************************************************
;   Function
;      pcibios_read_config_dword
;
;   Description
;       reads a dword from the PCI config space
;       bus # in ebx
;      devfn in ecx
;       where in edx ( ls 16 bits significant )
;
;      dword returned in eax
;       Only eax/edx destroyed
;***************************************************************************
pcibios_read_config_dword:
    push    edx
    call    config_cmd
    push    dx
    mov     dx, 0xCF8
    out     dx, eax
    pop     dx
    xor     eax, eax
    mov     dx, 0xCFC
    in      eax, dx
    pop     edx
    ret

;***************************************************************************
;   Function
;      pcibios_write_config_byte
;
;   Description
;       write a byte in al to the PCI config space
;       bus # in ebx
;      devfn in ecx
;       where in edx ( ls 16 bits significant )
;
;       Only eax/edx destroyed
;***************************************************************************
pcibios_write_config_byte:
    push    ax
    call    config_cmd
    push    dx
    mov     dx, 0xCF8
    out     dx, eax
    pop     dx
    pop     ax

    and     dx, 0x03
    add     dx, 0xCFC
    out     dx, al
    ret

;***************************************************************************
;   Function
;      pcibios_write_config_word
;
;   Description
;       write a word in ax to the PCI config space
;       bus # in ebx
;      devfn in ecx
;       where in edx ( ls 16 bits significant )
;
;       Only eax/edx destroyed
;***************************************************************************
pcibios_write_config_word:
    push    ax
    call    config_cmd
    push    dx
    mov     dx, 0xCF8
    out     dx, eax
    pop     dx
    pop     ax

    and     dx, 0x02
    add     dx, 0xCFC
    out     dx, ax
    ret

;***************************************************************************
;   Function
;      delay_us
;
;   Description
;       delays for 30 to 60 us
;
;        I would prefer this routine to be able to delay for
;       a selectable number of microseconds, but this works for now.
;
;       If you know a better way to do 2us delay, pleae tell me!
;***************************************************************************
delay_us:
    push    eax
    push    ecx

    mov     ecx,2

    in      al,0x61
    and     al,0x10
    mov     ah,al
    cld

dcnt1:
    in      al,0x61
    and     al,0x10
    cmp     al,ah
    jz      dcnt1

    mov     ah,al
    loop    dcnt1

    pop     ecx
    pop     eax

    ret

;***************************************************************************
;   Function
;      scan_bus
;
;   Description
;       Scans the PCI bus for a supported device
;        If a supported device is found, the drvr_ variables are initialised
;       to that drivers functions ( as defined in the PCICards table)
;
;        io_addr   holds card I/O space. 32 bit, but only LS 16 bits valid
;        pci_data  holds the PCI vendor + device code
;       pci_dev   holds PCI bus dev #
;       pci_bus   holds PCI bus #
;
;        io_addr will be zero if no card found
;
;***************************************************************************
scan_bus:
    xor     eax, eax
    mov     [hdrtype], al
    mov     [pci_data], eax

    xor     ebx, ebx         ; ebx = bus# 0 .. 255

sb_bus_loop:
    xor     ecx, ecx         ; ecx = devfn# 0 .. 254  ( not 255? )

sb_devf_loop:
    mov     eax, ecx
    and     eax, 0x07

    cmp     eax, 0
    jne     sb_001

    mov     edx, PCI_HEADER_TYPE
    call    pcibios_read_config_byte
    mov     [hdrtype], al
    jmp     sb_002

sb_001:
    mov     al, [hdrtype]
    and     al, 0x80
    cmp     al, 0x80
    jne     sb_inc_devf

sb_002:
    mov     edx, PCI_VENDOR_ID
    call    pcibios_read_config_dword
    mov     [vendor_device], eax
    cmp     eax, 0xffffffff
    je      sb_empty
    cmp     eax, 0
    jne     sb_check_vendor

sb_empty:
    mov     [hdrtype], byte 0
    jmp     sb_inc_devf

sb_check_vendor:
    ; iterate though PCICards until end or match found
    mov     esi, PCICards

sb_check:
    cmp     [esi], dword 0
    je      sb_inc_devf                ; Quit if at last entry
    cmp     eax, [esi]
    je      sb_got_card
    add     esi, PCICARDS_ENTRY_SIZE
    jmp     sb_check

sb_got_card:
    ; indicate that we have found the card
    mov     [pci_data], eax
    mov     [pci_dev], ecx
    mov     [pci_bus], ebx

    ; Define the driver functions
    push    eax
    mov     eax, [esi+4]
    mov     [drvr_probe], eax
    mov     eax, [esi+8]
    mov     [drvr_reset], eax
    mov     eax, [esi+12]
    mov     [drvr_poll], eax
    mov     eax, [esi+16]
    mov     [drvr_transmit], eax
    pop     eax

    mov     edx, PCI_BASE_ADDRESS_0

sb_reg_check:
    call    pcibios_read_config_dword
    mov     [io_addr], eax
    and     eax, PCI_BASE_ADDRESS_IO_MASK
    cmp     eax, 0
    je      sb_inc_reg
    mov     eax, [io_addr]
    and     eax, PCI_BASE_ADDRESS_SPACE_IO
    cmp     eax, 0
    je      sb_inc_reg

    mov     eax, [io_addr]
    and     eax, PCI_BASE_ADDRESS_IO_MASK
    mov     [io_addr], eax

sb_exit1:
    ret

sb_inc_reg:
    add     edx, 4
    cmp     edx, PCI_BASE_ADDRESS_5
    jbe     sb_reg_check

sb_inc_devf:
    inc     ecx
    cmp     ecx, 255
    jb      sb_devf_loop
    inc     ebx
    cmp     ebx, 256
    jb      sb_bus_loop

    ; We get here if we didn't find our card
    ; set io_addr to 0 as an indication
    xor     eax, eax
    mov     [io_addr], eax

sb_exit2:
    ret

;***************************************************************************
;
;  DEBUGGING CODE FOLLOWS
;
;  If debugging data output is not required, ALL code & data below may
;  be removed.
;
;***************************************************************************

if DEBUGGING_STATE = DEBUGGING_ENABLED

;***************************************************************************
;   Function
;      eth_dump
;
;   Description
;       Dumps a tx or rx ethernet packet over the rs232 link
;       This is a debugging routine that seriously slows down the stack.
;       Use with caution.
;
;       Baud rate is 57600, 8n1  com1
;         eax : type (0 == rx, 1 == tx )
;          cx : # of bytes in buffer
;         esi : address of buffer start
;         edi : pointer to MACAddress ( tx only )
;
;***************************************************************************
eth_dump:
    pusha

    ; Set the port to the desired speed
    mov     ebx, 0x3f8                     ; combase

    mov     edx, ebx
    add     edx, 3                        ; data format register
    mov     al, 0x80                    ; enable access to divisor latch
    out     dx, al

    mov     edx, ebx
    add     edx, 1                        ; interrupt enable register
    mov     al, 0x00                    ; No interruts enabled
    out     dx, al

    mov     edx, ebx
    mov     al, 0x20 / 16                ; set baud rate to 57600 0x10 =115200
    out     dx, al

    mov     edx, ebx
    add     edx, 3                        ; data format register
    mov     al, 0x03                    ; 8 data bits
    out     dx, al

    mov     edx, ebx
    add     edx, 4                        ; Modem control register
    mov     al, 0x08                    ; out2 enabled. No handshaking.
    out     dx, al

    mov     edx, ebx
    add     edx, 1                        ; interrupt enable register
    mov     al, 0x01                    ; Receive data interrupt enabled,
    out     dx, al

    popa

    ; First, display the type of the buffer.
    ; If it is a tx buffer, display the macaddress

    pusha

    cmp     eax, 0
    jne     dd001

    mov     bl, 0x0a
    call    tx_byted
    mov     bl, 0x0d
    call    tx_byted

    ; Output "RX:"
    mov     bl, 'R'
    call    tx_byted
    mov     bl, 'X'
    call    tx_byted
    mov     bl, ':'
    call    tx_byted
    jmp     dump_data

dd001:
    mov     bl, 0x0a
    call    tx_byted
    mov     bl, 0x0d
    call    tx_byted

    ; Output TX: xxxxxxxxxxxx
    mov     bl, 'T'
    call    tx_byted
    mov     bl, 'X'
    call    tx_byted
    mov     bl, ':'
    call    tx_byted
    mov     bl, ' '
    call    tx_byted

    ; Display MAC address
    xor     eax, eax
    mov     al, [edi]
    shr     al, 4
    mov     bl, [eax + hexchars]
    call    tx_byted ; byte in bl eax ebx edx destroyed

    xor     eax, eax
    mov     al, [edi]
    and     al, 0x0f
    mov     bl, [eax + hexchars]
    call    tx_byted ; byte in bl eax ebx edx destroyed

    inc     edi
    xor     eax, eax
    mov     al, [edi]
    shr     al, 4
    mov     bl, [eax + hexchars]
    call    tx_byted ; byte in bl eax ebx edx destroyed

    xor     eax, eax
    mov     al, [edi]
    and     al, 0x0f
    mov     bl, [eax + hexchars]
    call    tx_byted ; byte in bl eax ebx edx destroyed

    inc     edi
    xor     eax, eax
    mov     al, [edi]
    shr     al, 4
    mov     bl, [eax + hexchars]
    call    tx_byted ; byte in bl eax ebx edx destroyed

    xor     eax, eax
    mov     al, [edi]
    and     al, 0x0f
    mov     bl, [eax + hexchars]
    call    tx_byted ; byte in bl eax ebx edx destroyed

    inc     edi
    xor     eax, eax
    mov     al, [edi]
    shr     al, 4
    mov     bl, [eax + hexchars]
    call    tx_byted ; byte in bl eax ebx edx destroyed

    xor     eax, eax
    mov     al, [edi]
    and     al, 0x0f
    mov     bl, [eax + hexchars]
    call    tx_byted ; byte in bl eax ebx edx destroyed

    inc     edi
    xor     eax, eax
    mov     al, [edi]
    shr     al, 4
    mov     bl, [eax + hexchars]
    call    tx_byted ; byte in bl eax ebx edx destroyed

    xor     eax, eax
    mov     al, [edi]
    and     al, 0x0f
    mov     bl, [eax + hexchars]
    call    tx_byted ; byte in bl eax ebx edx destroyed

    inc     edi
    xor     eax, eax
    mov     al, [edi]
    shr     al, 4
    mov     bl, [eax + hexchars]
    call    tx_byted ; byte in bl eax ebx edx destroyed

    xor     eax, eax
    mov     al, [edi]
    and     al, 0x0f
    mov     bl, [eax + hexchars]
    call    tx_byted ; byte in bl eax ebx edx destroyed

dump_data:
    popa

    ; OK, we come in here with
    ; cx == number of byte to send
    ; esi == buffer start
    ;
dd_000:
    mov     bl, 0x0a
    call    tx_byted
    mov     bl, 0x0d
    call    tx_byted

    mov     eax, 16        ; Number of characters on the line
    mov     edi, esi    ; Save first byte position for later

    push    ecx

dd_001:
    push    eax

    ; Print a byte, and a space
    xor     eax, eax
    mov     al, [esi]
    shr     al, 4
    mov     bl, [eax + hexchars]
    call    tx_byted ; byte in bl eax ebx edx destroyed

    xor     eax, eax
    mov     al, [esi]
    and     al, 0x0f
    mov     bl, [eax + hexchars]
    call    tx_byted ; byte in bl eax ebx edx destroyed

    mov     bl, ' '
    call    tx_byted

    pop     eax

    inc     esi
    dec     ecx
    cmp     ecx, 0
    je      dd_0011            ; Print the ASCII format

    dec     eax

    cmp     eax, 0
    je      dd_002            ; Print the ASCII format
    jmp     dd_001            ; Print rest of line

dd_0011:
    ; First, complete the 16 bytes of data, by printing spaces
    dec     eax
    cmp     eax, 0
    je      dd_002

    push    eax
    mov     bl, ' '
    call    tx_byted
    mov     bl, ' '
    call    tx_byted
    mov     bl, ' '
    call    tx_byted
    pop     eax
    jmp     dd_0011

dd_002:
    pop     ecx
    mov     esi, edi        ; Go back to the start of the line data

    mov     eax, 16

outLineAscii:
    push    eax

    xor     eax, eax
    mov     al, [esi]
    mov     bl, '.'

    cmp     al, 0x1F
    jle     outAscii
    cmp     al, 0x7e
    jge     outAscii

    mov     bl, al

outAscii:
    call    tx_byted ; byte in bl eax ebx edx destroyed

    pop     eax
    dec     ecx
    inc     esi
    cmp     ecx, 0
    je      dd_003

    dec     eax
    cmp     eax, 0
    je      dd_003
    jmp     outLineAscii

dd_003:
    cmp     ecx, 0
    je      dd_004
    jmp     dd_000

dd_004:
    ret

;***************************************************************************
;   Function
;      tx_byte
;
;   Description
;       Send a byte in bl out of the com port 1
;       destroys eax, edx
;
;***************************************************************************
tx_byted:
    push    ebx                     ; Save the byte

    mov     ebx, 0x3f8            ; get the com port address

    ; Wait for transmit buffer to empty. This could take 1ms @ 9600baud

    mov     edx, ebx
    add     edx, 5

wait_txd:
    in      al, dx                   ; read uart serialisation status
    and     al, 0x40
    cmp     al, 0
    jz      wait_txd                  ; loop until free

    mov     edx, ebx
    pop     eax                     ; restore the byte to send
    out     dx, al
    ret

iglobal
  ; This is used for translating hex to ASCII for display or output
  hexchars    db '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
endg
end if

