//
// Uthernet II Wiznet 5100 based ethernet card
//
// TCP/IP is built into hardware, so no dependencies on the software
// layers, like the Uthernet
//
include "inc/cmdsys.plh"
include "inc/inet.plh"
//
// Wiznet registers
//
const WIZ_MR     = $00
const WIZ_GWR    = $01
const WIZ_SUBR   = $05
const WIZ_SHAR   = $09
const WIZ_SIPR   = $0F
const WIZ_IR     = $15
const WIZ_IMR    = $16
const WIZ_RTR    = $17
const WIZ_RCR    = $19
const WIZ_RMSR   = $1A
const WIZ_TMSR   = $1B
const WIZ_PATR   = $1C
const WIZ_PTMR   = $28
const WIZ_PMGC   = $29
const WIZ_UIPR   = $2A
const WIZ_UPRT   = $2E
//
// Wiznet socket registers
//
const WIZ_SREGS   = $0400
const WIZ_SSIZE   = $0100
const WIZ_SOCK0   = $0400
const WIZ_SOCK1   = $0500
const WIZ_SOCK2   = $0600
const WIZ_SOCK3   = $0700
const WIZ_SnMR    = $00
const WIZ_SnCR    = $01
const WIZ_SnIR    = $02
const WIZ_SnSR    = $03
const WIZ_SnPORT  = $04
const WIZ_SnDHAR  = $06
const WIZ_SnDIPR  = $0C
const WIZ_SnDPORT = $10
const WIZ_SnMSSR  = $12
const WIZ_SnPROTO = $14
const WIZ_SnTOS   = $15
const WIZ_SnTTL   = $16
const WIZ_SnFSR   = $20
const WIZ_SnTXRD  = $22
const WIZ_SnTXWR  = $24
const WIZ_SnRSR   = $26
const WIZ_SnRXRD  = $28
//
// Wiznet socket data
//
const WIZ_TXMEM  = $4000
const WIZ_TXSIZE = $0800
const WIZ_TXMASK = WIZ_TXSIZE-1
const WIZ_TXMEM0 = $4000
const WIZ_TXMEM1 = $4800
const WIZ_TXMEM2 = $5000
const WIZ_TXMEM3 = $5800
const WIZ_RXMEM  = $6000
const WIZ_RXSIZE = $0800
const WIZ_RXMASK = WIZ_RXSIZE-1
const WIZ_RXMEM0 = $6000
const WIZ_RXMEM1 = $6800
const WIZ_RXMEM2 = $7000
const WIZ_RXMEM3 = $7800
//
// Wiznet indirect registers
//
word[] rom
word   saveidx
byte   regidx
byte   regdata
byte   slot
//
// Wiznet MAC address
//
byte[6] wizMAC = $00,$0A,$99,$1E,$02,$B0
//
// Wiznet IP addresses
//
byte[4] localip
byte[4] subnet
byte[4] gateway
//
// Predefine service routine
//
predef wizServiceIP
//
// Segment list element
//
struc t_segment
    word seg_buf
    word seg_len
end
//
// Max Ethernet frame size
//
const MAX_FRAME_SIZE = 1518
const MAC_BROADCAST  = $FFFF
const MAC_SIZE       = 6
//
// Ethernet header
//
struc t_ethrhdr
    byte[MAC_SIZE] ethr_dst
    byte[MAC_SIZE] ethr_src
    word           ethr_payload
end
const PAYLOAD_IP  = $0008 // BE format
const PAYLOAD_ARP = $0608 // BE format
//
// IP datagram header
//
const IP4ADR_SIZE = 4
struc t_iphdr
    byte ip_vers_hlen
    byte ip_service
    word ip_length
    word ip_id
    word ip_flags_fragofst
    byte ip_ttl
    byte ip_proto
    word ip_chksm
    byte[IP4ADR_SIZE] ip_src
    byte[IP4ADR_SIZE] ip_dst
    byte[] ip_options
end
const IP_BROADCAST  = $FFFF
const IP_PROTO_ICMP = $01
const IP_PROTO_UDP  = $11
const IP_PROTO_TCP  = $06
word bcast = IP_BROADCAST, IP_BROADCAST
//
// ICMP type/codes
//
const ICMP_ECHO_REQST = 8
const ICMP_ECHO_REPLY = 0
//
// ICMP message format
//
struc t_icmp
    byte    icmp_type
    byte    icmp_code
    word    icmp_chksm
    word[2] icmp_header
end
//
// UDP IPv4 psuedo header
//
struc t_piphdr
    byte[IP4ADR_SIZE] pip_src
    byte[IP4ADR_SIZE] pip_dst
    byte              pip_zero
    byte              pip_proto
    word              pip_len
end
//
// UDP header
//
struc t_udphdr
    word udp_src
    word udp_dst
    word udp_len
    word udp_chksm
end
//
// TCP header
//
struc t_tcphdr
    word tcp_src
    word tcp_dst
    word tcp_len
    word tcp_chksm
end
//
// Local network parameters
//
const MAX_WIZ_CHANNELS  = 4
//
// Channel protocols
//
const WIZ_PROTO_CLOSED  = 0
const WIZ_PROTO_TCP     = 1
const WIZ_PROTO_UDP     = 2
const WIZ_PROTO_IP      = 3
const WIZ_PROTO_RAW     = 4
//
// State transistions
//
const TCP_STATE_CLOSED  = 0
const TCP_STATE_CLOSING = 1
const TCP_STATE_LISTEN  = 2
const TCP_STATE_CONNECT = 3
const TCP_STATE_OPEN    = 4
//
// HW channels
//
struc t_channel
    byte    channel_proto
    byte    channel_state
    word    channel_regs
    word    channel_txmem
    word    channel_rxmem
    word    channel_lclport
    word    channel_remport
    byte[4] channel_remip
    word    channel_recv_func
    word    channel_recv_parm
end
byte[t_channel] wizChannel[MAX_WIZ_CHANNELS]
//
// Service ICMP hook
//
export word hookICMP
//
// Defines for ASM routines
//
asm equates
        !SOURCE "vmsrc/plvmzp.inc"
end
//
// Swap bytes in word
//
asm swab(val)
        LDA     ESTKL,X
        LDY     ESTKH,X
        STA     ESTKH,X
        STY     ESTKL,X
        RTS
end
//
// Wiznet I/O functions
//
// POKE WORD TO I/O SPACE
// Note: Big Endian format
//
asm _pokeiow(val)#0
        LDA     ESTKH,X
end
asm _pokeiowl
        STA     $C000
        LDA     ESTKL,X
end
asm _pokeiowh
        STA     $C000
        INX
        RTS
end
//
// POKE BYTE TO I/O SPACE
//
asm _pokeio(val)#0
        LDA     ESTKL,X
end
asm _pokeiol
        STA     $C000
        INX
        RTS
end
//
// PEEK BYTE FROM I/O SPACE
//
asm _peekio
        DEX
end
asm _peekiol
        LDA     $C000
        STA     ESTKL,X
        LDA     #$00
        STA     ESTKH,X
        RTS
end
//
// PEEK WORD FROM I/O SPACE
// Note: Big Endian format
//
asm _peekiow
        DEX
end
asm _peekiowl
        LDA     $C000
        STA     ESTKH,X
end
asm _peekiowh
        LDA     $C000
        STA     ESTKL,X
        RTS
end
//
// WRITE DATA INTO I/O SPACE
// pokedata(BUF, LEN)
//
asm pokedata(buf, len)#0
        LDA     ESTKL+1,X
        STA     SRCL
        LDA     ESTKH+1,X
        STA     SRCH
        LDY     ESTKL,X
        BEQ     POKELP
        LDY     #$00
        INC     ESTKH,X
POKELP  LDA     (SRC),Y
end
asm _pokedata
        STA     $C000
        INY
        BNE     +
        INC     SRCH
+       DEC     ESTKL,X
        BNE     POKELP
        DEC     ESTKH,X
        BNE     POKELP
        INX
        INX
        RTS
end
//
// READ DATA FROM I/O SPACE
// peekdata(BUF, LEN)
//
asm peekdata(buf, len)#0
        LDA     ESTKL+1,X
        STA     DSTL
        LDA     ESTKH+1,X
        STA     DSTH
        LDY     ESTKL,X
        BEQ     PEEKLP
        LDY     #$00
        INC     ESTKH,X
end
asm _peekdata
PEEKLP  LDA     $C000
        STA     (DST),Y
        INY
        BNE     +
        INC     DSTH
+       DEC     ESTKL,X
        BNE     PEEKLP
        DEC     ESTKH,X
        BNE     PEEKLP
        INX
        INX
        RTS
end
def pokeiow(io, data)#0
    _pokeiowl.1 = io
    _pokeiowh.1 = io+1
    _pokeiow(data)
end
def pokeio(io, data)#0
    _pokeiol.1 = io
    _pokeio(data)
end
def peekio(io)
    _peekiol.1 = io
    return _peekio()
end
def peekiow(io)
    _peekiowl.1 = io
    _peekiowh.1 = io+1
    return _peekiow()
end
def pokereg(reg, data)#0
    _pokeiow(reg)
    _pokeio(data)
end
def peekreg(reg)
    _pokeiow(reg)
    return _peekio()
end
def pokeregs(reg, buf, len)#0
    _pokeiow(reg)
    pokedata(buf, len)
end
def peekregs(reg, buf, len)#0
    _pokeiow(reg)
    peekdata(buf, len)
end
def pokeregw(reg, dataw)#0
    _pokeiow(reg)
    _pokeio(dataw.1)
    _pokeio(dataw.0)
end
def peekregw(reg)
    word dataw

    _pokeiow(reg)
    dataw.1 = _peekio()
    _pokeiow(reg + 1)
    dataw.0 = _peekio()
    return dataw
end
//
// Send UDP datagram
//
def wizSendUDP(wiz, ipdst, portdst, data, len)
    word wizregs, wizdata, txrr, txwr, splitlen

    wizregs = wiz=>channel_regs
    wizdata = wiz=>channel_txmem
    if !ipdst
        ipdst = @bcast
    fin
    //
    // Wait for Tx room
    //
    repeat; until peekregw(wizregs + WIZ_SnFSR) >= len
    //
    // Calc new write ptr, check for split
    //
    txwr = peekregw(wizregs + WIZ_SnTXWR)
    txrr = txwr & WIZ_TXMASK
    if txrr + len > WIZ_TXSIZE
        splitlen = WIZ_TXSIZE - txrr
        pokeregs(wizdata + txrr, data,            splitlen)
        pokeregs(wizdata,        data + splitlen, len - splitlen)
    else
        pokeregs(wizdata + txrr, data, len)
    fin
    //
    // Set destination address/port
    //
    pokeregs(wizregs + WIZ_SnDIPR, ipdst, IP4ADR_SIZE)
    pokeregw(wizregs + WIZ_SnDPORT, portdst)
    //
    // Update write pointer and send
    //
    pokeregw(wizregs + WIZ_SnTXWR, txwr + len)
    pokereg(wizregs + WIZ_SnCR, $20) // SEND
    return 0
end
//
// Open UDP channel and set datagram received callback
//
def wizOpenUDP(localport, callback, param)
    word wiz
    byte i

    if !localport; return -1; fin // invalid port
    //
    // Look for an existing notification on localport
    //
    wiz = @wizChannel
    for i = 1 to MAX_WIZ_CHANNELS
        if wiz->channel_proto == IP_PROTO_UDP and wiz=>channel_lclport == localport
            break
        fin
        wiz = wiz + t_channel
    next
    if i > MAX_WIZ_CHANNELS
        //
        // Add notification on localport if room
        //
        wiz = @wizChannel
        for i = 1 to MAX_WIZ_CHANNELS
            if !wiz->channel_proto
                break
            fin
            wiz = wiz + t_channel
        next
        if i > MAX_WIZ_CHANNELS
            return 0
        fin
    fin
    //
    // Fill in this channel and open it
    //
    wiz->channel_proto     = WIZ_PROTO_UDP
    wiz=>channel_lclport   = localport
    wiz=>channel_recv_func = callback
    wiz=>channel_recv_parm = param
    pokereg(wiz=>channel_regs  + WIZ_SnMR,   $02) // UDP protocol
    pokeregw(wiz=>channel_regs + WIZ_SnPORT, localport)
    pokereg(wiz=>channel_regs  + WIZ_SnCR,   $01) // OPEN
    return wiz
end
//
// Close UDP port
//
def wizCloseUDP(wiz)
    if isuge(wiz, @wizChannel) and isult(wiz, @wizChannel + MAX_WIZ_CHANNELS * t_channel)
        //
        // Clear notiications on this port
        //
        if wiz->channel_proto == WIZ_PROTO_UDP
            wiz->channel_proto = WIZ_PROTO_CLOSED
            pokereg(wiz=>channel_regs + WIZ_SnCR, $10) // CLOSE
            return 0
        fin
    fin
    //
    // Invalid port
    //
    return -1
end
//
// Open TCP socket in SERVER mode
//
def wizListenTCP(lclport, callback, param)
    word wiz
    byte i

    //
    // Look for an existing notification on localport
    //
    wiz = @wizChannel
    for i = 1 to MAX_WIZ_CHANNELS
        if wiz->channel_proto == WIZ_PROTO_TCP and wiz->channel_state == TCP_STATE_LISTEN and wiz=>channel_lclport == lclport
            break
        fin
        wiz = wiz + t_channel
    next
    if i > MAX_WIZ_CHANNELS
        //
        // Add notification on localport if room
        //
        wiz = @wizChannel
        for i = 1 to MAX_WIZ_CHANNELS
            if !wiz->channel_proto
                break
            fin
            wiz = wiz + t_channel
        next
        if i > MAX_WIZ_CHANNELS
            return 0
        fin
    fin
    //
    // Fill in this channel and open it
    //
    wiz->channel_proto     = WIZ_PROTO_TCP
    wiz->channel_state     = TCP_STATE_LISTEN
    wiz=>channel_remip:0   = 0
    wiz=>channel_remip:2   = 0
    wiz=>channel_remport   = 0
    wiz=>channel_lclport   = lclport
    wiz=>channel_recv_func = callback
    wiz=>channel_recv_parm = param
    pokereg(wiz=>channel_regs  + WIZ_SnMR,   $01) // TCP protocol
    pokeregw(wiz=>channel_regs + WIZ_SnPORT, lclport)
    pokereg(wiz=>channel_regs  + WIZ_SnCR,   $01) // OPEN
    while peekreg(wiz=>channel_regs + WIZ_SnSR) <> $13; loop // Wait for init
    pokereg(wiz=>channel_regs + WIZ_SnCR, $02) // LISTEN
    return wiz
end
//
// Open TCP socket in CLIENT mode
//
def wizConnectTCP(remip, remport, lclport, callback, param)
    word wiz
    byte i

    //
    // Look for an existing notification on localport
    //
    wiz = @wizChannel
    for i = 1 to MAX_WIZ_CHANNELS
        if wiz->channel_proto == WIZ_PROTO_TCP and wiz->channel_state == TCP_STATE_CONNECT and wiz=>channel_lclport == lclport
            break
        fin
        wiz = wiz + t_channel
    next
    if i > MAX_WIZ_CHANNELS
        //
        // Add notification on localport if room
        //
        wiz = @wizChannel
        for i = 1 to MAX_WIZ_CHANNELS
            if !wiz->channel_proto
                break
            fin
            wiz = wiz + t_channel
        next
        if i > MAX_WIZ_CHANNELS
            return 0
        fin
    fin
    //
    // Fill in this channel and open it
    //
    wiz->channel_proto     = WIZ_PROTO_TCP
    wiz->channel_state     = TCP_STATE_CONNECT
    wiz=>channel_remip:0   = remip=>0
    wiz=>channel_remip:2   = remip=>2
    wiz=>channel_remport   = remport
    wiz=>channel_lclport   = lclport
    wiz=>channel_recv_func = callback
    wiz=>channel_recv_parm = param
    pokereg(wiz=>channel_regs + WIZ_SnMR, $01) // TCP protocol
    pokeregs(wiz=>channel_regs + WIZ_SnDIPR, remip, IP4ADR_SIZE)
    pokeregw(wiz=>channel_regs + WIZ_SnDPORT, remport)
    pokeregw(wiz=>channel_regs + WIZ_SnPORT, lclport)
    pokereg(wiz=>channel_regs + WIZ_SnCR, $01) // OPEN
    while peekreg(wiz=>channel_regs + WIZ_SnSR) <> $13; loop // Wait for init
    pokereg(wiz=>channel_regs + WIZ_SnCR, $04) // CONNECT
    return wiz
end
//
// Write to TCP socket
//
def wizSendTCP(wiz, data, len)
    word wizregs, wizdata, txrr, txwr, splitlen

    if wiz->channel_state <> TCP_STATE_OPEN; return -1; fin
    wizregs = wiz=>channel_regs
    wizdata = wiz=>channel_txmem
    //
    // Wait for Tx room
    //
    repeat; until peekregw(wizregs + WIZ_SnFSR) >= len
    //
    // Calc new write ptr, check for split
    //
    txwr = peekregw(wizregs + WIZ_SnTXWR)
    txrr = txwr & WIZ_TXMASK
    if txrr + len > WIZ_TXSIZE
        splitlen = WIZ_TXSIZE - txrr
        pokeregs(wizdata + txrr, data,            splitlen)
        pokeregs(wizdata,        data + splitlen, len - splitlen)
        //putc('(');puti(splitlen);putc(',');puti(len-splitlen);putc(')')
    else
        pokeregs(wizdata + txrr, data, len)
    fin
    //
    // Update write pointer and send
    //
    pokeregw(wizregs + WIZ_SnTXWR, txwr + len)
    pokereg(wizregs + WIZ_SnCR, $20) // SEND
    return wiz
end
//
// Close TCP socket
//
def wizCloseTCP(wiz)
    if isuge(wiz, @wizChannel) and isult(wiz, @wizChannel + MAX_WIZ_CHANNELS * t_channel)
        //
        // Clear notiications on this port
        //
        if wiz->channel_proto == WIZ_PROTO_TCP
            pokereg(wiz=>channel_regs + WIZ_SnCR, $10) // CLOSE
            wiz->channel_proto = WIZ_PROTO_CLOSED
            return 0
        fin
    fin
    //
    // Invalid port
    //
    return -1
end
//
// Update notify callback
//
def wizSetCallback(wiz, callback)
    if wiz->channel_proto == WIZ_PROTO_UDP or wiz->channel_proto == WIZ_PROTO_TCP
        //
        // Update callback on this port
        //
        wiz=>channel_recv_func = callback
        return 0
    fin
    //
    // Invalid port
    //
    return -1
end
//
// Update notify param
//
def wizSetParam(wiz, param)
    if wiz->channel_proto == WIZ_PROTO_UDP or wiz->channel_proto == WIZ_PROTO_TCP
        //
        // Update param on this port
        //
        wiz=>channel_recv_parm = param
        return 0
    fin
    //
    // Invalid port
    //
    return -1
end
//
// Service incoming packets
//
def wizServiceIP
    word wiz, wizregs, wizdata, rxlen, rxrr, rxwr, rxpkt, splitlen
    byte ir, i, sir

    ir = peekreg(WIZ_IR)
    if ir
        wiz = @wizChannel
        for i = 0 to 3
            //
            // Socket activity
            //
            if ir & (1 << i)
                wizregs = wiz=>channel_regs
                wizdata = wiz=>channel_rxmem
                sir = peekreg(wizregs + WIZ_SnIR)
                pokereg(wizregs + WIZ_SnIR, sir) // Clear SnIR
                when wiz->channel_proto
                    is WIZ_PROTO_UDP
                        if sir & $04
                            //
                            // Receive UDP packet
                            //
                            rxlen = peekregw(wizregs + WIZ_SnRSR)
                            rxrr  = peekregw(wizregs + WIZ_SnRXRD)
                            rxwr  = rxrr & WIZ_RXMASK
                            rxpkt = heapalloc(rxlen)
                            if rxwr + rxlen >= WIZ_RXSIZE
                                splitlen = WIZ_RXSIZE - rxwr
                                peekregs(wizdata + rxwr, rxpkt,            splitlen)
                                peekregs(wizdata,        rxpkt + splitlen, rxlen - splitlen)
                            else
                                peekregs(wizdata + rxwr, rxpkt, rxlen)
                            fin
                            pokeregw(wizregs + WIZ_SnRXRD, rxrr + rxlen)
                            pokereg(wizregs + WIZ_SnCR, $40) // RECV
                            wiz=>channel_recv_func(rxpkt,swab(rxpkt=>4),rxpkt+8,rxlen-8,wiz=>channel_recv_parm)
                            heaprelease(rxpkt)
                        fin
                        break
                    is WIZ_PROTO_TCP
                        if sir & $01
                            //
                            // Connect TCP socket
                            //
                            when wiz->channel_state
                                is TCP_STATE_LISTEN
                                    peekregs(wiz=>channel_regs + WIZ_SnDIPR, @wiz=>channel_remip, IP4ADR_SIZE)
                                    wiz=>channel_remport = peekregw(wiz=>channel_regs + WIZ_SnDPORT)
                                is TCP_STATE_CONNECT
                                    wiz->channel_state = TCP_STATE_OPEN
                            wend
                        fin
                        if sir & $04
                            //
                            // Receive TCP packet
                            //
                            if wiz->channel_state == TCP_STATE_OPEN
                                rxlen = peekregw(wizregs + WIZ_SnRSR)
                                rxrr  = peekregw(wizregs + WIZ_SnRXRD)
                                rxwr  = rxrr & WIZ_RXMASK
                                rxpkt = heapalloc(rxlen)
                                if rxwr + rxlen > WIZ_RXSIZE
                                    splitlen = WIZ_RXSIZE - rxwr
                                    peekregs(wizdata + rxwr, rxpkt,            splitlen)
                                    peekregs(wizdata,        rxpkt + splitlen, rxlen - splitlen)
                                else
                                    peekregs(wizdata + rxwr, rxpkt, rxlen)
                                fin
                                pokeregw(wizregs + WIZ_SnRXRD, rxrr + rxlen)
                                pokereg(wizregs + WIZ_SnCR, $40) // RECV
                                wiz=>channel_recv_func(@wiz=>channel_remip,wiz=>channel_remport,wiz=>channel_lclport,rxpkt,rxlen,wiz=>channel_recv_parm)
                                heaprelease(rxpkt)
                            fin
                        fin
                        if sir & $02
                            //
                            // Close TCP socket
                            //
                            if wiz->channel_state == TCP_STATE_OPEN // Notify callback w/ len = 0
                                wiz=>channel_recv_func(@wiz=>channel_remip,wiz=>channel_remport,0,wiz=>channel_lclport,0,wiz=>channel_recv_parm)
                            fin
                            wiz->channel_state = TCP_STATE_CLOSED
                            pokereg(wiz=>channel_regs + WIZ_SnCR, $10) // CLOSE
                        fin
                        if sir & $08
                            //
                            // Timeout on TCP socket
                            //
                            when wiz->channel_state
                                is TCP_STATE_OPEN
                                    wiz->channel_state = TCP_STATE_CLOSING
                                    wiz=>channel_recv_func(@wiz=>channel_remip,wiz=>channel_remport,wiz=>channel_lclport,0,0,wiz=>channel_recv_parm)
                                    break
                                is TCP_STATE_CONNECT
                                    wiz=>channel_recv_func(@wiz=>channel_remip,wiz=>channel_remport,wiz=>channel_lclport,0,0,wiz=>channel_recv_parm)
                                is TCP_STATE_CLOSING
                                    wiz->channel_state = TCP_STATE_CLOSED
                                    pokereg(wiz=>channel_regs + WIZ_SnCR, $10) // CLOSE
                            wend
                        fin
                wend
            fin
            wiz = wiz + t_channel
        next
        if ir & $80
            //
            // IP conflict
            //
            pokereg(WIZ_IR, $80)
        fin
        if ir & $40
            //
            // Destination unreachable
            //
            pokereg(WIZ_IR, $40)
        fin
        if ir & $20
            //
            // PPOE connection close
            //
            pokereg(WIZ_IR, $20)
        fin
    fin
    return 0
end
//
// Set the local IP addresses
//
def setWizIP(newIP, newSubnet, newGateway)
    if newIP
        localip:0 = newIP=>0; localip:2 = newIP=>2
        pokeregs(WIZ_SIPR, newIP, IP4ADR_SIZE)
    fin
    if newSubnet
        subnet:0 = newSubnet=>0; subnet:2 = newSubnet=>2
        pokeregs(WIZ_SUBR, newSubnet, IP4ADR_SIZE)
    fin
    if newGateway
        gateway:0 = newGateway=>0; gateway:2 = newGateway=>2
        pokeregs(WIZ_GWR, newGateway, IP4ADR_SIZE)
    fin
    return 0
end
//
// Get the interface hardware address
//
def getWizHA(ha)
    if ha
        ha=>0 = wizMAC:0; ha=>2 = wizMAC:2; ha=>4 = wizMAC:4
    fin
    return MAC_SIZE
end
//
// Identify Uthernet II card and initialize
//
for slot = $90 to $F0 step $10
    rom = ((slot & $70) << 4) | $C000
    if rom=>$06 <> $3C86 or (slot == $0B or (rom->$05 <> $38 and rom->$07 <> $18)) // Skip slots with signature
        regdata = peekio(slot)
        if (regdata & $E4) == $00
            pokeio(slot, $03) // Try setting auto-increment indirect I/F
            if peekio(slot) == $03
                saveidx = peekiow(slot + 1)
                peekio(slot + 3) // Dummy read to data register should increment index
                if peekiow(slot + 1) == saveidx + 1
                    //
                    // Good chance this is it
                    //
                    pokeio(slot, $80) // RESET
                    regidx      = slot + 1
                    regdata     = slot + 3
                    _pokedata.1 = regdata
                    _peekdata.1 = regdata
                    repeat
                        pokeio(slot, $03) // Auto-increment indirect I/F + enable ping
                    until peekio(slot) == $03
                    //
                    // The following looks redundant, but it sets up the peek/poke locations
                    // for peekreg(s)/pokereg(s)
                    //
                    pokeiow(regidx, WIZ_MR)
                    pokeio(regdata, $03) // Auto-increment indirect I/F + enable ping
                    peekio(regdata)
                    //
                    // Initialize common registers
                    //
                    wizMAC[5] = slot >> 4          // Slighty unique MAC address
                    pokeregs(WIZ_SHAR, @wizMAC, 6) // MAC addr
                    pokeregw(WIZ_RTR, 5000)        // Timeout period to 500ms
                    pokereg(WIZ_RMSR, $55)         // 2K Rx memory/channel
                    pokereg(WIZ_TMSR, $55)         // 2K Tx memory/channel
                    //
                    // Print settings
                    //
                    puts("Found Uthernet II in slot #")
                    putc('0' + ((slot - $80) >> 4))
                    putln
                    //
                    // Fill channel structure
                    //
                    saveidx = @wizChannel
                    for slot = 0 to 3
                        saveidx=>channel_regs  = WIZ_SREGS + (WIZ_SSIZE  * slot)
                        saveidx=>channel_txmem = WIZ_TXMEM + (WIZ_TXSIZE * slot)
                        saveidx=>channel_rxmem = WIZ_RXMEM + (WIZ_RXSIZE * slot)
                        saveidx = saveidx + t_channel
                    next
                    //
                    // Fill in Net class
                    //
                    iNet:serviceIP      = @wizServiceIP
                    iNet:openUDP        = @wizOpenUDP
                    iNet:sendUDP        = @wizSendUDP
                    iNet:closeUDP       = @wizCloseUDP
                    iNet:listenTCP      = @wizListenTCP
                    iNet:connectTCP     = @wizConnectTCP
                    iNet:sendTCP        = @wizSendTCP
                    iNet:closeTCP       = @wizCloseTCP
                    iNet:setInterfaceIP = @setWizIP
                    iNet:getInterfaceHA = @getWizHA
                    iNet:setCallback    = @wizSetCallback
                    iNet:setParam       = @wizSetParam
                    return modkeep
                fin
            fin
            pokeio(slot, regdata) // Restore register
        fin
    fin
next
//
// Not found
//
return -1
done
