version equ 3 page ,132 include defs.asm ;/* PC/FTP Packet Driver source, conforming to version 1.05 of the spec, ;* for the NE2000 interface card. ;* Robert C Clements, K1BC, 14 February, 1989 ;* Portions (C) Copyright 1988, 1989 Robert C Clements ;* Modified from 3com503 driver by D.J.Horne ;* ; Copyright, 1988, 1989, Russell Nelson ; This program is free software; you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation, version 1. ; ; This program is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License ; along with this program; if not, write to the Free Software ; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ;* Change history: ;* Updated to driver spec version 1.08 Feb. 17, 1989 by Russell Nelson. ;* Changes 27 Jul 89 by Bob Clements (/Rcc) ;* Added Thick versus Thin Ethernet switch 27 Jul 89 by Bob Clements (/Rcc) ;* Added call to memory_test. ;* Added rcv_mode logic. Started, but didn't finish, multicast logic. ;* Fixed get_address to return current, not PROM, address. ;* Minor races fixed. ;* Changes 19 Oct 89, Dave Horne ;* Modified for NE2000, use i/o instead of shared memory, ;* remove thick/thin logic, remove gate array logic code segment word public assume cs:code, ds:code ; Stuff specific to the NE2000 Ethernet controller board ; WD version in C by Bob Clements, K1BC, May 1988 for the KA9Q TCP/IP package ; 3Com version based on WD8003E version in .ASM, also by Bob Clements, dated ; 19 August 1988. The WD and 3Com cards both use the National DS8390. ; NE2000 based on 3COM503 version. ; Symbol prefix "EN" is for Ethernet, National chip ; Symbol prefix "NE" is for NE2000 register(s) ; The EN registers - the DS8390 chip registers ; These appear at Base+0 through Base+0F ; There are two (really 3) pages of registers in the chip. You select ; which page you want, then address them at offsets 00-0F from base. ; The chip command register (EN_CCMD) appears in both pages. EN_CCMD equ 000h ; Chip's command register ; Page 0 EN0_STARTPG equ 001h ; Starting page of ring bfr EN0_STOPPG equ 002h ; Ending page +1 of ring bfr EN0_BOUNDARY equ 003h ; Boundary page of ring bfr EN0_TSR equ 004h ; Transmit status reg EN0_TPSR equ 004h ; Transmit starting page EN0_TCNTLO equ 005h ; Low byte of tx byte count EN0_TCNTHI equ 006h ; High byte of tx byte count EN0_ISR equ 007h ; Interrupt status reg EN0_RSARLO equ 008h ; Remote start address reg 0 EN0_RSARHI equ 009h ; Remote start address reg 1 EN0_RCNTLO equ 00ah ; Remote byte count reg EN0_RCNTHI equ 00bh ; Remote byte count reg EN0_RXCR equ 00ch ; RX control reg EN0_TXCR equ 00dh ; TX control reg EN0_COUNTER0 equ 00dh ; Rcv alignment error counter EN0_DCFG equ 00eh ; Data configuration reg EN0_COUNTER1 equ 00eh ; Rcv CRC error counter EN0_IMR equ 00fh ; Interrupt mask reg EN0_COUNTER2 equ 00fh ; Rcv missed frame error counter ; Page 1 EN1_PHYS equ 001h ; This board's physical enet addr EN1_CURPAG equ 007h ; Current memory page EN1_MULT equ 008h ; Multicast filter mask array (8 bytes) ; Board regs NE_DATAPORT equ 10h NE_OTHERPORT equ 1fh ; Chip commands in EN_CCMD ENC_STOP equ 001h ; Stop the chip ENC_START equ 002h ; Start the chip ENC_TRANS equ 004h ; Transmit a frame ENC_RREAD equ 008h ; remote read ENC_RWRITE equ 010h ; remote write ENC_NODMA equ 020h ; No remote DMA used on this card ENC_PAGE0 equ 000h ; Select page 0 of chip registers ENC_PAGE1 equ 040h ; Select page 1 of chip registers ; Commands for RX control reg ENRXCR_MON equ 020h ; Monitor mode (no packets rcvd) ENRXCR_PROMP equ 010h ; Promiscuous physical addresses ENRXCR_MULTI equ 008h ; Multicast (if pass filter) ENRXCR_BCST equ 004h ; Accept broadcasts ENRXCR_BAD equ 003h ; Accept runts and bad CRC frames ; Commands for TX control reg ENTXCR_LOOP equ 002h ; Set loopback mode ; Bits in EN0_DCFG - Data config register ENDCFG_BM8 equ 049h ; Set burst mode, 8 deep FIFO, words ; Bits in EN0_ISR - Interrupt status register ENISR_RX equ 001h ; Receiver, no error ENISR_TX equ 002h ; Transmitter, no error ENISR_RX_ERR equ 004h ; Receiver, with error ENISR_TX_ERR equ 008h ; Transmitter, with error ENISR_OVER equ 010h ; Receiver overwrote the ring ENISR_COUNTERS equ 020h ; Counters need emptying ENISR_RDC equ 040h ; remote dma complete ENISR_RESET equ 080h ; Reset completed ENISR_ALL equ 03fh ; Interrupts we will enable ; Bits in received packet status byte and EN0_RSR ENPS_RXOK equ 001h ; Received a good packet ; Bits in TX status reg ENTSR_PTX equ 001h ; Packet transmitted without error ENTSR_COLL equ 004h ; Collided at least once ENTSR_COLL16 equ 008h ; Collided 16 times and was dropped ENTSR_FU equ 020h ; TX FIFO Underrun ; Shared memory management parameters XMIT_MTU equ 600h ; Largest packet we have room for. SM_TSTART_PG equ 040h ; First page of TX buffer SM_RSTART_PG equ 046h ; Starting page of RX ring SM_RSTOP_PG equ 080h ; Last page +1 of RX ring ; Description of header of each packet in receive area of memory EN_RBUF_STAT equ 0 ; Received frame status EN_RBUF_NXT_PG equ 1 ; Page after this frame EN_RBUF_SIZE_LO equ 2 ; Length of this frame EN_RBUF_SIZE_HI equ 3 ; Length of this frame EN_RBUF_NHDR equ 4 ; Length of above header area ; End of NE2000 parameter definitions pause_ macro jmp $+2 endm longpause macro push cx mov cx,0 loop $ pop cx endm ; The following two values may be overridden from the command line. ; If they are omitted from the command line, these defaults are used. ; The shared memory base is set by a jumper. We read it from the ; card and set up accordingly. public int_no, io_addr int_no db 2,0,0,0 ; Interrupt level io_addr dw 0300h,0 ; I/O address for card (jumpers) public driver_class, driver_type, driver_name, driver_function, parameter_list driver_class db 1 ;from the packet spec driver_type db 54 ;from the packet spec driver_name db 'NE2000',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 dw GIANT ;MTU, including MAC headers 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, rxcr_bits db ENRXCR_BCST ; Default to ours plus multicast public card_hw_addr, curr_hw_addr, mcast_list_bits, mcast_all_flag card_hw_addr db 0,0,0,0,0,0 ;Physical ethernet address curr_hw_addr db 0,0,0,0,0,0 ;Address set into the 8390 mcast_list_bits db 0,0,0,0,0,0,0,0 ;Bit mask from last set_multicast_list mcast_all_flag db 0 ;Non-zero if hware should have all ; ones in mask rather than this list. mcast_sw_filter db 0 ; set if software filter is required. is_186 db 0 mcast_sw_fin dw 0 mcast_sw_fout dw 0 public rcv_modes rcv_modes dw 7 ;number of receive modes in our table. dw 0 ;There is no mode zero dw rcv_mode_1 dw rcv_mode_2 dw rcv_mode_3 dw rcv_mode_4 dw rcv_mode_5 dw rcv_mode_6 public mcast_tab mcast_hcount dw 0 ; multicast header count mcast_tab_b db 0ffh,0ffh,0ffh,0ffh,0ffh,0ffh ; entry for broadcast mcast_tab db (MAX_MULTICAST*EADDR_LEN) dup (0) ; ; a temp buffer for the received header ; RCV_HDR_SIZE equ 18 ; 2 ids @6 + protocol, + 4byte header rcv_hdr db RCV_HDR_SIZE dup(0) ; ; The board data ; public board_data BOARD_DATA_SIZE equ 32 board_data db BOARD_DATA_SIZE dup(0) soft_tx_errors dw 0,0 soft_tx_err_bits db 0 soft_rx_errors dw 0,0 soft_rx_err_bits db 0 ; send_pkt: - The Transmit Frame routine 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 loadport ; Point at chip command register setport EN_CCMD ; .. pause_ mov bx, 8000h ; Avoid infinite loop tx_wait: in al, dx ; Get chip command state test al,ENC_TRANS ; Is transmitter still running? jz tx_idle ; Go if free dec bx ; Count the timeout jnz tx_wait ; Fall thru if TX is stuck call count_out_err ; Should count these error timeouts ; Maybe need to add recovery logic here tx_idle: cmp cx,XMIT_MTU ; Is this packet too large? ja send_pkt_toobig cmp cx, RUNT ; Is the frame long enough? jnb tx_oklen ; Go if OK mov cx, RUNT ; Stretch frame to minimum allowed tx_oklen: push cx ; Hold count for later loadport ; Set up for address setport EN0_ISR pause_ mov al,ENISR_RDC ; clear remote interrupt int. out dx,al setport EN0_TCNTLO ; Low byte of TX count pause_ mov al, cl ; Get the count out dx, al ; Tell card the count setport EN0_TCNTHI ; High byte of TX count pause_ mov al, ch ; Get the count out dx, al ; Tell card the count xor ax, ax ; Set up ax at base of tx buffer mov ah, SM_TSTART_PG ; Where to put tx frame pop cx ; Get back count to give to board call block_output loadport mov cx,0 setport EN0_ISR in al,dx tx_check_rdc: test al,ENISR_RDC ; dma done ??? jnz tx_start loop tx_check_rdc jmp tx_no_rdc tx_start: setport EN0_TPSR ; Transmit Page Start Register pause_ mov al, SM_TSTART_PG out dx, al ; Start the transmitter setport EN_CCMD ; Chip command reg pause_ mov al, ENC_TRANS+ENC_NODMA+ENC_START out dx, al ; Start the transmitter clc ; Successfully started sti ret ; End of transmit-start routine send_pkt_toobig: mov dh,NO_SPACE stc sti ret tx_no_rdc: mov dh,CANT_SEND stc sti ret count_soft_err: add word ptr soft_tx_errors,1 adc word ptr soft_tx_errors+2,0 or byte ptr soft_tx_err_bits,al ret include movemem.asm public get_address get_address: ;get the address of the interface. ;enter with es:di -> place to get the address, cx = size of address buffer. ;exit with nc, cx = actual size of address, or cy if buffer not big enough. ; Give caller the one currently in the 8390, not necessarily the one in PROM. assume ds:code cmp cx, EADDR_LEN ; Caller wants a reasonable length? jb get_addr_x ; No, fail. mov cx, EADDR_LEN ; Move one ethernet address from our copy mov si, offset curr_hw_addr ; Copy from most recent setting rep movsb mov cx, EADDR_LEN ; Tell caller how many bytes we fed him clc ; Carry off says success ret get_addr_x: stc ; Tell caller our addr is too big for him ret ; ;get the board data. This is (16) bytes starting at remote ;dma address 0. Put it in a buffer called board_data. get_board_data: mov cx,10h ; get 16 bytes, push ds pop es ; set es to ds mov di,offset board_data mov ax,0 ; from address 0 call sp_block_input ret public set_address set_address: assume ds:nothing ;enter with ds:si -> Ethernet address, CX = length of address. ;exit with nc if okay, or cy, dh=error if any errors. ; cmp cx,EADDR_LEN ;ensure that their address is okay. je set_address_4 mov dh,BAD_ADDRESS stc jmp short set_address_done set_address_4: push cs ; Copy from them to our RAM copy pop es ; Destination of move mov di, offset curr_hw_addr rep movsb ; Move their address call set_8390_eaddr ; Put that address in the chip set_address_okay: mov cx,EADDR_LEN ;return their address length. clc set_address_done: push cs pop ds assume ds:code ret ; Copy our Ethernet address from curr_hw_addr into the DS8390 set_8390_eaddr: push cs ; Get it from our local RAM copy pop ds mov si, offset curr_hw_addr mov cx, EADDR_LEN ; Move one ethernet address from our copy loadport setport EN_CCMD ; Chip command register pause_ cli ; Protect from irq changing page bits mov al, ENC_NODMA+ENC_PAGE1+ENC_STOP out dx, al ; Switch to page one for writing eaddr setport EN1_PHYS ; Where it goes in 8390 pause_ set_8390_1: lodsb out dx,al inc dx loop set_8390_1 loadport setport EN_CCMD ; Chip command register pause_ mov al, ENC_NODMA+ENC_PAGE0+ENC_STOP out dx, al ; Restore to page zero sti ; OK for interrupts now ret ; Routines to set address filtering modes in the DS8390 rcv_mode_1: ; Turn off receiver mov al, ENRXCR_MON ; Set to monitor for counts but accept none jmp short rcv_mode_set rcv_mode_2: ; Receive only packets to this interface mov al, 0 ; Set for only our packets jmp short rcv_mode_set rcv_mode_3: ; Mode 2 plus broadcast packets (This is the default) mov al, ENRXCR_BCST ; Set four ours plus broadcasts jmp short rcv_mode_set rcv_mode_4: ; Mode 3 plus selected multicast packets mov al, ENRXCR_BCST+ENRXCR_MULTI ; Ours, bcst, and filtered multicasts mov mcast_all_flag,0 ; need to do sw filter. mov mcast_sw_filter,1 ; because chip filter is not 100% jmp short rcv_mode_set rcv_mode_5: ; Mode 3 plus ALL multicast packets mov al, ENRXCR_BCST+ENRXCR_MULTI ; Ours, bcst, and filtered multicasts mov mcast_all_flag,1 jmp short rcv_mode_set rcv_mode_6: ; Receive all packets (Promiscuous physical plus all multi) mov al, ENRXCR_BCST+ENRXCR_MULTI+ENRXCR_PROMP mov mcast_all_flag,1 rcv_mode_set: push ax ; Hold mode until masks are right call set_8390_multi ; Set the multicast mask bits in chip pop ax loadport setport EN0_RXCR ; Set receiver to selected mode pause_ out dx, al mov rxcr_bits,al ; Save a copy of what we set it to ret public set_multicast_list set_multicast_list: ;enter with ds:si ->list of multicast addresses, cx = number of addresses. ;return nc if we set all of them, or cy,dh=error if we didn't. assume ds:nothing push cs pop es ; set es to destination mov di,offset mcast_tab mov ax,cx ; save byte count repz movsb mov dx,0 mov cx,6 div cx mov mcast_hcount,ax ; mov word ptr mcast_list_bits,0 mov word ptr mcast_list_bits+2,0 mov word ptr mcast_list_bits+4,0 mov word ptr mcast_list_bits+6,0 ; mov cx,mcast_hcount inc cx mov di,offset mcast_tab_b set_mcl_1: call add_mc_bits add di,6 loop set_mcl_1 call set_8390_multi ; Set the multicast mask bits in chip clc mov dh,0 ret ; ; multicast is at es:di assume ds:nothing add_mc_bits: push cx push di mov cx,6 mov dx,0ffffh ; this is msw. mov bx,0ffffh ; set 32 bit number add_mcb_1: mov al,es:[di] inc di call upd_crc ; update crc loop add_mcb_1 ; and loop. mov ah,0 mov al,dh ; get ms 8 bits, rol al,1 rol al,1 rol al,1 ; put 3 bits at bottom and al,7 mov dl,al ; save in dl mov al,dh ; get ms 8 bits, ror al,1 ror al,1 ; but at bottom and al,7 mov cl,al ; save in cl mov al,1 rol al,cl ; set the correct bit, mov di,offset mcast_list_bits mov dh,0 add di,dx or cs:[di],al pop di pop cx ret ; ; dx is high, ; bx is low. ; al is data upd_crc: push cx mov cx,8 ; do 8 bits mov ah,0 upd_crc1: shl bx,1 ; shift bx rcl dx,1 ; through dx rcl ah,1 ; carry is at bottom of ah xor ah,al ; xor with lsb of data rcr ah,1 ; and put in carry bit jnc upd_crc2 ; ; autodin is x^32+x^26+x^23x^22+x^16+ ; x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+1 xor dx,0000010011000001b xor bx,0001110110110111b upd_crc2: shr al,1 ; shift the data loop upd_crc1 pop cx ret ; Set the multicast filter mask bits in case promiscuous rcv wanted set_8390_multi: push cs pop ds assume ds:code loadport setport EN_CCMD ; Chip command register pause_ mov cx, 8 ; Eight bytes of multicast filter mov si, offset mcast_list_bits ; Where bits are, if not all ones cli ; Protect from irq changing page bits mov al, ENC_NODMA+ENC_PAGE1+ENC_STOP out dx, al ; Switch to page one for writing eaddr setport EN1_MULT ; Where it goes in 8390 pause_ mov al, mcast_all_flag ; Want all ones or just selected bits? or al, al je set_mcast_2 ; Just selected ones mov al, 0ffh ; Ones for filter set_mcast_all: out dx, al ; Write a mask byte inc dl ; Step to next one loop set_mcast_all ; .. jmp short set_mcast_x set_mcast_2: lodsb ; Get a byte of mask bits out dx, al ; Write a mask byte inc dl ; Step to next I/O register loop set_mcast_2 ; .. set_mcast_x: loadport setport EN_CCMD ; Chip command register pause_ mov al, ENC_NODMA+ENC_PAGE0+ENC_START out dx, al ; Restore to page zero sti ; OK for interrupts now ret public reset_chip reset_chip: assume ds:nothing loadport ; Base of I/O regs setport NE_OTHERPORT in al,dx longpause out dx,al ; should set command 21, 80 longpause setport EN_CCMD ; Chip command reg pause_ mov al, ENC_STOP+ENC_NODMA out dx, al ; Stop the DS8390 setport EN0_ISR pause_ mov cx,0 reset_chip_loop: in al,dx ; get isr and al,ENISR_RESET jnz reset_chip_done jmp reset_chip_loop reset_chip_done: ret public terminate terminate: ret public reset_interface reset_interface: assume ds:code call reset_chip loadport ; Base of I/O regs setport EN0_ISR ; Interrupt status reg pause_ mov al, 0ffh ; Clear all pending interrupts out dx, al ; .. setport EN0_IMR ; Interrupt mask reg pause_ xor al, al ; Turn off all enables out dx, al ; .. ret ; ; Special case Block input routine. Used on extra memory ; space for board ID etc. DMA count is set X2, ; CX = byte count, es:si = buffer location, ax = buffer address sp_block_input: push ax ; save buffer address loadport setport EN_CCMD pause_ mov al,ENC_NODMA+ENC_STOP out dx,al ; stop & clear the chip setport EN0_RCNTLO ; remote byte count 0 pause_ mov ax,cx add ax,ax out dx,al setport EN0_RCNTHI pause_ mov al,ah out dx,al pop ax ; get our page back setport EN0_RSARLO pause_ out dx,al ; set as hi address setport EN0_RSARHI pause_ mov al,ah out dx,al setport EN_CCMD pause_ mov al,ENC_RREAD+ENC_START ; read and start out dx,al setport NE_DATAPORT pause_ jmp read_loop ; ; Block input routine ; CX = byte count, es:si = buffer location, ax = buffer address public block_input block_input: push ax ; save buffer address loadport setport EN_CCMD pause_ mov al,ENC_NODMA+ENC_PAGE0+ENC_START out dx,al setport EN0_RCNTLO ; remote byte count 0 pause_ mov al,cl out dx,al setport EN0_RCNTHI pause_ mov al,ch out dx,al pop ax ; get our page back setport EN0_RSARLO pause_ out dx,al ; set as hi address setport EN0_RSARHI pause_ mov al,ah out dx,al setport EN_CCMD pause_ mov al,ENC_RREAD+ENC_START ; read and start out dx,al setport NE_DATAPORT pause_ cmp byte ptr is_186,0 jnz read_186 read_loop: in al,dx ; get a byte stosb ; save it loop read_loop ret read_186: inc cx ; make even shr cx,1 ; word count db 0f3h, 06dh ;masm 4.0 doesn't grok "rep insw" ret ; ; Block output routine ; CX = byte count, ds:si = buffer location, ax = buffer address block_output: assume ds:nothing push ax ; save buffer address inc cx ; make even and cx,0fffeh loadport setport EN_CCMD pause_ mov al,ENC_NODMA+ENC_START out dx,al ; stop & clear the chip setport EN0_RCNTLO ; remote byte count 0 pause_ mov al,cl out dx,al setport EN0_RCNTHI pause_ mov al,ch out dx,al pop ax ; get our page back setport EN0_RSARLO pause_ out dx,al ; set as lo address setport EN0_RSARHI pause_ mov al,ah out dx,al setport EN_CCMD pause_ mov al,ENC_RWRITE+ENC_START ; write and start out dx,al setport NE_DATAPORT pause_ cmp byte ptr is_186,0 jnz write_186 write_loop: lodsb ; get a byte out dx,al ; save it loop write_loop ret write_186: shr cx,1 ; word count db 0f3h, 06fh ;masm 4.0 doesn't grok "rep outsw" ret ; Linkages to non-device-specific routines ;called when we want to determine what to do with a received packet. ;enter with cx = packet length, es:di -> packet type. ;It returns with es:di = 0 if don't want this type or if no buffer available. 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 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. ;Actually, not just receive, but all interrupts come here. ;Upon exit, the interrupt will be acknowledged. assume ds:code check_isr: ; Was there an interrupt from this card? loadport ; Point at card's I/O port base setport EN0_IMR ; point at interrupt masks pause_ ; switch off, this way we can mov al,0 ; leave the chip running. out dx,al ; no interrupts please. setport EN0_ISR ; Point at interrupt status register pause_ in al, dx ; Get pending interrupts and al, ENISR_ALL ; Any? jnz isr_test_overrun jmp interrupt_done ; Go if none ; First, a messy procedure for handling the case where the rcvr ; over-runs its ring buffer. This is spec'ed by National for the chip. ; This is handled differently in sample code from 3Com and from WD. ; This is close to the WD version. May need tweaking if it doesn't ; work for the 3Com card. isr_test_overrun: test al,ENISR_OVER ; Was there an overrun? jnz recv_overrun ; Go if so. jmp recv_no_overrun ; Go if not. recv_overrun: setport EN_CCMD ; Stop the chip pause_ mov al, ENC_STOP+ENC_NODMA out dx, al ; Write "stop" to command register mov al, ENC_NODMA+ENC_PAGE1 ; Could be in previous out, but out dx,al ; was only tested this way setport EN1_CURPAG ; Get current page in al,dx mov bl,al ; save it setport EN_CCMD ; mov al, ENC_NODMA+ENC_PAGE0 out dx,al ; Back to page 0 ; Remove one frame from the ring setport EN0_BOUNDARY ; Find end of this frame pause_ in al, dx ; Get memory page number inc al ; Page plus 1 cmp al, SM_RSTOP_PG ; Wrapped around ring? jnz rcv_ovr_nwrap ; Go if not mov al, SM_RSTART_PG ; Yes, wrap the page pointer rcv_ovr_nwrap: cmp al,bl ; Check if buffer emptry je rcv_ovr_empty ; Yes ? Don't receive anything mov ah,al ; make a byte address. e.g. page mov bl,ah ; and save in bl mov al,0 ; 46h becomes 4600h into buffer mov cx,RCV_HDR_SIZE ; size of rcv_hdr mov di,offset rcv_hdr ;point to header push ds pop es ; set es to right place call block_input mov al, rcv_hdr+EN_RBUF_STAT ; Get the buffer status byte test al,ENPS_RXOK ; Is this frame any good? jz rcv_ovr_ng ; Skip if not call rcv_frm ; Yes, go accept it rcv_ovr_ng: mov al, rcv_hdr+EN_RBUF_NXT_PG ; Get pointer to next frame dec al ; Back up one page cmp al, SM_RSTART_PG ; Did it wrap? jge rcv_ovr_nwr2 mov al, SM_RSTOP_PG-1 ; Yes, back to end of ring rcv_ovr_nwr2: loadport ; Point at boundary reg setport EN0_BOUNDARY ; .. pause_ out dx, al ; Set the boundary rcv_ovr_empty: setport EN0_RCNTLO ; Point at byte count regs pause_ xor al, al ; Clear them out dx, al ; .. setport EN0_RCNTHI pause_ out dx, al setport EN0_ISR ; Point at status reg pause_ mov cx, 8000h ; Timeout counter rcv_ovr_rst_loop: in al, dx ; Is it finished resetting? test al,ENISR_RESET ; .. jnz rcv_ovr_rst ; Go if so dec cx ; Loop til reset, or til timeout jnz rcv_ovr_rst_loop rcv_ovr_rst: loadport ; Point at Transmit control reg setport EN0_TXCR ; .. pause_ mov al, ENTXCR_LOOP ; Put transmitter in loopback mode out dx, al ; .. setport EN_CCMD ; Point at Chip command reg pause_ mov al, ENC_START+ENC_NODMA out dx, al ; Start the chip running again setport EN0_TXCR ; Back to TX control reg pause_ xor al, al ; Clear the loopback bit out dx, al ; .. setport EN0_ISR ; Point at Interrupt status register pause_ mov al, ENISR_OVER ; Clear the overrun interrupt bit out dx, al ; .. call count_in_err ; Count the anomaly jmp check_isr ; Done with the overrun case recv_no_overrun: ; Handle receive flags, normal and with error (but not overrun). test al,ENISR_RX+ENISR_RX_ERR ; Frame received without overrun? jnz recv_frame ; Go if so. jmp recv_no_frame ; Go if not. recv_frame: loadport ; Point at Chip's Command Reg setport EN_CCMD ; .. pause_ mov al, ENC_NODMA+ENC_PAGE1+ENC_START out dx, al ; Switch to page 1 registers setport EN1_CURPAG ;Get current page of rcv ring pause_ in al, dx ; .. mov ah, al ; Hold current page in AH setport EN_CCMD ; Back to page zero registers pause_ mov al, ENC_NODMA+ENC_PAGE0+ENC_START out dx, al ; Switch back to page 0 registers setport EN0_BOUNDARY ;Get boundary page pause_ in al, dx ; .. inc al ; Step boundary from last used page cmp al, SM_RSTOP_PG ; Wrap if needed jne rx_nwrap3 ; Go if not mov al, SM_RSTART_PG ; Wrap to first RX page rx_nwrap3: cmp al, ah ; Read all the frames? je recv_frame_break ; Finished them all mov ah,al ; make a byte address. E.G. page mov al,0 ; 46h becomes 4600h into buffer mov bl,ah mov cx,RCV_HDR_SIZE mov di,offset rcv_hdr push ds pop es ; set es to right place call block_input mov al, rcv_hdr+EN_RBUF_STAT ; Get the buffer status byte test al,ENPS_RXOK ; Good frame? jz recv_err_no_rcv call rcv_frm ; Yes, go accept it jmp recv_no_rcv recv_err_no_rcv: or byte ptr soft_rx_err_bits,al add word ptr soft_rx_errors,1 adc word ptr soft_rx_errors+2,0 recv_no_rcv: mov al, rcv_hdr+EN_RBUF_NXT_PG ; Start of next frame dec al ; Make previous page for new boundary cmp al, SM_RSTART_PG ; Wrap around the bottom? jge rcv_nwrap4 mov al, SM_RSTOP_PG-1 ; Yes rcv_nwrap4: loadport ; Point at the Boundary Reg again setport EN0_BOUNDARY ; .. pause_ out dx, al ; Set new boundary jmp recv_frame ; See if any more frames recv_frame_break: loadport ; Point at Interrupt Status Reg setport EN0_ISR ; .. pause_ mov al, ENISR_RX+ENISR_RX_ERR+ENISR_OVER out dx, al ; Clear those requests setport EN_CCMD pause_ mov al, ENC_NODMA+ENC_PAGE0+ENC_START out dx,al jmp check_isr ; See if any other interrupts pending recv_no_frame: ; Handle transmit flags. test al,ENISR_TX+ENISR_TX_ERR ; Frame transmitted? jnz isr_tx ; Go if so. jmp isr_no_tx ; Go if not. isr_tx: mov ah, al ; Hold interrupt status bits loadport ; Point at Transmit Status Reg setport EN0_TSR ; .. pause_ in al, dx ; .. test ah,ENISR_TX ; Non-error TX? jz isr_tx_err ; No, do TX error completion call count_soft_err ; soft error ?? test al,ENTSR_COLL16 ; Jammed for 16 transmit tries? jz isr_tx_njam ; Go if not call count_out_err ; Yes, count those isr_tx_njam: setport EN0_ISR ; Clear the TX complete flag pause_ mov al, ENISR_TX ; .. out dx, al ; .. jmp isr_tx_done isr_tx_err: test al,ENTSR_FU ; FIFO Underrun? jz isr_txerr_nfu call count_out_err ; Yes, count those isr_txerr_nfu: loadport ; Clear the TX error completion flag setport EN0_ISR ; .. pause_ mov al, ENISR_TX_ERR ; .. out dx, al ; .. isr_tx_done: ; If TX queue and/or TX shared memory ring buffer were being ; used, logic to step through them would go here. However, ; in this version, we just clear the flags for background to notice. jmp check_isr ; See if any other interrupts on isr_no_tx: ; Now check to see if any counters are getting full test al,ENISR_COUNTERS ; Interrupt to handle counters? jnz isr_stat ; Go if so. jmp isr_no_stat ; Go if not. isr_stat: ; We have to read the counters to clear them and to clear the interrupt. ; Version 1 of the PC/FTP driver spec doesn't give us ; anything useful to do with the data, though. ; Fix this up for V2 one of these days. loadport ; Point at first counter setport EN0_COUNTER0 ; .. pause_ in al, dx ; Read the count, ignore it. setport EN0_COUNTER1 pause_ in al, dx ; Read the count, ignore it. setport EN0_COUNTER2 pause_ in al, dx ; Read the count, ignore it. setport EN0_ISR ; Clear the statistics completion flag pause_ mov al, ENISR_COUNTERS ; .. out dx, al ; .. isr_no_stat: jmp check_isr ; Anything else to do? interrupt_done: ret ; Do the work of copying out a receive frame. ; Called with bl/ the page number of the frame header in shared memory public rcv_frm rcv_frm: ; first do a software multicast filter. push bx ; save page. cmp mcast_sw_filter,1 ; do software check of mcast ? jnz rcv_frm_ok ; no, accept. mov ax,word ptr rcv_hdr+EN_RBUF_NHDR ; get first word of address test al,1 ; odd first byte jz rcv_frm_ok ; must be our address if even inc word ptr mcast_sw_fin mov bx,word ptr rcv_hdr+EN_RBUF_NHDR+2 ; get second word of address mov dx,word ptr rcv_hdr+EN_RBUF_NHDR+4 ; get third word of address mov di,offset mcast_tab_b ; point to table and broadcast mov cx,mcast_hcount ; get number in table inc cx ; plus the broadcast rcv_loop: mov si,di ; save this table entry cmp ax,[di] jnz rcv_trynext inc di inc di cmp bx,[di] jnz rcv_trynext inc di inc di cmp dx,[di] jz rcv_mc_ok ; got it. rcv_trynext: mov di,si ; get table back, add di,6 loop rcv_loop pop bx ; restore bx jmp rcv_no_copy rcv_mc_ok: inc word ptr mcast_sw_fout rcv_frm_ok: ; Set cx to length of this frame. mov ch, rcv_hdr+EN_RBUF_SIZE_HI ; Extract size of frame mov cl, rcv_hdr+EN_RBUF_SIZE_LO ; Extract size of frame sub cx, EN_RBUF_NHDR ; Less the header stuff ; Set es:di to point to Ethernet type field. mov di, offset rcv_hdr+EN_RBUF_NHDR+EADDR_LEN+EADDR_LEN push cx ; Save frame size push es mov ax, cs ; Set ds = code mov ds, ax mov es,ax assume ds:code call recv_find ; See if type and size are wanted pop ds ; RX page pointer in ds now assume ds:nothing pop cx pop bx cld ; Copies below are forward, please mov ax, es ; Did recv_find give us a null pointer? or ax, di ; .. je rcv_no_copy ; If null, don't copy the data push cx ; We will want the count and pointer push es ; to hand to client after copying, push di ; so save them at this point mov ah,bl ; set ax to page to start from mov al,EN_RBUF_NHDR ; skip the header stuff call block_input pop si ; Recover pointer to destination pop ds ; Tell client it's his source pop cx ; And it's this long assume ds:nothing call recv_copy ; Give it to him rcv_no_copy: push cs ; Put ds back in code space pop ds ; .. assume ds:code ret ; That's it for rcv_frm public recv_exiting recv_exiting: ;called from the recv isr after interrupts have been acknowledged. ;Only ds and ax have been saved. assume ds:nothing push dx loadport setport EN0_IMR ; Tell card it can cause these interrupts pause_ mov al, ENISR_ALL out dx, al pop dx ret ;any code after this will not be kept after initialization. end_resident label byte public usage_msg usage_msg db "usage: NE2000 [-n] [-d] [-w] ",CR,LF,'$' public copyright_msg copyright_msg db "Packet driver for Novell NE2000, version ",'0'+majver,".",'0'+version,CR,LF db "Portions Copyright 1989, Robert C. Clements, K1BC",CR,LF,'$' cfg_err_msg: db "NE2000 Configuration failed. Check parameters.",CR,LF,'$' int_no_name: db "Interrupt number ",'$' io_addr_name: db "I/O port ",'$' using_186_msg db "Using 80[123]86 I/O instructions.",CR,LF,'$' 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 ;enter with dx -> name of word, di -> dword to print. extrn print_number: near public parse_args parse_args: ;exit with nc if all went well, cy otherwise. mov di, offset int_no ; May override interrupt channel call get_number mov di, offset io_addr ; May override I/O address call get_number ; mov di, offset mem_base ; Not movable in 3C503 ; call get_number ; Must get from jumpers. clc ret cfg_error: mov dx,offset cfg_err_msg error: mov ah,9 ; Type the msg int 21h stc ; Indicate error ret ; Return to common code ; Called once to initialize the NE2000 card public etopen etopen: ; Initialize interface ;Determine the processor type. The 8088 and 8086 will actually shift ax ;over by 33 bits, while the 80[123]86 use a shift count mod 32. ;This bit lifted from NI5010 driver. mov cl,33 mov ax,0ffffh shl ax,cl jz not_186 mov is_186,1 mov dx,offset using_186_msg mov ah,9 int 21h not_186: ; Now, initialize the DS8390 Ethernet Controller chip ini_8390: call reset_chip loadport setport EN0_DCFG ; Configure the fifo organization pause_ mov al, ENDCFG_BM8 ; Fifo threshold = 8 bytes out dx, al setport EN_CCMD ; DS8390 chip's command register pause_ mov al, ENC_NODMA+ENC_PAGE0+ENC_STOP out dx, al ; Switch to page zero setport EN0_TXCR ; Set transmitter mode to normal pause_ xor al, al out dx, al setport EN0_RXCR ; Set receiver to monitor mode pause_ mov al, ENRXCR_MON out dx, al ; Set up control of shared memory, buffer ring, etc. setport EN0_STARTPG ; Set receiver's first buffer page pause_ mov al, SM_RSTART_PG out dx, al setport EN0_STOPPG ; and receiver's last buffer page + 1 pause_ mov al, SM_RSTOP_PG out dx, al setport EN0_BOUNDARY ; Set initial "last page we have emptied" pause_ mov al, SM_RSTOP_PG ; (WD doc says set to RSTART_PG) dec al ; (3Com doc says set to RSTOP_PG-1 ?) ; ; (and 3Com handling of BOUNDARY is ; ; different throughout.) out dx, al ; (Check out why WD and 3Com disagree) ; setport EN0_IMR ; Clear all interrupt enable flags pause_ xor al, al out dx, al setport EN0_ISR ; Clear all interrupt assertion flags pause_ mov al, 0ffh out dx, al setport EN_CCMD pause_ mov al, ENC_NODMA+ENC_PAGE1+ENC_STOP out dx,al setport EN1_CURPAG pause_ mov al, SM_RSTART_PG out dx, al setport EN_CCMD pause_ mov al, ENC_NODMA+ENC_PAGE0+ENC_START out dx,al call get_board_data ; read board data push ds ; Copy from card's address to current address pop es mov si, offset board_data ; address is at start mov di, offset curr_hw_addr mov cx, EADDR_LEN ; Copy one address length rep movsb ; .. call set_8390_eaddr ; Now set the address in the 8390 chip call set_8390_multi ; Put the right stuff into 8390's multicast masks loadport ; Base of I/O regs setport EN_CCMD ; Chip command register pause_ mov al, ENC_NODMA+ENC_PAGE0+ENC_STOP out dx, al ; Back to page zero setport EN0_RCNTLO ; Clear the byte count registers pause_ xor al, al ; .. out dx, al setport EN0_RCNTHI pause_ out dx, al ; Clear high byte, too setport EN0_IMR ; Clear all interrupt enable flags pause_ xor al, al out dx, al setport EN0_ISR ; Clear all interrupt assertion flags pause_ mov al, 0ffh ; again for safety before making the out dx, al ; interrupt be enabled call set_recv_isr ; Put ourselves in interrupt chain loadport setport EN_CCMD ; Now start the DS8390 pause_ mov al, ENC_START+ENC_NODMA out dx, al ; interrupt be enabled setport EN0_RXCR ; Tell it what frames to accept pause_ mov al, rxcr_bits ; As most recently set by set_mode out dx, al setport EN0_IMR ; Tell card it can cause these interrupts pause_ mov al, ENISR_ALL out dx, al mov al, int_no ; Get board's interrupt vector add al, 8 cmp al, 8+8 ; Is it a slave 8259 interrupt? jb set_int_num ; No. add al, 70h - 8 - 8 ; Map it to the real interrupt. set_int_num: xor ah, ah ; Clear high byte mov int_num, ax ; Set parameter_list int num. mov dx, offset end_resident ; Report our size clc ; Say no error ret ; Back to common code public print_parameters print_parameters: ;echo our command-line parameters mov di,offset int_no ; May override interrupt channel mov dx,offset int_no_name ; Message for it call print_number mov di,offset io_addr ; May override I/O address mov dx,offset io_addr_name ; Message for it call print_number ret code ends end