home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power-Programmierung
/
CD1.mdf
/
lan
/
drivrs30
/
ni5210.asm
< prev
next >
Wrap
Assembly Source File
|
1989-06-08
|
22KB
|
831 lines
version equ 1
include defs.asm
;Ported from Tim Krauskopf's micnet.asm, an assembly language
;driver for the MICOM-Interlan NI5210, by Russell Nelson. Any bugs
;are due to Russell Nelson.
;Updated to version 1.08 Feb. 17, 1989 by Russell Nelson.
;Updated to support 1500 byte MTU April 27, 1989 By Brad Clements.
; 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.
code segment byte public
assume cs:code, ds:code
;
; Equates for controlling the MICOM board
;
; I/O addresses, writing anything in AL trips these gates
;
; First six addresses are the EPROM board Ether address (read)
;
IORESET EQU 0 ; reset the board
IOCA EQU 1 ; execute command which is in SCB
IODIS EQU 2 ; disable network connect
IOENA EQU 3 ; enable network
IOINTON EQU 4 ; enable interrupts
IOINTOF EQU 5 ; disable interrupts, '586 thinks it still ints
;
; Structure elements specific to the Intel 82586 chip
;
BUF_COUNT EQU 28 ; number of buffers
XMIT_MTU EQU 1500+14 ; size of xmit buffer (1500 MTU +
; 14 bytes for Ethernet headers
B EQU 0 ; base offset for the board.
SCB EQU B + 10 ; system control block base
CCBPTR EQU B + 26 ; offset of configure command block
TCBPTR EQU B + 44 ; xmit CB offset
TBDPTR EQU B + 60 ; xmit BD offset
TBUFPTR EQU B + 68 ; xmit buffer offset
FDBASE EQU TBUFPTR+XMIT_MTU ; base addr for 30 frame descriptors
FDSIZE EQU BUF_COUNT * 22
BDBASE EQU FDBASE+FDSIZE ; base address for 30 buffer descriptors
BDASIZE EQU BUF_COUNT * 10
BUFBASE EQU BDBASE+BDASIZE ; base address for 30 200 byte buffers
BUFFTOP EQU BUFBASE+BUF_COUNT*200
ISCPTR EQU B + 03feeh ; my address for ISCP, points to SCB
SCPTR EQU B + 03ff6h ; hardwired address for SCP
if2
%out Buffer top is BUFFTOP
if BUFFTOP > ISCPTR
%out Warning, buffers extend beyond ISCPTR, reduce BUF_COUNT.
endif
endif
BDSTAT EQU 0 ; status word in BD
BDLINK EQU 2 ; 16pointer to next BD
BDPTR EQU 4 ; 24pointer to actual buffer
BDSIZE EQU 8 ; size of the buffer
;
SSTAT EQU 0 ; status word for SCB
SCOM EQU 2 ; command word in SCB
SCBL EQU 4 ; 16pointer to command block list
SRFA EQU 6 ; 16pointer to receive frame list
SERRS EQU 8 ; 4 words of error counts
;
FDSTAT EQU 0 ; status word for frame
FDEOL EQU 2 ; end of FD list flag
FDLINK EQU 4 ; 16pointer to next FD
FDPTR EQU 6 ; 16pointer to list of BD's
;
TSTAT EQU 0 ; status word for xmit
TCOM EQU 2 ; command to transmit
TLINK EQU 4 ; 16pointer to next command (always ffff)
TPTR EQU 6 ; 16pointer to xmit TBD
TTRIES EQU 8 ; number of transmit retries
;
; Data segment
;
public int_no
int_no db 2,0,0,0 ; interrupt number.
io_addr dw 0360h,0 ; I/O address for card (jumpers)
base_addr dw 0d000h,0 ; base segment for board (jumper set)
public driver_class, driver_type, driver_name
driver_class db 1 ;from the packet spec
driver_type db 11 ;from the packet spec
driver_name db "NI5210",0 ;name of the driver.
firstfd dw FDBASE ; start of FD queue
lastfd dw 0 ; end of the FD chain
lastbd dw 0 ; end of the BD chain
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
mov ax,base_addr
mov es,ax ; base for board
mov word ptr es:[SCB+SCBL],TCBPTR; say where xmit command is
cmp cx,XMIT_MTU ; Is this packet too large?
ja send_pkt_toobig
mov dx,cx ; save a copy, might be less than 60, ok
cmp dx,RUNT ; minimum length for Ether
jnb oklen
mov dx,RUNT ; make sure size at least RUNT
oklen:
mov di,TBUFPTR ; start of xmit buffer
;
; check for previous xmit
;
xwait:
mov bx,word ptr es:[SCB+SCOM] ; is command zeroed yet?
or bx,bx
jnz xwait ; not there yet, wait for it
;
; move the data using word moves.
;
call movemem
;
; put the correct size into the TDB
;
or dx,08000h ; end of frame bit flag
mov word ptr es:[TBDPTR],dx ; store it
mov word ptr es:[TCBPTR],0 ; zero status wd
mov word ptr es:[TCBPTR+TCOM],08004h; xmit command in TCB
mov word ptr es:[SCB+SCOM],0100h ; execute command
call doca
clc
ret
send_pkt_toobig:
mov dh,NO_SPACE
stc
ret
movemem:
;does the same thing as "rep movsb", only 50% faster.
;moves words instead of bytes, and handles the case of both addresses odd
;efficiently. There is no way to handle one address odd efficiently.
;This routine always aligns the source address in the hopes that the
;destination address will also get aligned. This is from Phil Karn's
;code from ec.c, a part of his NET package. I bummed a few instructions
;out.
jcxz movemem_cnte ; If zero, we're done already.
test si,1 ; Does source start on odd byte?
jz movemem_adre ; Go if not
movsb ; Yes, move the first byte
dec cx ; Count that byte
movemem_adre:
shr cx,1 ; convert to word count
rep movsw ; Move the bulk as words
jnc movemem_cnte ; Go if the count was even
movsb ; Move leftover last byte
movemem_cnte:
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.
assume ds:code
cmp cx,EADDR_LEN ;make sure that we have enough room.
jb get_address_2
mov cx,EADDR_LEN
mov dx,io_addr ; Get our IO base address.
cld
get_address_1:
in al,dx ; get a byte of the eprom address
stosb ; put it away
inc dx ; next register
loop get_address_1 ; go back for rest
mov cx,EADDR_LEN
clc
ret
get_address_2:
stc
ret
public reset_interface
reset_interface:
;reset the interface.
;we don't do anything.
ret
;called when we want to determine what to do with a received packet.
;enter with cx = packet length, es:di -> packet type.
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.
;Upon exit, the interrupt will be acknowledged.
mov ds,base_addr ; base for board
assume ds:nothing
mov ax,ds:[SCB+SSTAT] ;get the status.
recv_isr_1:
cmp word ptr ds:[SCB+SCOM],0 ;are we done yet?
jne recv_isr_1 ;no -- keep waiting.
and ax,0f000h ;isolate the ACK bits.
mov ds:[SCB+SCOM],ax ;make a command to
;acknowledge the interrupt.
call doca
recv_isr_2:
cmp word ptr ds:[SCB+SCOM],0 ;are we done yet?
jne recv_isr_2 ;no -- keep waiting.
; Get whatever packets are on the board
;
mov bx,firstfd ; get addr of first FD in list
;
;
ckframe:
mov ax,[bx+FDSTAT] ; status word of frame
test ax,08000h ; frame written?
jnz okframe
jmp ru_start ; no, restore receiver if necessary
ptrupdate_j_1:
jmp ptrupdate
frame_bad_j_1:
call count_in_err
jmp frame_bad
; we have a frame, read it in
;
okframe:
test ax,02000h ;check frame OK bit
jz frame_bad_j_1 ;bad, fix it.
mov si,[bx+FDPTR] ;get pointer to buffer descriptor
xor cx,cx ;start with zero bytes.
countbuf: ;es:di is already set to receive packet
mov dx,si ;save a copy of current BD ptr
mov ax,[si+BDSTAT] ;get status and count word for BD
test ax,04000h ;is count field there?
jz ptrupdate_j_1 ;no - we give up here.
add cl,al ;add the count into cx.
adc ch,0
mov si,[si+BDLINK] ;go to next BD in list
test ax,8000h ;is this the last frame?
je countbuf ;no - keep counting.
push bx
push cx
mov ax,cs ;we need ds = code.
mov ds,ax
assume ds:code
mov es,base_addr ;get a pointer to their type.
mov di,es:[bx+FDPTR] ;get pointer to buffer descriptor
mov di,es:[di+BDPTR] ;get offset of data
add di,EADDR_LEN+EADDR_LEN ;skip the ethernet addreses and
; point to the packet type.
call recv_find ;look up our type.
pop cx
pop bx
mov ds,base_addr ;restore ds to the board.
assume ds:nothing
mov ax,es ;is this pointer null?
or ax,di
je ptrupdate ;yes - just free the frame.
push cx
push es ;remember where the buffer pointer is.
push di
mov si,[bx+FDPTR] ;get pointer to buffer descriptor
copybuf:
mov dx,si ;save a copy of current BD ptr
xor ch,ch ;200 bytes is largest this can be
mov cl,[si+BDSTAT] ;get count word for BD
mov si,[si+BDPTR] ;get offset of data
call movemem
mov si,dx ;get back current BD ptr
test [si+BDSTAT],8000h ;check EOF bit
mov si,[si+BDLINK] ;go to next BD in list
jz copybuf ;not done, keep copying it.
pop si ;now give the frame to the client.
pop ds
pop cx
assume ds:nothing
call recv_copy
jmp short ptrupdate
frame_bad:
;
; we are done with the frame, do the list management
;
ptrupdate:
push cs
pop ds
assume ds:code
mov es,base_addr ; reload board segment
mov si,es:[bx+FDPTR] ; first BD in frame list
nextbd:
mov cx,es:[si+BDSTAT] ; count word for BD, EOF bit
test cx,08000h ; EOF bit, if set, save si in lastbd
jnz dolastbd
mov word ptr es:[si+BDSTAT],0 ; clear status word, EOF bit
cmp si,lastbd ; see if we are wrapping
jz dolastbd ; yes, just undo it
mov si,es:[si+BDLINK] ; follow link
jmp nextbd
dolastbd:
mov di,lastbd ; where end of BD list is now
mov lastbd,si ; store last known BD
mov word ptr es:[si+BDSIZE],08000h+200 ; end of list here
mov word ptr es:[si+BDSTAT],0 ; clear status word, EOF bit
; size field for not end of list
mov word ptr es:[di+BDSIZE],200 ; remove old end-of-list
;
; update the FD list flags, new end-of-list
;
mov word ptr es:[bx+FDEOL],08000h ; store new EOL
mov word ptr es:[bx+FDSTAT],0 ; clear status word for frame
mov di,lastfd ; get old end-of-list
mov word ptr es:[di+FDEOL],0 ; zero old one
mov lastfd,bx ; update stored pointer
mov si,es:[bx+FDLINK] ; where next fd is
mov firstfd,si ; store that info for next time
ru_start:
; re-start receive unit
;
; check to see if the receiver went off because of no resources
; and restart receiver if necessary
;
push cs
pop ds
mov es,base_addr
mov ax,es:[SCB+SSTAT] ; status word for SCB
and ax,070h ; receiver status
cmp al,020h ; receiver has no resources
jnz hasres
call count_out_err
;
; setup lists for starting the RU on the chip
; we know that there isn't anything in the buffer that we want
;
mov bx,firstfd ; get first FD on free list (assume free)
mov word ptr es:[SCB+SRFA],bx ; put into SCB
mov si,lastbd ; pointer to a BD, end of chain
mov ax,word ptr es:[si+BDLINK] ; pointer to next BD
mov word ptr es:[bx+FDPTR],ax ; set to start of BDs
;
;
; Start the RU, doesn't need CB, only SCB parms.
; command, to start receiving again
;
mov word ptr es:[SCB+SSTAT],0 ; clear status word
mov word ptr es:[SCB+SCOM],010h ; start RU
call doca
hasres:
recv_isr_end:
cmp word ptr es:[SCB+SCOM],0 ;are we done yet?
jne recv_isr_end ;no -- keep waiting.
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:
; Next step, load our address into the board
; reuses the space that the configure command used, with different command
;
mov es,base_addr ; set to base address
mov word ptr es:[SCB+SCBL],CCBPTR ; say where conf command is
mov di,CCBPTR ; start of config command block
xor ax,ax
stosw ; zero status word for commmand
mov ax,8001h ; IA setup command + EL
stosw
xor ax,ax
dec ax
stosw ; set link value to -1 (unused)
rep movsb ; move their ethernet address in.
;
; start the IA setup command
;
mov word ptr es:[SCB+SCOM],0100h ; do-command command
call doca
xor cx,cx ; timeout
set_address_1:
mov ax,word ptr es:[CCBPTR] ; get status word
test ax,08000h ; is command complete?
loopz set_address_1
jnz set_address_okay
stc
mov dh,-1 ; no error in the list applies.
jmp short set_address_done
set_address_okay:
clc
set_address_done:
push cs
pop ds
assume ds:code
ret
lbca:
doca:
;we may be called from places in which ds is unknown.
assume ds:nothing
loadport
setport IOCA
out dx,al ; send it
ret
assume ds:code
;yet, we really should assume ds==code for the rest of this stuff.
end_resident label byte
public usage_msg
usage_msg db "usage: ni5210 <packet_int_no> <int_no> <io_addr> <base_addr>",CR,LF,'$'
public copyright_msg
copyright_msg db "Packet driver for the MICOM-Interlan NI5210, version ",'0'+majver,".",'0'+version,CR,LF
db "Portions Copyright 1988 The Board of Trustees of the University of Illinois",CR,LF,'$'
no_5210_msg db "No 5210 found at that address.",CR,LF,'$'
timeout_msg db "Timed out while initializing 5210.",CR,LF,'$'
int_no_name db "Interrupt number ",'$'
io_addr_name db "I/O port ",'$'
base_addr_name db "Memory address ",'$'
our_address db 6 dup(?) ;temporarily hold our address
tdr_ok_msg db "TDR Ok",CR,LF,'$'
tdr_none_msg db "Your Ethernet card doesn't seem to be plugged in.",CR,LF,'$'
tdr_open_msg db " clocks away is an OPEN",CR,LF,'$'
tdr_short_msg db " clocks away is a SHORT",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:ax = number to print.
extrn hexout: near, decout: near
public parse_args
parse_args:
mov di,offset int_no
mov bx,offset int_no_name
call get_number
mov di,offset io_addr
mov bx,offset io_addr_name
call get_number
mov di,offset base_addr
mov bx,offset base_addr_name
call get_number
ret
timeout_error:
mov dx,offset timeout_msg
jmp short error
no_5210_error:
mov dx,offset no_5210_msg
error:
mov ah,9
int 21h
stc
ret
;
; data for configuring and setting up the board
;
; chip always looks at SCP for config info which points to ISCP for the
; pointer to the CONTROL BLOCK which handles everything from there.
; Kind of indirect, but it works.
;
SCP DB 1 ; bus use flag
DB 5 DUP(0) ; unused
DW ISCPTR ; 24pointer to ISCP offset
DW 0 ; high part
;
; Intermediate SCP
;
ISCP DW 1 ; busy flag
DW SCB ; 16pointer to SCB
DW 0,0 ; base for all 16 pointers, lo, hi
; board is hardwired to 0 for these values
;
; Configuration block for 82586, this comprises one config command
; Parameters taken from MICOM driver
;
CBCONF DW 0 ; status word
DW 8002H ; end of command list + configure command
DW 0ffffh ; link to next command (not used)
DW 080CH ; fifo=8, byte count=C
DW 2E00H ; important! Addr (AL) not inserted on the fly!
DW 6000H ; IFS = 60h
DW 0F200H ; retry=F, slot time=200h
DW 0 ; flags, set to 1 for promiscuous
DW 40H ; min frame length=40h
;
; CB for xmit, followed by BD for xmit, copied together
;
TCB DW 0 ; status word
DW 08004H ; command word for xmit + EL
DW 0ffffh ; no command link
DW TBDPTR ; 16pointer to xmit BD
DW 0,0,0,0 ; no addressing used here
;
; BD template for xmit
TBD DW 0
DW 0 ; next BD pointer, unused
DW TBUFPTR ; 24pointer to xmit buffer
DW 0 ; high part of pointer
public etopen
etopen:
; Initialize the Ethernet board, set receive type.
;
; check for correct EPROM location
;
mov dx,io_addr ; i/o address
add dx,EADDR_LEN ; look past the ethernet address.
in al,dx
mov bl,al ; assemble pattern to check
inc dx
in al,dx
mov bh,al
cmp bx,05500h ; pattern known to be there in ROM
jz have_5210
jmp no_5210_error ;not there -- no 5210.
have_5210:
; Initialize the Ethernet board, set receive type.
;
; Install 8K SCP, we only use 8K bytes no matter what
;
mov es,base_addr ; set to base address
mov di,SCPTR
mov si,offset SCP ; get pre-set values
mov cx,5 ; 5 words
rep movsw ; install SCP
;
; Install 16K SCP, just in case they have that much.
;
mov si,offset SCP ; get pre-set values
mov di,SCPTR+2000h ; offset for 16K board
mov cx,5 ; 5 words
rep movsw ; install SCP
;
; Intermediate SCP
;
mov si,offset ISCP ; addr of pre-set values
mov di,ISCPTR
mov cx,4 ; 4 words
rep movsw ; install ISCP
;
; Turn off interrupts, I don't want them
;
loadport
setport IORESET
out dx,al ; reset the chip
;
; Issue a CA to initialize the chip after reset
;
call lbca
;
; Disconnect from network
loadport
setport IODIS
out dx,al
;
; configure 82586
;
mov si,offset CBCONF ; configure command
mov di,CCBPTR ; where command will reside
mov cx,9
rep movsw ; copy to board
;
; issue the configure command
;
mov word ptr es:[SCB+SCOM],0100h ; do-command command
mov word ptr es:[SCB+SCBL],CCBPTR ; where conf command is
mov word ptr es:[SCB+SERRS],0 ; zero errs field
mov word ptr es:[SCB+SERRS+2],0 ; zero errs field
mov word ptr es:[SCB+SERRS+4],0 ; zero errs field
mov word ptr es:[SCB+SERRS+6],0 ; zero errs field
call lbca
xor cx,cx ; timeout
waitconf:
mov ax,word ptr es:[CCBPTR] ; get status word
test ax,08000h ; is command complete?
loopz waitconf
jnz confok
jmp timeout_error
confok:
;
; Ask the board for the Ethernet address, and then use set_address to set it.
;
mov dx,io_addr ; Get our IO base address
mov si,offset our_address
mov cx,EADDR_LEN
store_address_1:
in al,dx ; get a byte of the eprom address
mov [si],al ; put it away
inc si
inc dx ; next register
loop store_address_1 ; go back for rest
mov si,offset our_address
mov cx,EADDR_LEN
call set_address
jnc store_address_2
jmp timeout_error
store_address_2:
;
; IA sent, setup all of the other data structures on the board
; start with xmit command descriptors
;
mov si,offset TCB ; template for xmit
mov di,TCBPTR ; where it goes on board
mov cx,12 ; copies CB and BD for xmit
rep movsw
;
; Set up frame and buffer descriptors, 30 each
;
mov cx,BUF_COUNT ; # of FDs
mov di,FDBASE ; base addr for FDs
fdloop:
xor ax,ax
mov bx,di ; save pointer
stosw ; clear status wd
stosw ; clear EL field
add bx,22 ; points to next one
mov es:[di],bx ; put in link ptr
inc di
inc di
dec ax
stosw ; clear BD ptr to -1
add di,14
loop fdloop
sub di,20 ; point back to last EL field
mov ax,08000h ; end of list
stosw ; put into last FD
sub di,4 ; back to beginning of last FD
mov lastfd,di ; save the pointer
mov word ptr es:[di+FDLINK],FDBASE ; make list circular,
; from last to first
mov ax,BDBASE ; first BD
mov word ptr es:[FDBASE+FDPTR],ax ; put it in the first FD frame
;
; now BDs
mov cx,BUF_COUNT
mov di,BDBASE ; start of BD area
mov dx,BUFBASE ; start of buffer area
bdloop:
xor ax,ax
mov bx,di ; save pointer
stosw ; zero status field
add bx,10 ; point to next record
mov es:[di],bx ; put in link ptr
inc di
inc di
mov es:[di],dx ; address of buffer, lo part
inc di
inc di
stosw ; zero out high part
mov ax,200
stosw ; store length field
add dx,ax ; add in length of buffer, updates ptr
loop bdloop
sub di,2 ; back to last BD size field
mov ax,08000h+200 ; end of list + 200
stosw ; mark end of list
sub di,8 ; back to last BDLINK field
mov ax,BDBASE
stosw ; put link to beginning of list here
sub di,4 ; back to beginning of last BD
mov lastbd,di ; save pointer to end of list
;
; configure to connect to network
;
loadport
setport IOENA ; enable network
out dx,al ; any al value
;
; Test to see if the network is okay.
;
mov di,CCBPTR ; start of config command block
xor ax,ax ; zero status word for commmand
stosw
mov ax,8005h ; TDR command + EL
stosw
xor ax,ax
dec ax
stosw ; set link value to -1 (unused)
inc ax
stosw ; zero time result.
mov word ptr es:[SCB+SCOM],0100h ; do-command command
call lbca
xor cx,cx ; timeout
do_tdr_1:
mov ax,word ptr es:[CCBPTR] ; get status word
test ax,08000h ; is command complete?
loopz do_tdr_1
mov ax,word ptr es:[CCBPTR+6]
jnz do_tdr_2
mov ax,2000h ; treat a timeout as an open
do_tdr_2:
test ax,8000h
mov dx,offset tdr_ok_msg
jne do_tdr_3
mov dx,offset tdr_short_msg
test ax,2000h
je do_tdr_4
mov dx,offset tdr_none_msg
cmp ax,2000h
je do_tdr_3
mov dx,offset tdr_open_msg
do_tdr_4:
push dx
and ax,2048-1
xor dx,dx
call decout
pop dx
do_tdr_3:
mov ah,9
int 21h
;
; minor detail, but important
; Change SCB command block pointer to setup for xmit commands
; = only commands needed when operational
;
mov word ptr es:[SCB+SCBL],TCBPTR ; where xmit command is
;
; Start the RU, doesn't need CB, only SCB parms.
; command, to start receiving
;
mov word ptr es:[SCB],0 ; clear status word
mov word ptr es:[SCB+SRFA],FDBASE ; set to frame descriptors
mov word ptr es:[SCB+SCOM],010h ; start RU
call lbca
;
; Now reset CX, FR, CNA, and RNR so that we don't get a spurious interrupt.
;
store_ack_1:
cmp word ptr es:[SCB+SCOM],0 ;are we done yet?
jne store_ack_1 ;no -- keep waiting.
mov ax,es:[SCB+SSTAT] ;get the status.
and ax,0f000h ;isolate the ACK bits.
mov es:[SCB+SCOM],ax ;make a command to
;acknowledge the interrupt.
call lbca
;
; Now hook in our interrupt
;
call set_recv_isr
mov dx,offset end_resident
clc
ret
code ends
end