Metropoli BBS
VIEWER: 3c505.asm MODE: TEXT (ASCII)
version	equ	1

	include	defs.asm	;SEE ENCLOSED COPYRIGHT MESSAGE

; PC/FTP Packet Driver Source, conforming to version 1.08 of spec
; Krishnan Gopalan and Gregg Stefancik, Clemson Univesity Engineering
; Computer Operations.
; Date: September 1, 1989
; Portions of the code have been adapted from the 3c505 driver for NCSA
; Telnet by Bruce Orchard and later modified by Warren Van Houten and krus
; @diku.dk.

; Permission is granted to any individual or institution to use,copy,
; modify or redistribute this software provided this notice is retained.
; The authors make no guarantee to the suitability of the software for
; any purpose. Any damage caused by using this program is the responsibility
; of the user and not the authors. 

code	segment	word public
	assume	cs:code, ds:code

;	3c505 card definitions
;control register	bit definitions
EN_ATTN			equ	0200q
EN_FLSH_DATA		equ	0100q
EN_DMA_ENABLE		equ	0040q
EN_TO_HOST		equ	0020q
EN_TERMINAL_CNT_ENBLE 	equ	0010q
EN_COMMAND_ENABLE	equ	0004q
EN_HSF2			equ	0002q
EN_HSF1			equ	0001q

;status register	bit definitions

EN_DATA_READY		equ	0200q
EN_HOST_COMMAND_EMPTY	equ	0100q
EN_ADAPTER_COMMAND_FULL equ	0040q
EN_TO_HOST		equ	0020q
EN_DMA_DONE		equ	0010q
EN_ASF3			equ	0004q
EN_ASF2			equ	0002q
EN_ASF1			equ	0001q
; auxiliary dma register  bit definition
EN_BURST		equ	0001q

; timeout values
SECOND		EQU	36
RESDEL		EQU	6
RESTO		EQU	15*SECOND
CMDBTO		EQU	6
CMDCTO		EQU	6
RETRYDELAY  	EQU	6
RCMDTO		EQU	6
RESPTO		EQU	6

;port addresses
ECOMMAND	equ	0
ESTATUS 	equ	2
EDATA 		equ	4
ECONTROL 	equ	6

recvbuf		db  4096 dup(?)		; size of receive buffer
rbufct		dw	?		; recv buffer count

pcblen		dw 	?		; length of pcb
pcbaddr		dw	?		; address of pcb

lastcon		db	?		; last control to board
cmdlen		dw	?		; length of command

cbsh		equ	50
cbs		equ	cbsh*2	

icmdb		db	cbs dup(?)
icmd		db	cbsh dup(?)

fconc		db	0	; flag configure 82586
fgeth		db	0	; flag:get ethernet address
fseth		db	0	; flag:set ethernet address
fxmit		db	0	; flag:transmit packet

cconc		db	02h	; command configure 82586
		db	2	; 2 more bytes
		dw	1	; receive broadcasts

rconc		db	2 dup(?);Response; configure
rconc_st	dw	?	; status

cgeth		db	03h	; command get ethernet address
		db	00	; no more bytes

txmit		db	09h	; command ; transmit packet
		db	06	; 6 more bytes
tx_offset	dw	?	; offset of host transmit buffer 
tx_segment	dw	?	; segment of host transmit buffer 
tx_length	dw	?	; packet length

rxmit		db	2 dup(?); Response tx packet
rx_offset	dw	?	; buffer offset
rx_segment	dw	?	; buffer segment
rx_status	dw	?	; completion status
rx_cstatus	dw	?	; 82586 status

rgeth		db	2 dup(?); Response get Ethernet address
rgeth_ad	db	6 dup(?); -- address

cseth		db	10h	; command :set station address
		db	6	; 6 more bytes
cseth_ad	db	6 dup(?); ethernet address

rseth		db	2 dup(?); response set ethernet address
rseth_status	dw	?	; status

crecv		db	08h	; command receive
		db	08	; 8 more bytes
crecv_offset	dw	?	; buffer offset
crecv_segment	dw	?	; buffer segment
crecv_length	dw	?	; buffer length
crecv_timeout	dw	?	; timeout

rr		db	2 dup(?); Response; receive
rr_offset	dw	?	; buffer offset
rr_segment	dw	?	; buffer segment
rr_dmalen	dw	?	; buffer dmalen
rr_length	dw	?	; packet length
rr_status	dw	?	; completion status
rr_rstatus	dw	?	; 82586 receive status
rr_time		dd	?	; time tag
		
public	int_no,io_addr,mem_base

int_no 	db	2,0,0,0			;must be four bytes long for get_number.
io_addr dw	0300h,0			; io addr for card(jumpers)
mem_base dw	0d000h,0		; shared memory address(software)

public	driver_class, driver_type, driver_name, driver_function, parameter_list

driver_class	db	BLUEBOOK, IEEE8023, 0		;from the packet spec 
driver_type	db	2		;from the packet spec 
driver_name	db	'3C505',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,

	public	rcv_modes
rcv_modes	dw	4		;number of receive modes in our table.
		dw	0,0,0,rcv_mode_3

	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 es:di->upcall routine, (0:0) if no upcall is desired.
;  (only if the high-performance bit is set in driver_function)
;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

	push	si			; save si for  lodsw
	push	ds			; save ds
	push	es			; save es for timer
	push	cs			;
	pop	ds			; cs = ds align

	mov	tx_segment,ds		;tx->data
	mov	tx_offset,si		;tx_offset ->segment offset 

	mov	ax,cx			; save cx
	cmp	ax,60			; compare with minimum limit
	jnb	pkt_ok			; go if ok 
	mov	ax,60			; make length minimum allowed
pkt_ok:
	inc	ax			; round up
	sar	ax,1			; divide by 2
	shl	ax,1			; multiply by 2
	mov	tx_length,ax		; put in request
	
	mov	fxmit,0			; clear response received flag

	mov	si, offset txmit	; si->request(pcb)
	mov	ax,8			;length of pcb 

	call	outpcb			; send command to board

	mov	cx,tx_length
	sar	cx,1

	pop	es
	pop	ds
	pop	si

tx_1:

	lodsw

	loadport
	setport	EDATA
	out	dx,ax			; output word
	setport	ESTATUS
tx_2:
	in	al,dx			; get status
	test	al, EN_DATA_READY	; ready for next word ?
	jz	tx_2			;no

	dec	cx			; count word
	jnz	tx_1			; loop thru buffer

tx_3:
	test	fxmit,0ffh		; is transmit over
	jz	tx_3			; no
	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
	push	es
	push	di

	cmp	cx, EADDR_LEN		; does caller want reasonable length?
	jb	get_addr_fail		; no , fails
	mov	si,offset cgeth		; si->pcb address
	mov	ax,2			; length of pcb
	mov	fgeth,0			; clear response received flag

	call	outpcb		

	mov	ax,RESTO		; get wait time
	call	set_timeout
get_addr_1:
	test	fgeth,0ffh		; answered?
	jnz	get_addr_2		; yes
	call	do_timeout
	jnz	get_addr_1		; test again
	jmp	get_addr_fail		; go return

get_addr_2:
	pop	di
	pop	es
	cld				; make sure of string operation
	mov	si,offset rgeth_ad	; load source of address
	mov	cx,EADDR_LEN		; return length of address
	rep	movsb			; copy address
	mov	cx,EADDR_LEN		; return length of address
	clc
	ret
get_addr_fail:
	add sp,4
	stc
	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

	cmp	cx, EADDR_LEN		; check if address ok
	je	set_addr_1	
	mov	dh,BAD_ADDRESS		; don't like length
	stc
	ret
set_addr_1:
	mov	di,offset cseth_ad	;di->destination offset
	rep	movsb			; return address
	mov	si,offset cseth		; si->request ethernet address
	mov	ax,8			; request length -> ax
	mov	fseth,0			; clear response received flag
	call 	outpcb			; send the pcb

	mov	ax,RESTO		; get response time
	call	set_timeout
set_addr_2:
	test	fseth,0ffh		; pcb answered?
	jnz	set_addr_3
	call	do_timeout		; has time expired
	jne	set_addr_2		; no, go back
	stc				; error
	ret
set_addr_3:
	mov	cx,EADDR_LEN		;return their address length.
	clc
	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, cx = number of addresses.
;return nc if we set all of them, or cy,dh=error if we didn't.
	mov	dh,NO_MULTICAST
	stc
	ret


	public	terminate
terminate:
	ret

	public	reset_interface
reset_interface:
;reset the interface.
	assume	ds:code

	mov	al, EN_ATTN OR EN_FLSH_DATA	; hard reset command
	loadport
	setport	ECONTROL
	out	dx,al				; do reset
	mov	ax,RESDEL			; get reset delay
	call	set_timeout
rst1:
	call	do_timeout
	jne	rst1				; wait for reset
	mov	al,EN_COMMAND_ENABLE		; command interrupt enable
	mov	lastcon,al			; save last command
	out	dx,al				; release reset
	mov	ax,RESDEL
	call	set_timeout
rst2:
	call	do_timeout
	jne	rst2				; wait to start reset
	mov	ax,RESTO			; add time out
	call	set_timeout
rst3:
	call getstat				; getstatus	
	and	ax,EN_ASF1 OR EN_ASF2		; 
	cmp	ax,EN_ASF1 OR EN_ASF2		;  both on ?
	jne 	resdone
	call	do_timeout			; long enough?
	jne	rst3				; no
	ret
resdone:
	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

	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.
; all interrupts come here
;Upon exit, the interrupt will be acknowledged.
	assume	ds:code
	;sti
	cld
recv1:
	loadport
	setport	ESTATUS
	in	al,dx				; read flags
	and	al,EN_ADAPTER_COMMAND_FULL	; 
	jnz	recv2				; yes,full
	jmp	interrupt_done	
;there might be a response, clear flags to check for response
recv2:
	mov	ax,RCMDTO			; incoming command time out
	call	set_timeout

	mov	al,lastcon			; last control->ax
	and	al,NOT (EN_HSF1 OR EN_HSF2)	; clear flags
	mov	lastcon,al

	loadport
	setport	ECONTROL
	out	dx,al				; clear flags in cntl reg

	mov	di,offset icmdb			; di->incoming command buffer

recv3:
	loadport
	setport	ESTATUS
	in	al,dx				; status->al

	mov	cx,ax				; status->cx
	test	al,EN_ADAPTER_COMMAND_FULL	; is adapter register full
	jnz	recv4				; yes go ahead to read from
	call	do_timeout			; is time up ? 
	jne	recv3				; give some more time
	jmp	int1				; give up
recv4:
	loadport
	setport	ECOMMAND
	in	al,dx				; get command byte
	and	cl,EN_ASF1 OR EN_ASF2		
	cmp	cl,EN_ASF1 OR EN_ASF2		; both on?
	je	recv5				; end of command
	mov	[di],al				;save byte
	inc	di				;inc	di 
	mov	ax,di				; current pointer->ax
	sub	ax,offset icmdb			; - start of buffer
	cmp	ax,cbs				; full?
	jl	recv3

	mov	si,(offset icmdb)+cbsh		; si->middle of buffer
	mov	di,offset icmdb			; di->start of buffer
	mov	cx,cbsh				; size of half buffer->cx
	push	ds
	pop	es
	rep	movsb

	jmp	recv3				; loop for more bytes
recv5:
	push	ds
	pop	es
	mov	ah,0
	mov	cmdlen,ax			; save cmdlen
	mov	si,di 
	sub	si,cmdlen
	mov	di,offset icmd	
	mov	cx,cmdlen

	rep movsb				; copy

	mov	al,icmd				; check

	cmp	al,32h				; Configure?
	je	respconfig

	cmp	al,33h				; get ethernet address?
	je	respgetaddr			; yes

	cmp	al,39h				; transmit complete?
	je	transmit			; yes

	cmp	al,40h				; set ethernet address
	je	respsetaddr			; yes
	
	cmp	al,38h				; receive complete?
	je	resprecv

	jmp	int1

respconfig:

	push	ds
	pop	es
	mov	si,offset icmd
	mov	di,offset rconc
	mov	cx,2
	
	rep	movsw
	mov	fconc,1
	jmp	int1
	
respgetaddr:
	push	ds
	pop	es

	mov	si,offset icmd
	mov	di,offset rgeth
	mov	cx,4

	rep	movsw

	mov	fgeth,1
	jmp	int1

respsetaddr:

	mov	si,offset icmd		; si->command received
	mov	di,offset cseth		; di->set ethernet address resp
	push	ds
	pop	es
	mov	cx,2

	rep	movsw
	mov	fseth,1
	jmp	int1

transmit:
	mov	si,offset icmd			; si->command received
	mov	di,offset rxmit			; response,transmit packet
	push	ds
	pop	es
	mov	cx,5				; response length->cx
	rep	movsw

	mov	fxmit,1				; response received
	jmp	int1				; return from interrupt

resprecv:
	mov	si,offset icmd			; si->command received
	mov	di,offset rr			; di->receive response
	push	ds
	pop	es
	mov	cx,9				; response length->cx
	
	rep	movsw				; move response
	mov	di,offset recvbuf		; di->receive buffer
	mov	ax,rr_length			; message size->ax
	inc	ax				; round up
	shr	ax,1				; convert to words
	shl	ax,1				; convert to characters
	mov	rr_length,ax			; ax->message length

	mov	cx,ax				; message length->cx
	shr	cx,1				; convert to words

	mov	al,lastcon			; lastcontrol->al
	or	al,EN_TO_HOST OR EN_HSF1	; set direction & ack	
	mov	lastcon,al			; response

	loadport
	setport	ECONTROL
	out	dx,al				; pass direction
	
	setport ESTATUS

resprecv_1:

	in	al,dx				; get status
	test	al,EN_DATA_READY		; data ready ?
	jz	resprecv_1

	setport	EDATA				; data word->ax
	in	ax,dx				; get word
	stosw					; store word in buffer

	setport	ESTATUS
	dec	cx				; count word
	jnz	resprecv_1			; loop if more words

	mov	al,lastcon
	and	al,NOT (EN_TO_HOST OR EN_HSF1)
	mov	lastcon,al

	loadport				; dx->control register
	setport	ECONTROL
	out	dx,al

	mov	cx,rr_length			; cx->packet length

	push	cs				; align cs and ds
	pop	ds

	mov	di,offset recvbuf		; reset di to beginning
	add	di,EADDR_LEN+EADDR_LEN		; point to type field
						; of buffer
	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			; first call

	assume	ds:nothing
	mov	ax,es				; ax->es
	or	ax,di				; is es=di=0?
	je	rcv_no_copy

	mov	cx,rr_length			; cx->packet length
	push	cx
	push	es
	push	di

	mov	si,offset recvbuf		; prepare to copy the packet
	rep	movsb				; copy

	pop	si
	pop	ds
	pop	cx

	call	recv_copy			; second call 
rcv_no_copy:
	push	cs
	pop	ds
	call	anotherrecv			; start another recv
int1:
	jmp	recv1

interrupt_done:
	ret

;*****************************OUTPCB***********************************
;outpcb	 send pcb to board, retry until accepted
; entry	ax = number of bytes in pcb
;	si = address of pcb
outpcb	proc 	near
	mov	pcblen,ax		; save pcb length
	mov	pcbaddr,si		; save pcb address
pcb_0:
	mov	cx,pcblen		;length->cx
	mov	si,pcbaddr		; address->si
	cli				; stop interrupts
	mov	al,lastcon		; save last command
	and	al, NOT (EN_HSF1 OR EN_HSF2); clear flags
	mov	lastcon,al
	sti				; enable interrupts
	loadport
	setport	ECONTROL
	out	dx,al			; send control
	setport	ECOMMAND
pcb_1:
	mov	al,[si]			; 
	out	dx,al			; send command byte
	mov	ax,CMDBTO		; get time out
	call	set_timeout
chk_hcre:
	call	getstat
	and	al, EN_HOST_COMMAND_EMPTY	; command accepted
	jne	pcb_2				; go on
	call	do_timeout
	jne	chk_hcre			; time is still left
	jmp	cmdretry			; retry command
pcb_2:
	inc	si				; increment source pointer
	dec	cx				; count byte
	jg	pcb_1				; loop 		
	loadport
	setport	ECONTROL
	cli					; disable interrupts
	mov	al,lastcon			; last control -> al
	or	al,(EN_HSF1 OR EN_HSF2)		; set end of command
	mov	lastcon,al			; save last control
	out	dx,al				; send flag bits
	setport	ECOMMAND
	mov	ax,pcblen
	out	dx,al				; send pcb length
	sti					; enable interrupts
	mov	ax,CMDCTO			; time out for acceptance
	call	set_timeout
pcb_3:
	call	getstat
	and	al,(EN_ASF1 OR EN_ASF2)		; just keep status flags
	cmp	al,1				; accepted
	je	cmdaccept
	cmp	al,2
	je	cmdretry
	call	do_timeout
	jne	pcb_3

cmdretry:
	mov	ax,RETRYDELAY			;add retry delay
	call	set_timeout
pcb_4:
	call	do_timeout
	jne	pcb_4
	jmp	pcb_0

cmdaccept:
	cli
	mov	al,lastcon
	and	al, NOT (EN_HSF1 OR EN_HSF2)	; turn off end of command flag
	mov	lastcon,al			; save last control
	sti					; enable interrupts
	loadport
	setport	ECONTROL
	out	dx,al				; pass control byte
	ret	
outpcb	endp
;*************get status of board***********************
getstat		proc	near
	push	bx
	push	dx
	loadport
	setport	ESTATUS

stat_1:
	in	al,dx			; get status into al
	mov	bl,al			; save al
	in	al,dx			; get status again
	cmp	al,bl			; same status ?
	jne	stat_1
	pop	dx
	pop	bx
	ret
getstat	endp

; UPDATE BUFFER POINTERS AND ISSUE ANOTHER RECV
;	update recv 
anotherrecv	proc	near
	mov	ax,rbufct
	mov	crecv_offset,ax
	inc	ax
	mov	rbufct,ax
	mov	ax,10
	mov	si,offset crecv

	call	outpcb
	ret
anotherrecv	endp

	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
	ret

	include	timeout.asm

;any code after this will not be kept after initialization.
end_resident	label	byte

	public	usage_msg
usage_msg  db  "usage: 3C505 [-n] [-d] [-w] <packet_int_no> <int_level> <io_addr> <mem_base>",CR,LF,'$'
	public	copyright_msg
copyright_msg    db    "Packet Driver for 3c505, version ",'0'+(majver / 10),'0'+(majver mod 10), ".",CR,LF
		 db	"Portions copyright 1989, Krishnan Gopalan & Gregg Stefancik.",CR,LF
		 db	 "Clemson University Engineering Comp Ops.",CR,LF,'$'
no_board_msg:
	db	"3c505 apparently not present at this address.",CR,LF,'$'
int_no_name	db	"Interrupt number ",'$'
io_addr_name	db	"I/O port ",'$'
mem_base_name	db	"Memory address ",'$'

	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
	call 	get_number
	mov	di,offset io_addr
	call	get_number
	mov	di,offset mem_base
	call	get_number
	clc
	ret

	public	etopen
etopen:
;if all is okay,
; reset the 3c505 board takes about 15-20 seconds
	loadport
	setport	ECONTROL 
	mov	al,EN_ATTN OR EN_FLSH_DATA	; reset command
	out	dx,al				; do reset
  if 0
	mov	ax,1
	call	set_timeout
reset1:
	call	do_timeout
	jne	reset1				; wait for reset to propagate
  else
  endif

	mov	al,EN_COMMAND_ENABLE		; command interrupt enable
	mov	lastcon,al			; save last command
	out	dx,al				; release reset
	mov	ax,RESDEL			; time to wait
	call	set_timeout
reset2:
	call	do_timeout
	jne	reset2

	mov	ax,RESTO			;this is the long wait.
	call	set_timeout
reset3:
	call	getstat
	and	ax, EN_HSF1 OR EN_HSF2
	cmp	ax,EN_HSF1 OR	EN_HSF2
	jne	resetdone
	call	do_timeout
	jne	reset3
	jmp	openfail			; open failed

resetdone:
	call	set_recv_isr			;install the interrupt handler

;Tell the 3c505 board to receive packets
	mov	si,offset cconc
	mov	ax,4
	mov	fconc,0

	call	outpcb

	mov	ax,RESTO
	call	set_timeout
open_1:
	test	fconc,0ffh
	jnz	setuprecv

	call	do_timeout
	jne	open_1

; set up the recv buffers

setuprecv:
	mov	rbufct,0			; clear buffer counter
	mov	crecv_length,1600		; buffer length,1600

	mov	crecv_timeout,0

startrecv:
	mov	ax,rbufct			; buffer counter->ax
	mov	crecv_offset,ax			; buf no used as offset
	inc	ax				; count buffer
	mov	rbufct,ax
	mov	ax,10				; length of pcb
	mov	si,offset crecv			;si->command receive

	call	outpcb				; pass the pcb
	mov	ax,rbufct
	cmp	ax,10
	jl	startrecv

	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
	clc
	ret

	public	print_parameters
print_parameters:
	mov	di,offset int_no
	mov	dx,offset int_no_name
	call 	print_number
	mov	di,offset io_addr
	mov	dx,offset io_addr_name
	call	print_number
	mov	di,offset mem_base
	mov	dx,offset mem_base_name
	call	print_number
	ret

;if we got an error,
openfail:
	stc
	ret
code	ends
	end
[ RETURN TO DIRECTORY ]