;History:640,1 dp8390_version equ 1 ;version number of the generic 8390 driver. ; Copyright, 1988-1992, Russell Nelson, Crynwr Software ; 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. ; This driver is the work of several people: Bob Clements, Eric Henderson, ; Dave Horne, Glenn Talbott and Russell Nelson. pause_ macro ; jmp $+2 ; ; The reason for the pause_ macro is to establish a minimum time between ; accesses to the card hardware. The assumption is that the fetch and execution ; of the jmp $+2 instruction will provide this time. In a fast cache machine ; this may be a false assumption. In a fast cache machine, there may be ; NO REAL TIME DIFFERENCE between the two I/O instruction streams below: ; ; in al,dx in al,dx ; jmp $+2 ; in al,dx in al,dx ; ; To establish a minimum delay, an I/O instruction must be used. A good rule of ; thumb is that ISA I/O instructions take ~1.0 microseconds and MCA I/O ; instructions take ~0.5 microseconds. Reading the NMI Status Register (0x61) ; is a good way to pause on all machines. ; ; The National 8390 Chip (NIC) requires 4 bus clocks between successive ; chip selects (National DP8390 Data Sheet Addendum, June 1990 -- it took them ; long enough to figure this out and tell everyone) or the NIC behaves badly. ; Therefor one I/O instruction should be inserted between each successive ; NIC I/O instruction that could occur 'back - to - back' on a fast cache ; machine. ; - gft - 910529 ; push ax in ax, 61h pop ax ; endm ; ; The longpause macro was originally written as this: ; ;longpause macro ; push cx ; mov cx,0 ; loop $ ; pop cx ;endm ; ; It was only used to stall while hard resetting the card. On my ; 25Mhz 486 longpause was taking more than 18ms, and almost forever ; on slower machines, much longer than necessary and not predictable. ; ; To be able to utilize longpause elsewhere and make it machine independent and ; predictable, I have re-written it to be a fixed time of 1.6ms, which just ; happens to be the time National recommends waiting for the NIC chip to ; stop sending or receiving after being commanded to stop. ; ; Based on the assumption that ISA specs mandate a 1.0 uS minimum I/O cycle ; Microchannel a 0.5uS minimum I/O cycle, and the NMI Status register (location ; 61h) is readable via I/O cycle on all machines, the longpause macro is now ; defined below. - gft - 901604 ; (I realize that on slow machines this may take much longer, but the point ; is that on FAST machines it should never be faster than 1.6ms) longpause macro local lp_not_mc push cx push ax mov cx,1600 ; 1.6ms = 1600*1.0us test sys_features,MICROCHANNEL je lp_not_mc shl cx,1 ; twice as many loops for Microchannel lp_not_mc: in al,61h loop lp_not_mc pop ax pop cx endm extrn sys_features: byte sm_rstop_ptr db SM_RSTOP_PG rxcr_bits db ENRXCR_BCST ; Default to ours plus multicast public curr_hw_addr, mcast_list_bits, mcast_all_flag 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. 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 26 ; 2 ids @6 + protocol @2+8, + header @4 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 ifdef debug ; Include a very useful logging mechanism. ; The log entry structure. Log entries include useful data such as ; a type (each place a log entry is made uses a different type), various ; chip status, ring buffer status, log entry dependent data, and optionally ; 8259 interrupt controller status. logentry struc le_type db 0 ; Log entry type le_ccmd db ? ; Value of CCMD register le_isr db ? ; Value of ISR register le_tsr db ? ; Value of TSR register le_tcur dw ? ; Value of sm_tcur le_tboundary dw ? ; Value of sm_tboundary le_tnum dw ? ; Value of sm_tnum le_dw dw ? ; Log type specific dw data ifndef mkle8259 ; Log 8259 status? le_dd dd ? ; Log type specific dd data else le_irr1 db ? ; Value of 8259-1 IRR register le_isr1 db ? ; Value of 8259-1 ISR register le_irr2 db ? ; Value of 8259-2 IRR register le_isr2 db ? ; Value of 8259-2 ISR register endif logentry ends ; The types of log entries. LE_SP_E equ 0 ; send_pkt entry LE_SP_X equ 1 ; send_pkt exit LE_ASP_E equ 2 ; as_send_pkt entry LE_ASP_X equ 3 ; as_send_pkt exit LE_RBALLOC_E equ 4 ; tx_rballoc entry LE_RBALLOC_X equ 5 ; tx_rballoc exit LE_COPY_E equ 6 ; sm_copy entry LE_COPY_X equ 7 ; sm_copy exit LE_START_E equ 8 ; tx_start entry LE_START_X equ 9 ; tx_start exit LE_XMIT_E equ 0ah ; xmit entry LE_XMIT_X equ 0bh ; xmit exit LE_TXISR_E equ 0ch ; txisr entry LE_TXISR_X equ 0dh ; txisr exit LE_RECV_E equ 0eh ; recv entry LE_RECV_X equ 0fh ; recv exit LE_RCVFRM_E equ 10h ; rcv_frm entry LE_RCVFRM_X equ 11h ; rcv_frm exit LE_COPY_L equ 12h ; sm_copy loop LE_TIMER_E equ 13h ; timer entry LE_TIMER_X equ 14h ; timer exit public log, log_index log logentry 256 dup (<>) ; The log itself log_index db 0 ; Index to current log entry ; The macro used to create log entries. mkle macro letype, ledw, ledd, ledd2 ; Make an entry in the log pushf ; Save interrupt enable state cli ; Disable interrupts push dx ; Save registers push bx push ax mov bl, log_index ; Get current log_index xor bh, bh ; Clear high byte shl bx, 1 ; Multiply by sixteen shl bx, 1 shl bx, 1 shl bx, 1 mov log[bx].le_type, letype ; Store log entry type loadport ; Base of device setport EN_CCMD ; Point at chip command register in al, dx ; Get chip command state mov log[bx].le_ccmd, al ; Store CCMD value setport EN0_ISR ; Point at chip command register in al, dx ; Get chip command state mov log[bx].le_isr, al ; Store ISR value setport EN0_TSR ; Point at chip command register in al, dx ; Get chip command state mov log[bx].le_tsr, al ; Store TSR value mov ax, sm_tcur ; Get current sm_tcur mov log[bx].le_tcur, ax ; Store sm_tcur value mov ax, sm_tboundary ; Get current sm_tboundary mov log[bx].le_tboundary, ax ; Store sm_tboundary value mov ax, sm_tnum ; Get current sm_tnum mov log[bx].le_tnum, ax ; Store sm_tnum value mov log[bx].le_dw, ledw ; Store log entry dw ifndef mkle8259 ; Include extra per-type data mov word ptr log[bx].le_dd, ledd ; Store low word of log entry dd mov word ptr log[bx].le_dd+2, ledd2 ; Store high word of log entry dd else ; Include 8259 status mov al,0ah ; read request register from out 0a0h,al ; secondary 8259 pause_ in al,0a0h ; get it mov log[bx].le_irr2, al mov al,0bh ; read in-service register from out 0a0h,al ; secondary 8259 pause_ in al,0a0h ; get it mov log[bx].le_isr2, al mov al,0ah ; read request register from out 020h,al ; primary 8259 pause_ in al,020h ; get it mov log[bx].le_irr1, al mov al,0bh ; read in-service register from out 020h,al ; primary 8259 pause_ in al,020h ; get it mov log[bx].le_isr1, al endif ifdef screenlog ; Log the entry type to the screen too push es mov ax, 0b800h ; Color screen only... mov es, ax mov bl, log_index ; Get current log_index xor bh, bh ; Clear high byte shl bx, 1 ; Multiply by sixteen add bx, 3360 mov byte ptr es:[bx-1], 07h mov byte ptr es:[bx], letype+30h mov byte ptr es:[bx+1], 70h pop es endif inc log_index ; pop ax ; Restore registers pop bx pop dx popf ; Restore interrupt enable state endm else mkle macro letype, ledw, ledd, ledd2 ; Define an empty macro endm endif 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 mkle LE_SP_E, cx, si, ds ;ne1000 checks the packet size at this point, which is probably more sensible. loadport ; Point at chip command register setport EN_CCMD ; .. pause_ ;ne1000 fails to check to see if the transmitter is still busy. 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,GIANT ; 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 jc tx_no_rdc loadport 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 mkle LE_SP_X, cx, 1, 0 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 mkle LE_SP_X, cx, 0, 0 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 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 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: cld 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 set_8390_1: lodsb out dx,al pause_ 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_board reset_board: assume ds:nothing reset_8390 setport EN_CCMD ; Chip command reg pause_ mov al, ENC_STOP+ENC_NODMA out dx, al ; Stop the DS8390 setport EN0_ISR mov ax,18 ;half a second ought be to enuf. call set_timeout reset_board_loop: pause_ in al,dx ; get isr and al,ENISR_RESET jnz reset_board_done call do_timeout jne reset_board_loop reset_board_done: ret public terminate terminate: terminate_board ret public reset_interface reset_interface: assume ds:code call reset_board 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 ; 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, dl = packet class. ;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. ;ne1000 and ne2000 routines are identical to this point (except that ne2000 ; masks off interrupts). assume ds:code mkle LE_RECV_E, 0, 0, 0 check_isr: ; Was there an interrupt from this card? loadport ; Point at card's I/O port base ram_enable 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 mkle LE_RECV_X, 0, 0, 0 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_ptr ; 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 ;ne1000 and ne2000 routines are identical to this point (except that ne2000 ; masks off interrupts). 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,ENRSR_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? jae rcv_ovr_nwr2 mov al,sm_rstop_ptr ; Yes, back to end of ring dec al rcv_ovr_nwr2: loadport ; Point at boundary reg setport EN0_BOUNDARY ; .. pause_ out dx, al ; Set the boundary rcv_ovr_empty: loadport ; Point at boundary reg 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 ; .. jmp $+2 ; limit chip access rate loopnz rcv_ovr_rst_loop; Loop til reset, or til timeout 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 EN0_ISR ; Point at Interrupt status register pause_ mov al, ENISR_RX+ENISR_RX_ERR out dx, al ; Clear those requests 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_ptr ; 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,ENRSR_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_ptr ; Yes dec al 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 Command register 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: mkle LE_RCVFRM_E, 0, 0, 0 ; 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 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: 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 mkle LE_RCVFRM_X, 0, 0, 0 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 include timeout.asm ;any code after this will not be kept after initialization. end_resident label byte using_186_msg db "Using 80[123]86 I/O instructions.",CR,LF,'$' ;standard EN0_DCFG contents: endcfg db 048h ; Set burst mode, 8 deep FIFO ; Called once to initialize the 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: ;Step 1. Reset and stop the 8390. call reset_board ;Step 2. Init the Data Config Reg. loadport mov al,endcfg setport EN0_DCFG pause_ out dx,al ;Step 3. Clear Remote Byte Count Regs. mov al, 0 setport EN0_RCNTLO pause_ out dx,al setport EN0_RCNTHI pause_ out dx,al ;Step 4. Set receiver to monitor mode mov al, ENRXCR_MON setport EN0_RXCR pause_ out dx,al ;Step 5. Place NIC into Loopback Mode 1. mov al,ENTXCR_LOOP setport EN0_TXCR pause_ out dx,al ;Step 6. Do anything special that the card needs. call init_card ;Step 7. Re-init endcfg in case they put it into word mode. loadport mov al,endcfg setport EN0_DCFG pause_ out dx,al ;Step 8. Init EN0_STARTPG to same value as EN0_BOUNDARY loadport mov al,SM_RSTART_PG setport EN0_STARTPG pause_ out dx,al mov al,SM_RSTART_PG setport EN0_BOUNDARY pause_ out dx,al mov al,sm_rstop_ptr setport EN0_STOPPG pause_ out dx,al ;Step 9. Write 1's to all bits of EN0_ISR to clear pending interrupts. mov al, 0ffh setport EN0_ISR pause_ out dx,al ;Step 10. Init EN0_IMR as desired. mov al, ENISR_ALL setport EN0_IMR pause_ out dx,al ;Step 11. Init the Ethernet address and multicast filters. 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 ;Step 12. Program EN_CCMD for page 1. loadport mov al, ENC_PAGE1 + ENC_NODMA + ENC_STOP setport EN_CCMD pause_ out dx,al ;Step 13. Program the Current Page Register to same value as Boundary Pointer. mov al,SM_RSTART_PG setport EN1_CURPAG pause_ out dx,al ;Step 14. Program EN_CCMD back to page 0, and start it. mov al, ENC_NODMA + ENC_START setport EN_CCMD pause_ out dx,al mov al, 0 ;set transmitter mode to normal. setport EN0_TXCR pause_ out dx,al call set_recv_isr ; Put ourselves in interrupt chain loadport setport EN0_RXCR ; Tell it what frames to accept pause_ mov al, rxcr_bits ; As most recently set by set_mode out dx, al ram_enable 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