home *** CD-ROM | disk | FTP | other *** search
- version equ 3
-
- ; Packet driver to simulate Ethernet on Novell IPX protocol.
- ;
- ; Paul Kranenburg
- ; Department of Computer Science
- ; University of Leiden
- ; Niels Bohrweg 1
- ; PO Box 9512
- ; 2300 RA Leiden
- ; The Netherlands
- ; e-mail: pk@cs.leidenuniv.nl
- ;
- ;
- ; File: ipxpkt.asm
- ;
- ;
- ; General description:
- ;
- ; Take destination from the Ethernet packet and feed it to IPX
- ; in the Event Control Block Immediate Address field.
- ;
- ; IPX packets are 576 bytes at most, 30 are needed for the IPX header
- ; leaving 546 bytes of user data. Another 4 bytes are used to describe
- ; fragments.
- ; If NO_OF_SND_BUFS is set to 1, this yields an MTU for this driver of 528.
- ; (546 - 4 - sizeof(Ether header)[=14]).
- ; If IPX trail is used another 32 bytes are lost (MTU = 496).
- ;
- ; If NO_OF_SND_BUFS is set to 3, the Ethernet packet is broken into at most
- ; 3 fragments. These are tagged with a Fragment id and shipped.
- ;
- ; On reception, fragments are kept on a linked list ordered by fragment number
- ; and keyed by source node address and fragment id.
- ; An IPX event is scheduled to allow for timeout of pending reassembly queues.
- ;
- ; If all fragments are reassembled, the client is called to provide a buffer for
- ; the packet.
- ;
- ; [ To save on buffer space, the driver could conceivably do with some minimum
- ; number of buffers and call recv_find as soon as a fragment arrives, copy
- ; the contents, and only call recv_copy when all fragments have arrived. However,
- ; I don't think there is a way to notify the client in case a fragment gets lost.]
- ;
- ; In this code, the number of receive buffers (NO_OF_RCV_BUFS) has been set
- ; to 6 (a wild guess).
- ; This driver has yet to be tested in a gateway under heavy load. One probably
- ; needs more buffers in this case.
- ;
- ; Buffer space for the receive buffers is allocated after the "end_resident"
- ; label.
- ; REMOVED 6/22/92 by adding memory allocation to the skeleton.
- ; There is a potential problem here: we start listening for packets
- ; using these buffers while still in the initialisation code, which is overlaid
- ; by the receive buffers. This is why interrupts are turned off wherever possible.
- ;
- ;
- ; CHANGELOG.
- ;
- ; 15 Feb 1991 apb
- ; The code for determining the ethernet address from the ipx address used
- ; word operations instead of byte operations in two places.
- ;
- ; 13 Feb 1991 apb
- ; Changed method of incorporating IPX net address into simulated ethernet
- ; address -- there's no need to get fancy if there is space for the entire
- ; net address to fit.
- ;
- ; 11 Feb 1991 apb
- ; Fixed usage message.
- ; Added message about long wait while GET_LOCAL_TARGET does its work.
- ; (XXX there must be a better way of doing this.)
- ;
- ; 07/16/90
- ; Decoupled simulated ethernet address from IPX node address in routing tables,
- ; allowing for unique addresses in nets like ARCNET with only a one byte
- ; node address.
- ; Thanks to Robert Roll and Reid Sweatman of the University of Utah
- ; for pointing this out.
- ; IPXPKT accepts a command line option of the from `-n [<no_bytes>]' to specify
- ; the number of significant bytes in the IPX node address. If less then EADDR_LEN,
- ; the presented ethernet address will get supplemented with some bytes from
- ; the IPX node address to (hopefully) create a unique address among the nodes
- ; in the network involved.
- ; If `<no_bytes>' is omitted, the number of significant bytes will be set as the
- ; number of bytes left in the node address after stripping leading zeroes.
- ;
- ; 07/12/90
- ; Reorganize initialisation; do GET_ADDESS etc., before posting LISTEN's
- ;
- ; 05/16/90
- ; New socket number used when TRAIL enabled (0x6181).
- ; Enables co-existance of original and "TRAIL" versions of packet driver.
- ; You can start both an old and a new experimental driver in your gateway,
- ; but watch those IP subnet addresses.
- ;
- ; 05/15/90
- ; Corrected byte-order of IPX socket.
- ; Socket number NOT actually changed (== 0x6180, in dynamic range)
- ;
- ; 05/15/90
- ; Add dummy GET_LOCAL_TARGET call to force some traffic to IPX from a bridge.
- ; This will get the local net address to IPX. Define TRY_GET_LOCAL_TARGET if
- ; you want to use this.
- ;
- ; 05/07/90
- ; Add statistics gathering on various table lookups.
- ; Compile option STAT. Use ipxstat.c for display (derivative of version 5.0 `stat.c').
- ;
- ; 05/04/90
- ; Fixed case of register trashing in route code.
- ; Add IPX 32-byte trailer for bridge routing.
- ; Compile option TRAIL.
- ;
- ; 05/03/90
- ; Add routing table.
- ; Net/node addresses of incoming packets are put routing table along with
- ; immediate address field in ecb struct.
- ; Outgoing packets have the their net- and immediate address looked up
- ; in the table with the node address as key.
- ; Special case: broadcast packets are sent with packet type 0x14 through
- ; permanent entry in routing table.
- ;
- ; 05/03/90
- ; **REMOVED** 07/17/90
- ; Add compile option to declare receive buffer space at compile time.
- ; Compile option NOT_SO_SAVE; if defined, buffer space is allocated beginning at
- ; `end_resident' else space is reserved by compiler
- ;
- ; 05/02/90
- ; Merge `fill_ipxhdr' into `route'.
- ;
-
- ;DEBUG EQU 1
- TRY_GET_LOCAL_TARGET EQU 1
- STAT EQU 1
- TRAIL EQU 1
- ETH_CONSTR EQU 3
-
- include defs.asm
-
- MAX_IPX_LEN = 576 ; Maximum packet size that can be
- ; shipped through IPX
- ifdef TRAIL
- IP_socket = 08161h ; Socket allocated for Blue book Ether
- ; on IPX with TRAILS (BYTE-SWAPPED)
- else
- IP_socket = 08061h ; Socket allocated for
- ; Blue book Ether on IPX (BYTE-SWAPPED)
- endif
-
- PEP = 4 ; Packet Exchange Packet (ipx_type)
- GBP = 014h ; Global Broadcast Packet (ipx_type)
- RTE_TICK = 37 ; Interval between calls to rte_ticker,
- ; 37 is approx. 2 seconds
-
- ipx_header struc
- ipx_chksum dw ? ; Checksum, network byte order
- ipx_len dw ? ; Packet length, "
- ipx_prot db ? ; Transport protocol
- ipx_type db ? ; Packet type
- ipx_destnet db 4 dup(?) ; Destination network
- ipx_destnode db 6 dup(?) ; Destination node
- ipx_destsock dw ? ; Destination socket
- ipx_srcnet db 4 dup(?) ; Source network
- ipx_srcnode db 6 dup(?) ; Source node
- ipx_srcsock dw ? ; Source socket
- ifdef TRAIL
- ipx_trail db 8 * 4 dup(?) ; IPX gateway trail
- endif
- ipx_header ends
-
-
- frag_dscr struc
- frag_addr dd ? ; Fragment address
- frag_size dw ? ; Fragment size
- frag_dscr ends
-
- ecb struc
- ecb_link dd ? ;
- ecb_esr dd ? ; Event Service Routine
- ecb_inuse db ? ; In Use field
- ecb_cmplt db ? ; Completion Code
- ecb_sock dw ? ; Socket Number
- ecb_ipxwork db 4 dup (?) ; IPX reserved workspace
- ecb_drvwork db 12 dup (?) ; Driver reserved workspace
- ecb_ia db 6 dup (?) ; Immediate Address
- ecb_fragcnt dw ? ; Fragment count
- ;ecb_dscr = $ ; Start of Fragment descriptor list
- ecb ends
-
- aes_ecb struc
- aes_link dd ? ;
- aes_esr dd ? ; Event Service Routine
- aes_inuse db ? ; In Use field
- aes_work db 5 dup (?) ; Driver reserved workspace
- aes_ecb ends
-
-
- ether_frag struc
- ef_fragno db ? ; This fragment number
- ef_fragtot db ? ; Total number of fragments comprising the packet
- ef_fragid dw ? ; Fragment Id
- ether_frag ends
-
- queue_entry struc
- q_aes db (size aes_ecb) dup(?); AES structure, used for reassembly timeouts
- q_filler db 0
- q_count db 0 ; Number of fragments currently queued here
- q_net db SIZE ipx_srcnet dup(?)
- q_node db SIZE ipx_srcnode dup(?) ; Source node
- q_fragid dw ? ; Fragment Id
- q_len dw ? ; Total length of user data queued here
- q_ecb dd ? ; Ecb pointer to fragment
- queue_entry ends
-
- u_buf struc
- u_ecb db (size ecb) dup(?)
- u_ipx_frag db (size frag_dscr) dup(?)
- u_frag_frag db (size frag_dscr) dup(?)
- u_data_frag db (size frag_dscr) dup(?)
- u_ipx db (size ipx_header) dup(?)
- u_ether_frag db (size ether_frag) dup(?)
- ;u_data LABEL BYTE
- u_buf ends
-
- MAX_PAYLOAD = MAX_IPX_LEN - SIZE ipx_header - SIZE ether_frag
-
- ;routing table entry
- rt_ent struc
- rt_ether db EADDR_LEN dup(?) ; Ethernet address of target: lookup key
- rt_net db SIZE ipx_srcnet dup(?) ; Net address of target
- rt_node db SIZE ipx_srcnode dup(?) ; Node address of target
- rt_gate db SIZE ecb_ia dup(?) ; First hop on route to above
- rt_x_pkt db ? ; IPX packet type to send packet with
- ;;rt_trail db ? ; This node uses IPX trail
- rt_age dw ? ; Usage indicator for this entry
- rt_ent ends
-
- print$ macro string
- ;---------------------------------------;
- ; sends $ terminated string to screen ;
- ;---------------------------------------;
- push dx
- mov ah,9
- mov dx,offset &string& ; print $ terminated string
- int 21h
- pop dx
- endm
-
- ; ipx function numbers
- OPEN_SOCKET = 0
- CLOSE_SOCKET = 1
- GET_LOCAL_TARGET = 2
- SEND_PACKET = 3
- LISTEN = 4
- SCHEDULE_EVENT = 5
- CANCEL_EVENT = 6
- SCHEDULE_SPECIAL_EVENT = 7
- GET_NODE_ADDRESS = 9
- RELINQUISH = 0Ah
-
- call_ipx macro opcode,reg1,reg2,reg3,reg4,reg5,reg6,reg7,reg8
- irp N, <reg1,reg2,reg3,reg4,reg5,reg6,reg7,reg8>
- ifnb <N>
- push N
- endif
- endm
- mov bx, opcode
- ;better be save here and use Code segment explicitly
- call cs:IPXentry
- irp N, <reg8,reg7,reg6,reg5,reg4,reg3,reg2,reg1>
- ifnb <N>
- pop N
- endif
- endm
- endm
-
- ifdef STAT
- statinc macro loc
- local a
- ;affects flags register (NOT carry)
- inc cs:loc
- jnz a
- inc cs:loc+2
- a:
- endm
- else
- statinc macro loc
- endm
- endif
-
- code segment word public
- assume cs:code, ds:code
-
- IPXentry dd ?
- FragmentID dw ?
-
- my_net_address db 4 dup(?) ; contiguous 10 byte addrss-area as IPX wants it
- my_node_address db 6 dup(?)
-
- no_bytes dw EADDR_LEN, 0 ; number of significant bytes in IPX node address
- ; set as command line option.
- init_cmplt dw 0
-
- dummy db 10 dup(0ffh) ; dummy addr/socket
- dw 05104h ; what socket to use (???)
- db 6 dup(?) ; returned address
-
-
- NO_OF_FRAGMENTS = 3
- NO_OF_RCV_BUFS = 6
- NO_OF_SND_BUFS = 3
- NO_OF_QUEUES = NO_OF_RCV_BUFS ; ????
- NO_OF_RTES = 30
-
- reass_queues queue_entry NO_OF_QUEUES dup(<>)
-
- rcv_bufs u_buf NO_OF_RCV_BUFS dup(<>)
- snd_bufs u_buf NO_OF_SND_BUFS dup(<>)
-
- ifdef STAT
- ;; org ($ + 1) and 0fffeh
- ; keep ID string an even number of bytes (including terminating zero and count)
- db "RTE_TABL", 0, NO_OF_RTES
- endif
- rte rt_ent NO_OF_RTES dup(<>)
- rte_end dw 0
- rte_scache dw 0
- rte_rcache dw 0
- rte_aes aes_ecb <>
-
- ifdef STAT
- ;; org ($ + 1) and 0fffeh
- db "IPXSTAT", 0 ; keep this string an even number of bytes
- queue_full dw 2 dup(0)
- route_drop dw 2 dup(0)
- scache_miss dw 2 dup(0)
- rcache_miss dw 2 dup(0)
- route_loops dw 2 dup(0)
- route_lookups dw 2 dup(0)
- endif
-
- ;-------------------------------------------------------------------------------
- ;
- ; local functions
- ;
- ; A NOTE on style:
- ;
- ; the functions below seem to liberally load and reload pointers into
- ; a register pair involving the ds segment register.
- ; In fact, ds almost always contains the code segment as "assumed" above.
- ; Also, the distinction between pointers to ecb's and ubuf's / queue's is not made
- ; most of the time. This is alright as long as the ecb structures remain the first
- ; ones declared in u_buf and queue.
- ; Need to work out a consistent register usage some day...
- ;
-
- find_queue proc near
- ;
- ; Find/allocate a queue-entry where an ether fragment can be stored.
- ; On entry: es:di -> source node address.
- ; dx == fragment Id.
- ; Out: si == 0 if no queue entry available,
- ; otherwise: (ds:)si -> allocated queue-entry.
- ; Must be called with interrupts disabled.
-
- push cx
- push bx
- mov cx, NO_OF_QUEUES
- lea si, reass_queues
- mov bx, 0
-
- fq_loop:
- mov al, [si].q_count
- or al, al
- jnz fq_1
- or bx, bx ;
- jne fq_2 ; remember first entry not in use
- mov bx, si ;
- jmp short fq_2
-
- fq_1:
- mov ax, cx ; can use AX in stead of `push cx'
- push si
- push di
- add si, q_net
- mov cx, SIZE q_net + SIZE q_node
- cld
- repe cmpsb
- pop di
- pop si
- mov cx, ax
- jne fq_2
- cmp dx, [si].q_fragid
- jne fq_2
- jmp short fq_x
-
- fq_2:
- add si, SIZE queue_entry
- loop fq_loop
-
- mov si, bx
-
- ifdef STAT
- or si, si
- jnz fq_stat_1
- statinc queue_full
- fq_stat_1:
- endif
-
- fq_x:
- pop bx
- pop cx
- ret
- find_queue endp
-
- enqueue proc near
- ; Queue an etherpacket fragment on appropriate queue
- ; On entry: es:si -> received ecb.
- ; cx = length of data in this fragment
- ; Out: carry set if no space available.
- ; zero flag set if packet on queue complete.
- ; ds:si -> queue_entry on which fragment was queued.
-
- push si
- push es
- mov ax, 0
- mov es:[si].u_ecb.ecb_link.offs, ax ; clear link-field
- mov es:[si].u_ecb.ecb_link.segm, ax
- mov di, si ; es:di -> ecb
- mov dx, es:[si].u_ether_frag.ef_fragid
- push di
- ; lea di, es:[si].u_ipx.ipx_srcnode
- lea di, es:[si].u_ipx.ipx_srcnet
- call find_queue
- pop di
-
- or si, si
- jnz enq_0
- add sp, 4
- stc
- ret
-
- enq_0:
- mov dl, es:[di].u_ether_frag.ef_fragno
- mov dh, es:[di].u_ether_frag.ef_fragtot
- cmp [si].q_count, 0
- jne enq_3
-
- ;this is the first fragment we receive
- call rte_enter ; record their route
- pop [si].q_ecb.segm
- pop [si].q_ecb.offs
- mov [si].q_len, cx
- mov [si].q_count, 1
- cmp dh, 1 ;
- jne enq_1 ; short cut if fragment count == 1.
- clc
- ret
-
- ;initialise queue structure a bit more...
- enq_1:
- mov ax, es:[di].u_ether_frag.ef_fragid
- mov [si].q_fragid, ax
-
- ;copy source node address
- mov bx, SIZE q_net + SIZE q_node - 1
-
- enq_2:
- ; mov al, es:[di+bx].u_ipx.ipx_srcnode
- mov al, es:[di+bx].u_ipx.ipx_srcnet
- mov ds:[si+bx].q_net, al
- sub bx, 1
- jnc enq_2
-
- mov ax, cs
- mov [si].q_aes.aes_esr.segm, ax
- mov [si].q_aes.aes_esr.offs, offset reass_timeout
- mov ax, ds
- mov es, ax
- mov ax, 2 ; two ticks to timeout
- call_ipx SCHEDULE_SPECIAL_EVENT,si,dx
- cmp dh, [si].q_count
- clc
- ret
-
- ; add ecb to existing queue, keep list ordered by fragment number.
- enq_3:
- lea ax, [si].q_ecb
- push ax ; put link field address on stack
- push ds
- les di, [si].q_ecb
-
- enq_4:
- mov ax, es ; are we at end of list?
- or ax, di
- jz enq_5
- cmp dl, es:[di].u_ether_frag.ef_fragno ; link after this frag?
- jb enq_5
- add sp, 4
- ; lea ax, es:[di].u_ecb.ecb_link
- ; push ax
- push di ; push `prev'-link
- push es
- les di, es:[di].u_ecb.ecb_link ; load `next'-link
- jmp enq_4
-
- ; enter here with two addresses on the stack:
- ; 1) address of ecb to link in
- ; 2) address of link field after which to link
- ; es:di contains the "next" link.
-
- enq_5:
- mov ax, es ; temp = next
- mov bx, di
- pop es ; get prev
- pop di
- pop es:[di].segm ; prev->next = this one
- pop es:[di].offs
- les di, es:[di] ; load `this one'
- mov es:[di].u_ecb.ecb_link.segm, ax ; `this one'->next = temp
- mov es:[di].u_ecb.ecb_link.offs, bx
- add [si].q_len, cx ; update total queued data
- inc [si].q_count ; update fragcount
- cmp dh, [si].q_count ; return `zero' if all there
- clc
- ret
-
- enqueue endp
-
- dequeue proc near
- ; Send reassembled packet to client and reschedule receive buffers.
- ; On entry: ds:si -> queue.
-
- mov cx, [si].q_len
- les di, [si].q_ecb
- les di, es:[di].u_data_frag.frag_addr
- add di, 2 * EADDR_LEN
-
- mov dl, BLUEBOOK ;assume bluebook Ethernet.
- mov ax, es:[di]
- xchg ah, al
- cmp ax, 1500
- ja BlueBookPacket
- inc di ;set di to 802.2 header
- inc di
- mov dl, IEEE8023
- BlueBookPacket:
-
- push si
- call recv_find
- pop si
- mov ax, es
- or ax, di
- jz deq_2
-
- mov dh, [si].q_count
- mov cx, [si].q_len
- push si ; save our queue address
- push ds
- push di ; save their buffer address
- push es
- push cx
- lds si, ds:[si].q_ecb
- cld
-
- ;all set, es:di -> user buffer, ds:si -> first fragment
- ;??? save count and source pointer for call to recv_copy
-
- deq_1:
- mov cx, ds:[si].u_ipx.ipx_len
- xchg cl, ch
- sub cx, (SIZE ipx_header + SIZE ether_frag)
- push si
- push ds
- lds si, ds:[si].u_data_frag.frag_addr
- rep movsb
- pop ds
- pop si
- lds si, ds:[si].u_ecb.ecb_link
- dec dh
- jnz deq_1
-
- pop cx ; recover packet length and address
- pop ds ; for completion call
- pop si ;
- call recv_copy
-
- pop ds ; recover queue address
- pop si ;
-
- deq_2:
- mov ax, ds
- mov es, ax
- call_ipx CANCEL_EVENT,si
-
- push si
- mov dh, [si].q_count
- les si, ds:[si].q_ecb
-
- deq_3:
- mov bx, es:[si].ecb_link.offs
- mov cx, es:[si].ecb_link.segm
- call listen_proc
- mov si, bx
- mov es, cx
- ; les si, es:[si].u_ecb.ecb_link
- dec dh
- jnz deq_3
- pop si
- mov [si].q_count, 0
- ret
- dequeue endp
-
- reass_timeout proc far
- ; Called by AES when reassembly timeout occurs.
- ; On entry: es:si pointer to ecb.
- ;
-
- push ds
- mov ax, cs
- mov ds, ax
- push si
- push es
- mov dh, es:[si].q_count
- les si, es:[si].q_ecb
-
- reass_to_3:
- mov bx, es:[si].ecb_link.offs
- mov cx, es:[si].ecb_link.segm
- call listen_proc
- mov si, bx
- mov es, cx
- dec dh
- jnz reass_to_3
-
- pop es
- pop si
- mov es:[si].q_count, 0
-
- pop ds
- ret
- reass_timeout endp
-
- receiver proc far
- ;
- ; On entry: es:si -> ecb.
- ;
-
- push ds
- mov ax, cs
- mov ds, ax
- mov al, es:[si].u_ecb.ecb_cmplt
- or al, al
- jnz receiver_err
-
- cmp es:[si].u_ecb.ecb_fragcnt, NO_OF_FRAGMENTS
- jne receiver_err
-
- ;IPX receives its own broadcasts because
- ;source and destination sockets are the same
- ;if ours, ignore
-
- mov ax, si
- mov di, si
- add di, ecb_ia
- lea si, my_node_address
- mov cx, SIZE my_node_address
- cld
- repe cmpsb
- mov si, ax
- jz receiver_x
-
- mov cx, es:[si].u_ipx.ipx_len
- xchg cl, ch
- sub cx, (SIZE ipx_header + SIZE ether_frag)
- jbe receiver_err
-
- call enqueue
- jc receiver_err
- jnz rec_1
- call dequeue
-
- rec_1:
- pop ds
- cli
- ret
-
- receiver_err:
- call count_in_err
-
- receiver_x:
- call listen_proc ; post listen again
- pop ds
- cli ; must return with interrupts disabled, says Novell.
- ret
- receiver endp
-
- listen_proc proc near
- ;
- ; Post to u_buf for reception.
- ; On entry: es:si -> receive-ecb
- ;
-
- push bx
-
- ;fill in ecb
- mov es:[si].u_ecb.ecb_esr.offs, offset receiver
- mov ax, cs
- mov word ptr es:[si].u_ecb.ecb_esr.segm, ax
- mov es:[si].u_ecb.ecb_sock, IP_socket
- call_ipx LISTEN,es,si,di,dx,cx
-
- pop bx
- ret
-
- listen_proc endp
-
- rte_ticker proc far
- ;
- ; ESR service routine called by AES
- ; Ages all entries in routing table
- ; executes entirely with disabled interrupts
- ;
- ; On entry: es:si -> ecb
-
- push ds
- mov ax, cs
- mov ds, ax
-
- mov dx, rte_end
- lea di, rte
-
- rtick_1:
- add di, SIZE rt_ent ; leave broadcast entry alone
- cmp di, dx
- jae rtick_done
- mov ax, [di].rt_age
- add ax, 1
- jo rtick_1
- mov [di].rt_age, ax
- jmp rtick_1
-
- ;re-schedule ecb
- rtick_done:
- mov ax, RTE_TICK
- call_ipx SCHEDULE_SPECIAL_EVENT
- pop ds
- ret
- rte_ticker endp
-
- rte_enter proc near
- ;
- ; Enter route to table
- ; On entry: es:di -> ecb
- ;
- assume ds:nothing, es:nothing
-
- push bx
- push cx
- push dx
- push si
- push ds
- push di
- push es
-
- les di, es:[di].u_data_frag.frag_addr
- add di, EADDR_LEN ; es:di -> src node address
- mov bx, rte_rcache ; global, last succesful entry
- call rte_find ; will set DS to code seg
- jc ert_1
-
- ifdef STAT
- cmp rte_rcache, si
- je ert_stat_1
- statinc rcache_miss
- ert_stat_1:
- endif
-
- mov rte_rcache, si
- jmp ert_x ; we already have route to src
-
- ert_1:
- ;not in table, find free entry
- ; mov ax, cs
- ; mov ds, ax ; ds == code
-
- lea dx, rte
- add dx, SIZE rte ; dx -> beyond rte
- mov bx, rte_end ;
- add rte_end, SIZE rt_ent ; take the chance
- cmp bx, dx ; check whether table full
- jb ert_2
- sub rte_end, SIZE rt_ent ; undo guess
- call kill_route ; will leave freed entry in bx
-
- ert_2:
- ;insert addresses in ecb and ipx into routing table
- ; $%#@, must swap registers for string operations
- ;
- cld
- mov ax, ds
- mov cx, es
- mov es, ax
- mov ds, cx
- mov si, di ; ds:si -> source address
- mov di, bx ; es:di -> rt_ent
-
- add di, rt_ether
-
- repmov <SIZE rt_ether>
-
- pop ds ; recover ecb from stack
- pop si ;
- push si
- push ds
-
- mov ax, si
-
- add si, u_ipx + ipx_srcnet ; record source net+node
- repmov <SIZE rt_net + SIZE rt_node>
-
- mov si, ax ; restore si to start of ecb
- add si, ecb_ia ; record immediate address
- repmov <SIZE rt_gate>
-
- mov es:[bx].rt_x_pkt, PEP ; packet type
- mov es:[bx].rt_age, 10 ; usage count (XXX)
-
- ert_x:
- pop es
- pop di
- pop ds
- pop si
- pop dx
- pop cx
- pop bx
- ret
- rte_enter endp
-
- kill_route proc near
- ;
- ; Delete entry in route table with highest rt_age field
- ; Out: bx -> route entry freed
- ;
-
- push cx
- push dx
- push di
- lea di, rte
- add di, SIZE rt_ent ; leave broadcast entry alone
-
- mov dx, rte_end
- mov bx, di
- mov cx, 0
-
- krt_1:
- cmp di, dx
- je krt_done
- mov ax, [di].rt_age
- cmp ax, cx
- jbe krt_2
- mov cx, ax
- mov bx, di
-
- krt_2:
- add di, SIZE rt_ent
- jmp krt_1
-
- krt_done:
- ifdef STAT
- statinc route_drop
- endif
- pop di
- pop dx
- pop cx
- ret
- kill_route endp
-
-
- rte_find proc near
- ;
- ; Find a route for address
- ; On entry: es:di -> target address we are looking for (EADDR_LEN bytes)
- ; bx -> entry to start search
- ; Out: ds:si -> route table entry, or carry set if not found
- ;
- assume ds:code, es:nothing
-
- push bx
- push dx
- mov ax, cs ; cs == code segment
- mov ds, ax
-
- mov dx, rte_end ; global, points to first invalid rte entry
- mov ax, bx ; ax == stopper
- cld
-
- frt_1:
- ifdef STAT
- statinc route_loops
- endif
- mov si, bx ;
- add si, rt_ether ; si -> rt_ether field (= key) of current rte-entry
- push di ; save di
- mov cx, SIZE rt_ether
- repe cmpsb
- pop di
- je frt_x ; compare ok, report succes
- add bx, SIZE rt_ent ; next rte-entry
- cmp bx, dx
- jb frt_2
- lea bx, rte ; end of valid entries, wrap around
- frt_2:
- cmp bx, ax
- jne frt_1
- stc ; back where we started, report failure
-
- ;found, update cache and prepare output
- frt_x:
- mov si, bx
-
- ifdef STAT
- statinc route_lookups
- endif
- pop dx
- pop bx
- ret
- rte_find endp
-
- route proc near
- ;
- ; Determine where to send the packet
- ; On entry: ds:si -> user data, es:di -> ecb
- ;
- assume ds:nothing, es:nothing
-
- push bx
- push cx
- push ds
- push si
-
- ;find entry in routing table, in: es:di, out: ds:si -> pointer to table entry
-
- push di
- push es
- mov ax, ds ;
- mov es, ax ; es:di -> target address
- mov di, si ;
-
- mov bx, rte_scache ; global, last succesful entry
- call rte_find
- pop es
- pop di
- jc route_x
-
- ifdef STAT
- cmp rte_scache, si
- je route_stat_1
- statinc scache_miss
- route_stat_1:
- endif
-
- mov rte_scache, si ; remember this entry
- mov ax, ds:[si].rt_age ;
- sub ax, 1 ; decrement usage
- jc route_1
- mov ds:[si].rt_age, ax
-
- route_1:
- cld
- mov bx, di ; remember ecb in BX
-
- ifdef TRAIL
- ;clear ipx trail fields
- add di, u_ipx.ipx_trail
- mov ax, 0
- mov cx, (SIZE ipx_trail) / 2
- rep stosw
-
- mov di, bx
- endif
-
- ;fill in packet type and destination socket
- mov al, ds:[si].rt_x_pkt
- mov es:[di].u_ipx.ipx_type, al ;PEP
- mov es:[di].u_ipx.ipx_destsock, IP_socket
-
- ;fill in full destination adress
- mov ax, si ; save si
- add si, rt_net
- add di, u_ipx.ipx_destnet
- repmov <SIZE ipx_destnet + SIZE ipx_destnode>
- mov si, ax ; restore si, di
- mov di, bx ;
-
- ;fill in immediate address
- add di, ecb_ia
- add si, rt_gate
- repmov <SIZE ecb_ia>
- mov di, bx ;
-
- route_x:
- pop si
- pop ds
- pop cx
- pop bx
- ret
- route endp
-
- public int_no
- int_no db 0,0,0,0 ;must be four bytes long for get_number.
-
- public driver_class, driver_type, driver_name, driver_function, parameter_list
- driver_class db BLUEBOOK, IEEE8023, 0 ;from the packet spec
- driver_type db 1 ;from the packet spec
- driver_name db 'IPX',0 ;name of the driver.
- driver_function db 2
- parameter_list label byte
- db 1 ;major rev of packet driver
- db 9 ;minor rev of packet driver
- db 14 ;length of parameter list
- db EADDR_LEN ;length of MAC-layer address
- if NO_OF_SND_BUFS eq 1
- dw MAX_PAYLOAD - 2 * EADDR_LEN - 2 ;MTU, including MAC headers
- else
- dw GIANT ;MTU, including MAC headers
- endif
- dw MAX_MULTICAST * EADDR_LEN ;buffer size of multicast addrs
- dw 0 ;(# of back-to-back MTU rcvs) - 1
- dw 0 ;(# of successive xmits) - 1
- int_num dw 0 ;Interrupt # to hook for post-EOI
- ;processing, 0 == none,
-
- public rcv_modes
- rcv_modes dw 4 ;number of receive modes in our table.
- dw 0,0,0,rcv_mode_3
-
- public bad_command_intercept
- bad_command_intercept:
- ;called with ah=command, unknown to the skeleton.
- ;exit with nc if okay, cy, dh=error if not.
- mov dh,BAD_COMMAND
- stc
- ret
-
- public as_send_pkt
- ; The Asynchronous Transmit Packet routine.
- ; Enter with es:di -> i/o control block, ds:si -> packet, cx = packet length,
- ; interrupts possibly enabled.
- ; Exit with nc if ok, or else cy if error, dh set to error number.
- ; es:di and interrupt enable flag preserved on exit.
- as_send_pkt:
- ret
-
- public drop_pkt
- ; Drop a packet from the queue.
- ; Enter with es:di -> iocb.
- drop_pkt:
- assume ds:nothing
- ret
-
- public xmit
- ; Process a transmit interrupt with the least possible latency to achieve
- ; back-to-back packet transmissions.
- ; May only use ax and dx.
- xmit:
- assume ds:nothing
- ret
-
-
- public send_pkt
- send_pkt:
- ;enter with ds:si -> packet, cx = packet length.
- ;exit with nc if ok, or else cy if error, dh set to error number.
- assume ds:nothing
-
- cmp cx,GIANT ; Is this packet too large?
- ja send_pkt_toobig
-
- push es
- push di
- mov ax, cs
- mov es, ax
-
- ;first, compute number of fragments needed, keep in dx
- mov dx, 0
- mov ax, cx
-
- snd_1:
- inc dx
- sub ax, MAX_PAYLOAD
- jnc snd_1
-
- ;can we handle this amount?
- cmp dx, NO_OF_SND_BUFS
- jbe snd_frags_ok
-
- snd_err:
- call count_out_err
- pop di
- pop es
- mov dh, CANT_SEND
- stc
- ret
-
- send_pkt_toobig:
- mov dh,NO_SPACE
- stc
- ret
-
- snd_frags_ok:
- lea di, snd_bufs
- push cx
- mov cx, dx
- mov bx, 0
- mov al, 0
-
- snd_free_chk:
- or al, es:[di+bx].u_ecb.ecb_inuse
- add bx, SIZE u_buf
- loop snd_free_chk
-
- pop cx
- or al, al
- jnz snd_err
-
- mov dh, dl
- mov dl, 1
- mov bx, 0
- inc FragmentID
- push di
-
- snd_next_frag:
- ;
- ; dh = total number of fragments to send
- ; dl = current fragment
- ; bx = offset into client buffer (ds:si) for this fragment
- ; cx = bytes to go
- ; es:di = address of current fragment's ecb
- ;
-
- ;determine next hop
-
- call route ; XXX, should be done for first fragment only!
- jnc snd_frag1
- pop di
- jmp snd_err
-
- snd_frag1:
- ;fill in ecb
- mov ax, 0
- mov es:[di].u_ecb.ecb_esr.offs, ax
- mov es:[di].u_ecb.ecb_esr.segm, ax
-
- mov es:[di].u_ecb.ecb_sock, IP_socket
-
- mov es:[di].u_ether_frag.ef_fragtot, dh
- mov es:[di].u_ether_frag.ef_fragno, dl
- mov ax, FragmentID
- mov es:[di].u_ether_frag.ef_fragid, ax
-
- mov ax, ds
- mov es:[di].u_data_frag.frag_addr.segm, ax
-
- mov ax, MAX_PAYLOAD
- sub cx, ax
- jnc snd_frag2
- add ax, cx
-
- snd_frag2:
- mov es:[di].u_data_frag.frag_size, ax
- push si
- add si, bx
- mov es:[di].u_data_frag.frag_addr.offs, si
- add bx, ax
-
- ;
- ; es:si -> ecb to ship
- ;
-
- mov si, di
- call_ipx SEND_PACKET,es,di,dx,cx,bx
- pop si ; ds:si -> client buffer once more
-
- add di, SIZE u_buf ; next send buffer
- inc dl
- cmp dl, dh
- jbe snd_next_frag
-
- pop di
-
- ;simple timeout on sends
- mov cx, 0ffffh
-
- snd_wait:
- ;wait until sends are done
- sti
- mov bx, 0
- push cx
- mov ch, 0
- mov cl, dh ; dh still has fragment count
- mov al, 0
- snd_wait1:
- or al, es:[di+bx].u_ecb.ecb_inuse
- add bx, SIZE u_buf
- loop snd_wait1
- pop cx
-
- or al, al
- jz snd_done
- call_ipx RELINQUISH,es,di,dx,cx
- loop snd_wait
-
- ;arrive here on timeout, cancel IPX sends
- mov ch, 0
- mov cl, dh
- mov si, di
-
- snd_cancel:
- call_ipx CANCEL_EVENT,es,si,cx
- add si, SIZE u_buf
- loop snd_cancel
- jmp snd_err
-
- snd_done:
- ;check completion status of send buffers
- mov bx, 0
- mov ch, 0
- mov cl, dh
- mov al, 0
- snd_done1:
- or al, es:[di+bx].u_ecb.ecb_cmplt
- add bx, SIZE u_buf
- loop snd_done1
-
- or al, al
- jz snd_ok
- jmp snd_err
-
- snd_ok:
- pop di
- pop es
- ret
-
- public set_address
- set_address:
- ;enter with ds:si -> Ethernet address, CX = length of address.
- ;exit with nc if okay, or cy, dh=error if any errors.
- assume ds:nothing
- ret
-
-
- rcv_mode_3:
- ;receive mode 3 is the only one we support, so we don't have to do anything.
- ret
-
-
- public set_multicast_list
- set_multicast_list:
- ;enter with ds:si ->list of multicast addresses, ax = number of addresses,
- ; cx = number of bytes.
- ;return nc if we set all of them, or cy,dh=error if we didn't.
- mov dh,NO_MULTICAST
- stc
- ret
-
-
- public reset_interface
- reset_interface:
- ;reset the interface.
- assume ds:code
- ret
-
-
- ;called when we want to determine what to do with a received packet.
- ;enter with cx = packet length, es:di -> packet type, dl = packet class.
- extrn recv_find: near
-
- ;called after we have copied the packet into the buffer.
- ;enter with ds:si ->the packet, cx = length of the packet.
- extrn recv_copy: near
-
- ;call this routine to schedule a subroutine that gets run after the
- ;recv_isr. This is done by stuffing routine's address in place
- ;of the recv_isr iret's address. This routine should push the flags when it
- ;is entered, and should jump to recv_exiting_exit to leave.
- ;enter with ax = address of routine to run.
- extrn schedule_exiting: near
-
- ;recv_exiting jumps here to exit, after pushing the flags.
- extrn recv_exiting_exit: near
-
- ;enter with dx = amount of memory desired.
- ;exit with nc, dx -> that memory, or cy if there isn't enough memory.
- extrn malloc: near
-
- extrn count_in_err: near
- extrn count_out_err: near
-
- public recv
- recv:
- ;called from the recv isr. All registers have been saved, and ds=cs.
- ;Upon exit, the interrupt will be acknowledged.
- assume ds:code
- ret
-
-
- public terminate
- terminate:
- ;called when this driver should cease operation.
- assume ds:nothing
-
- ;close socket, outstanding listens should be cancelled automatically
- mov dx, IP_socket
- call_ipx CLOSE_SOCKET
-
- ; mov ax, cs
- ; mov es, ax
- ; mov cx, NO_OF_RCV_BUFS
- ; lea si, rcv_bufs
- ;
- ;terminate_1:
- ; call_ipx CANCEL_EVENT,es,si,cx
- ; add si, SIZE u_buf
- ; loop terminate_1
-
- ret
-
-
- public timer_isr
- timer_isr:
- ;if the first instruction is an iret, then the timer is not hooked
- iret
-
- ;any code after this will not be kept after initialization. any code
- ;after this will not be kept. Buffers used by the program, if any,
- ;are allocated from the memory between end_resident and end_free_mem.
- public end_resident,end_free_mem
- end_resident label byte
- db NO_OF_RCV_BUFS*MAX_PAYLOAD dup(?)
- end_free_mem label byte
-
-
- public usage_msg
- usage_msg db "usage: ipxpkt [options] <packet_int_no>"
- db " [-n [<no_bytes>]]",CR,LF,'$'
-
- public copyright_msg
- copyright_msg db "Packet driver for IPX, version ",'0'+(majver / 10),'0'+(majver mod 10),".",'0'+version,CR,LF
- db "Portions Copyright 1990, P. Kranenburg",CR,LF,'$'
-
- no_ipx_msg db "IPX not present",CR,LF, '$'
- wrong_sock_msg db "IPX has no good socket",CR,LF, '$'
- no_memory_msg db "Unable to allocate enough memory, look at end_resident in IPXPKT.ASM",CR,LF,'$'
- longwait_msg db "This may take up to 30 seconds...",CR,LF,'$'
- ifdef DEBUG
- debugmsg1 db "Doing a GET TARGET",CR,LF, '$'
- debugmsg2 db "Past GET TARGET",CR,LF, '$'
- debugmsg3 db "Past GET ADDRESS",CR,LF, '$'
- endif
-
- extrn set_recv_isr: near
-
- ;enter with si -> argument string, di -> word to store.
- ;if there is no number, don't change the number.
- extrn get_number: near
- extrn skip_blanks: near
-
- ;-> the assigned Ethernet address of the card.
- extrn rom_address: byte
-
- public parse_args
- parse_args:
-
- call skip_blanks
- cmp byte ptr [si], CR
- je parse_args_x
- cmp byte ptr [si], '-'
- jne parse_err
- cmp byte ptr [si+1],'n' ; '-n'?
- je parse_args_1
-
- parse_err:
- stc ;no, must be an error.
- ret
-
- parse_args_1:
- add si, 2
- call skip_blanks
- cmp byte ptr [si], CR
- jne parse_args_2
- mov no_bytes, 0ffffh ;try heuristic
- jmp parse_args_x
-
- parse_args_2:
- mov di,offset no_bytes
- call get_number
- cmp no_bytes, 0
- jb parse_err
- cmp no_bytes, EADDR_LEN
- ja parse_err
-
- parse_args_x:
- clc
- ret
-
-
- public etopen
- etopen:
-
- ;first see if IPX is there
- mov ax, 07A00h
- int 2fh
- cmp al, 0ffh
- je ipx_is_here
- mov dx,offset no_ipx_msg
- stc
- ret
-
- ipx_is_here:
- mov ax, es
- mov IPXentry.offs, di
- mov IPXentry.segm, ax
-
- ;close socket first, since "head" won't notify us on termination
- mov dx, IP_socket
- call_ipx CLOSE_SOCKET
-
- ;next open socket
- mov al, 0ffh ; stay open until explicitly closed
- mov dx, IP_socket
- call_ipx OPEN_SOCKET
- or al, 0
- jnz wrong_socket
- cmp dx, IP_socket
- je good_socket
-
- ;close socket and exit
- wrong_socket:
- call_ipx CLOSE_SOCKET
- mov dx,offset wrong_sock_msg
- stc
- ret
-
-
- no_memory:
- call_ipx CLOSE_SOCKET
- mov dx,offset no_memory_msg
- stc
- ret
-
- good_socket:
- ;init send buffer fragment list
- mov ax, cs
- mov es, ax
-
- mov cx, NO_OF_SND_BUFS
- lea si, snd_bufs
-
- et_1:
- mov es:[si].u_ecb.ecb_fragcnt, NO_OF_FRAGMENTS
-
- mov bx, si ; bx = offset ipx_header
- add bx, u_ipx
- mov es:[si].u_ipx_frag.frag_addr.offs, bx
- mov es:[si].u_ipx_frag.frag_addr.segm, ax
- mov es:[si].u_ipx_frag.frag_size, SIZE ipx_header
-
- mov bx, si ; bx = offset ether_frag
- add bx, u_ether_frag
- mov es:[si].u_frag_frag.frag_addr.offs, bx
- mov es:[si].u_frag_frag.frag_addr.segm, ax
- mov es:[si].u_frag_frag.frag_size, SIZE ether_frag
- ;
- ; NOTE: u_data_frag is initialised send_pkt with address of user data buffer
- ;
- add si, SIZE u_buf
- loop et_1
-
- ;initialise receive buffer ipx data fragment addresses
- ;and start listening on each
- mov cx, NO_OF_RCV_BUFS
- lea si, rcv_bufs
- et_2:
- mov es:[si].u_ecb.ecb_fragcnt, NO_OF_FRAGMENTS
-
- mov bx, si
- add bx, u_ipx
- mov es:[si].u_ipx_frag.frag_addr.offs, bx
- mov es:[si].u_ipx_frag.frag_addr.segm, ax
- mov es:[si].u_ipx_frag.frag_size, SIZE ipx_header
-
- mov bx, si
- add bx, u_ether_frag
- mov es:[si].u_frag_frag.frag_addr.offs, bx
- mov es:[si].u_frag_frag.frag_addr.segm, ax
- mov es:[si].u_frag_frag.frag_size, SIZE ether_frag
-
- mov dx,MAX_PAYLOAD
- call malloc
- jnc got_memory
- jmp no_memory
- got_memory:
- mov es:[si].u_data_frag.frag_addr.offs, dx
- mov es:[si].u_data_frag.frag_addr.segm, ax
- mov es:[si].u_data_frag.frag_size, MAX_PAYLOAD
-
- add si, SIZE u_buf
- loop et_2
-
- ;heuristic to force network traffic which seems to necessary before
- ;the GET_NODE_ADDRESS call will work properly (boo,hiss)
-
- ;get our address
- sti
- lea si, my_net_address
- call_ipx GET_NODE_ADDRESS,es,si
- cmp byte ptr es:[si], 0
- jne et_3
- cmp byte ptr es:[si+1], 0
- jne et_3
- cmp byte ptr es:[si+2], 0
- jne et_3
- cmp byte ptr es:[si+3], 0
- jne et_3
-
- ifdef TRY_GET_LOCAL_TARGET
- ; if the network address came back as 0:0:0:0 then do a GET_LOCAL_TARGET
- ; before trying the GET_NODE_ADDRESS again.
- ; XXX: There must be a better way of doing this, but for now we just
- ; XXX: warn the user that it will take a long time.
- push es
- print$ longwait_msg
- ifdef DEBUG
- print$ debugmsg1
- endif
- lea si, dummy
- mov di, si
- add di, 12
- call_ipx GET_LOCAL_TARGET
- ifdef DEBUG
- print$ debugmsg2
- endif
- pop es
-
- ;get our address
- lea si, my_net_address
- call_ipx GET_NODE_ADDRESS,es,si
- ifdef DEBUG
- push es
- push si
- print$ debugmsg3
- pop si
- pop es
- endif
- endif
-
- et_3:
- ;initialise routing table with the broadcast address
- cld
- lea di, rte
- mov rte_end, di ; rte_end = second entry
- add rte_end, SIZE rt_ent ;
- mov rte_scache, di ; rte_cache = first (and only) entry
- mov rte_rcache, di ;
- mov es:[di].rt_age, 0
- mov es:[di].rt_x_pkt, GBP ; broadcast packet type
- ;; mov es:[di].rt_trail, 1 ; uses trail
-
- add di, rt_ether ; broadcast address
- mov al, 0ffh
- mov cx, SIZE rt_ether
- rep stosb
-
- push ds
- mov ax, cs
- mov ds, ax
- mov cx, SIZE rt_net
- rep movsb ; net field set from my_net_address
- pop ds
-
- mov al, 0ffh
- mov cx, SIZE rt_node + SIZE rt_gate
- rep stosb ; all FF's
-
- ;schedule periodic aging of route table entries
- lea si, rte_aes
- mov ax, cs
- mov es:[si].aes_esr.segm, ax
- mov es:[si].aes_esr.offs, offset rte_ticker
- mov ax, RTE_TICK
- call_ipx SCHEDULE_SPECIAL_EVENT, es
-
- movseg es,ds
- mov di,offset rom_address
-
- cmp init_cmplt, 1
- jnc ga_1
- call listen_init
- mov init_cmplt, 1
-
- ga_1:
- lea si, my_node_address ; first copy IPX node address
- cld
- repmov <EADDR_LEN>
-
- mov cx, EADDR_LEN ; if not significant enough,
- cmp no_bytes, 0ffffh ; magic in no_bytes?
- jne ga_3
- lea bx, my_node_address ; they don't know
- mov dx, bx ; figure out something for no_bytes
- add dx, SIZE my_node_address ; savety
- mov no_bytes, EADDR_LEN
-
- ga_2:
- cmp byte ptr [bx], 0
- jnz ga_3
- dec no_bytes
- inc bx
- cmp bx, dx
- je ga_3
- jmp ga_2
-
- ga_3:
- sub cx, no_bytes ; copy some of IPX net address
- jcxz get_addr_x
- cmp cx, SIZE my_net_address
- jbe ga_4
- mov cx, SIZE my_net_address
- ga_4:
- lea si, my_net_address
- sub di, EADDR_LEN
-
- ;next are three different methods for constructing the reported ethernet address
- ;from a less-than-6-bytes-long IPX node adress and the IPX net address.
- if ETH_CONSTR eq 1
-
- ; copy (the first few bytes of) my_net_address
- rep movsb
-
- elseif ETH_CONSTR eq 2
-
- ; copy (the last few bytes of) my_net_address, in reverse order
- ga_21:
- mov bx, SIZE my_net_address - 1
- mov al, [si+bx]
- stosb
- dec bx
- loop ga_21
-
- elseif ETH_CONSTR eq 3
-
- ; if there is enough space, copy the whole of my_net_address
- cmp cx, SIZE my_net_address
- jl ga_3_b
- rep movsb
- jmp ga_3_end
-
- ; no space for the whole of my_net_address, so go through
- ; this weird method of choosing just a few bytes to copy
- ga_3_b:
- push di
- push si
- push es
- mov ax, ds
- mov es, ax
- lea di, dummy
- repmov <SIZE my_net_address>
- pop es
- pop si
- pop di
-
- lea si, dummy
-
- ga_3_loop:
- mov bx, 0
- mov dx, 0
- mov al, 0
- ga_31:
- cmp bx, SIZE my_net_address
- jz ga_33
- mov ah, [si+bx]
- cmp al, ah
- jnc ga_32
- mov al, ah
- mov dx, bx
- ga_32:
- inc bx
- jmp ga_31
- ga_33:
- stosb
- mov bx, dx
- mov byte ptr [si+bx], 0
- loop ga_3_loop
- ga_3_end:
-
- endif
-
- get_addr_x:
-
- ;if all is okay,
- clc
- ret
-
- listen_init proc near
- ;and start listening on each receive buffer
- push si
- push cx
- push es
- mov ax, cs
- mov es, ax
- mov cx, NO_OF_RCV_BUFS
- lea si, rcv_bufs
- li_1:
- call listen_proc
- add si, SIZE u_buf
- loop li_1
- pop es
- pop cx
- pop si
- ret
- listen_init endp
-
-
- public print_parameters
- print_parameters:
- ret
-
- code ends
-
- end
-