	.title	do
	.enabl	lc
;++
;
; DOS file device handler for RT-11 V4.0 and later.
;
; By John Wilson.
;
; Copyright (C) 1998 by Digby's Bitpile, Inc. DBA D Bit.  All rights reserved.
;
; This handler allows RT-11 programs to access DOS files using the DOS file
; device found in Ersatz-11.  It is currently fairly primitive and does not
; handle stripping trailing NULs off of files (so text files written this way
; will usually have some number of NUL characters in them).  It allows only
; one file at a time to be read or written, similar to magtape files.  The
; file must be located in the current DOS directory, and has to have a 6.3
; filename with no non-RAD50 characters just like any other RT-11 filename.
;
; 02-Mar-1992	JMBW	Created (as unfinished Kermit device).
; 21-Oct-1998	JMBW	Converted to DOS file interface.
; 23-Oct-1998	JMBW	Added XM support.  Initial release.
; 31-Oct-1998	JMBW	Whoops -- fixed SEEK bug with files .GE. 128 blocks.
; 18-Dec-1998	JMBW	Whip up fake directory for DIR.SAV.
; 24-Apr-2000	JMBW	Use lower case filenames when opening (for E11/Linux).
;
;--
	.mcall	.drdef
;
nstmax=	8.	;max # nested opens
		;(V5.X PIP!!!)
;
; CSR bits:
dovec=	400	;7-bit vector/4 starts with this bit
dopri=	20	;3-bit interrupt priority (4-7) starts with this bit
dobsy=	4	;NZ => device busy
dogo=	2	;set to start command
doie=	1	;int enable
;
; Function codes (for CMDCMD field in command packet):
doopn=	0	;open file
docrt=	1	;create (but don't open) file
dodel=	2	;delete file
dordd=	3	;read data
dowrd=	4	;write data
dosek=	5	;seek
dofst=	6	;find first wildcard match
donxt=	7	;find next wildcard match
docls=	8.	;close file
;
; MOVE address to register
;
	.macro	mova	arg,r
	mov	pc,r
	add	#<arg>-.,r
	.endm
;
	.drdef	do,400-'D,specl$!spfun$,0,176470,240
;
do$ba=	do$csr+2	;bus addr reg
do$bae=	do$csr+4	;bus addr ext reg
;
.if ne 0 ;;;;; SET options not yet supported
	.drset	CSR,1,o.csr,OCT		;change CSR
	.drset	VECTOR,3,o.vec,OCT	;change vector
;
o.csr:	bis	#160000,r0	;at least trap or something if way off
	mov	r0,176		;set CSR
	mov	r0,docsr	;set all
	add	#2,r0
	mov	r0,doba
	add	#2,r0
	mov	r0,dobae
clcrts:	clc			;happy
	rts	pc
;
o.vec:	bic	#3,r0		;guarantee OK
;;	mov	r0,do$vtb	;set vector
	rts	pc
;
.iif gt <.-1000>, .error .-1000	;SET code too big
.endc ;;;;;
;+
;
; I/O initiation.
;
;-
	.drbeg	do
	mov	docqe,r4	;get curr Q-el
;	movb	q$unit(r4),r0	;get unit #
;	bic	#^C7,r0		;isolate
;	asl	r0		;*2 for use as table index
;	mov	r0,unit		;save
;;; (we don't do anything with that yet, indexing tables with PIC is a pain!)
	movb	q$func(r4),r0	;get special function code
	bne	spec		;NZ => handle it
	asl	q$wcnt(r4)	;WC*2=BC
	beq	20$		;seek, done
	bcs	50$		;.WRITE
	; .READ
	call	seek		;set up "seek" command
	bcs	60$		;(no open file, skip)
	call	docmd		;send it
	bcs	40$		;failed
	call	addr		;load up buffer addr
	mov	#dordd,cmdcmd	;set command
	call	docmd		;do command
	bcs	40$
	mov	cmdlen,r5	;get actual length
	beq	30$		;EOF, special
	add	r5,q$buff(r4)	;update addr
	sub	r5,q$wcnt(r4)	;and byte count
	beq	20$		;nothing to do
10$:	; zero fill
	clr	r5		;load zero
	call	putb		;store it
	bne	10$		;loop until buf is full
20$:	jmp	dodone		;finished
30$:	bis	#eof$,@-(r4)	;end of file
	jmp	dodone		;finished
40$:	jmp	doerr
50$:	br	write
60$:	; not a file, must be trying to read from raw disk
	mov	q$blkn(r4),r0	;get starting block #
	cmp	r0,#6		;before directory?
	blo	10$		;yes, just return zeros (probably home blk)
	beq	70$		;starting new directory
	cmp	r0,#6+<31.*2>	;after directory?
	bhis	10$		;yes, who knows what they're up to
	cmp	r0,dirblk	;is this next in sequence?
	beq	80$		;yes
	clr	dirblk		;forget about dir lookup
	br	10$		;go return NULs
70$:	mov	r0,dirblk	;starting a fresh directory
	clr	diroff		;init offset in segment
80$:	jmp	dir		;go handle directory access
;
write:	; .WRITE
	neg	q$wcnt(r4)	;positive byte count
	call	seek		;set up "seek" command
	bcs	10$		;(no open file, skip)
	call	docmd		;send it
	bcs	20$		;failed
	call	addr		;load up buffer addr
	mov	#dowrd,cmdcmd	;set command
	call	docmd		;do command
	bcs	20$
10$:	jmp	dodone		;finished
20$:	jmp	doerr
;+
;
; Special function or directory access.
;
;-
spec:	mova	spfuns-2,r1	;get ptr to special function list
10$:	tst	(r1)+		;skip a word
	mov	(r1)+,r2	;get function
	beq	badspf		;end of list
	cmp	r0,r2		;is this it?
	bne	10$		;no, keep searching
	add	(r1),pc		;jump
spfuns:	.word	1,close-spfuns
	.word	2,delete-spfuns
	.word	3,lookup-spfuns
	.word	4,enter-spfuns
	.word	0
;
badspf:	jmp	doerr		;bad .SPFUN
;+
;
; Close innermost open file.
;
;-
close:	call	gethnd		;get file handle
	bcs	10$		;not open
	mov	#docls,cmdcmd	;func=close
	call	docmd		;close the handle
10$:	sub	#2,opensp	;back up stack pointer
	bcc	20$
	 clr	opensp		;stop at 0
20$:	jmp	dodone
;+
;
; Delete a file.
;
;-
delete:	call	getnam		;convert filename
	mov	#dodel,cmdcmd	;func=delete
	call	docmd		;execute it, come back on interrupt
	bcs	doerr		;error
	br	dodone
;+
;
; Look up an existing file.
;
;-
lookup:	call	nulfil		;convert filename, see if it's null
	bcs	10$		;yes
	mov	#1,cmdprm	;access=RO, sharing=compatibility mode
	clr	cmdcmd		;func=open
	call	docmd		;execute it, come back on interrupt
	bcs	doerr		;error
	call	savhnd		;save handle
10$:	mov	docqe,r4	;restore qel ptr (NULFIL may trash)
	clr	q$wcnt(r4)	;FSM.MAC does this (say file length=0)
				;(V5.6 Quick Ref confirms .LOOKUP behavior)
	br	dodone		;success
;+
;
; Enter a new file.
;
;-
enter:	call	nulfil		;convert filename, see if it's null
	bcs	10$		;yes
	mov	#docrt,cmdcmd	;func=create
	call	docmd		;execute it, come back on interrupt
	bcs	doerr		;error
	clr	cmdprm		;access=RW, sharing=compatibility mode
	clr	cmdcmd		;func=open
	call	docmd		;execute it, come back on interrupt
	bcs	doerr		;error
	call	savhnd		;save handle
10$:	br	dodone
;
doerr:	bis	#hderr$,@-(r4)	;hard error bit in CSW
dodone:	;clr	@krcsr		;kill ints
	;clr	@ktcsr
	.drfin	do
;+
;
; Handle directory reads.
;
; We read filenames from the host OS one at a time, and build up RT-11 dir
; segments on the fly.  Unfortunately we get no size/date information.
;
; r4	I/O qel
; DIRBLK  starting blk # of this segment (assumed to be even, screwy results
;	if not but nothing will get damaged)
;
;-
dir:	bit	#1,dirblk	;begn of segment?
	bne	10$		;no, starting midway through
	; prepend segment header
	mov	#31.,r5		;total # of segments
	call	putw
	mov	dirblk,r5	;get curr dir blk
	sub	#6-2-2,r5	;find next segment (2-32.) *2
	asr	r5		;*1
	bic	#^C37,r5	;change to 0 if 32. (end of chain)
	call	putw
	mov	#31.,r5		;highest seg in use (say all of them)
	call	putw
	clr	r5		;# extra bytes
	call	putw
	mov	#6+<32.*2>,r5	;all our null files start after dir
	call	putw
	; if this is the first dir seg, get the dir search started
	cmp	dirblk,#6	;first block of dir?
	bne	20$		;no
	clr	dirdon		;not done yet
	mova	file,r5		;point at buf
	mov	#"*.,(r5)+	;init to "*.*"<0>
	mov	#'*,(r5)
	mov	#dofst,cmdcmd	;command=find first match
	br	30$		;skip
10$:	; starting midway through block, we owe the creation date from the
	; file entry that spanned the block boundary
	clr	r5		;creation date=0
	call	putw
20$:	; retrieve next filename
	mov	#donxt,cmdcmd	;command=find next match
30$:	tst	dirdon		;already hit EOF?
	bne	90$		;yes
	mova	file,r5		;point at buf
	mov	r5,cmdba	;set addr
	clr	cmdbae
	mov	#lfile,cmdlen	;length of buf
	call	docmd		;get next dir entry
	bcs	80$		;failed
	clrb	efile		;guarantee terminating NUL
	cmp	diroff,#<<1000-<5*2>>/16>*16+<5*2> ;about to wrap to 2nd blk?
	bne	40$		;(that number works out to 764)
	 inc	dirblk		;yes, plan ahead
40$:	mov	#2000,r5	;status=permanent file
	call	putw
	; convert filename to RAD50, and trim off path in the process
	mova	file,r5		;point at filename
	mov	r0,-(sp)	;save
	mov	r1,-(sp)
	mov	r2,-(sp)
50$:	call	getrad		;get filename
	cmp	r2,#'.		;it *is* the filename right?
	beq	60$		;yes
	tst	r2		;filename with no extension?
	bne	50$		;no, device or path element, ignore
60$:	mov	r5,-(sp)	;save ptr
	mov	r0,r5		;save first word
	call	putw
	mov	r1,r5		;save second word
	call	putw
	mov	(sp)+,r5	;restore ptr
	mov	r2,r0		;extension?  (R0=0 if not)
	beq	70$		;no, go save the zero
	call	getrad		;parse extension
70$:	mov	r0,r5		;save
	call	putw
	mov	(sp)+,r2	;restore
	mov	(sp)+,r1
	mov	(sp)+,r0
	clr	r5		;length=0 (since we don't know)
	call	putw
	clr	r5		;channel/job=0
	call	putw
	; if they're reading the segment (which is 2 blocks) one block at a
	; time, we can get kicked out on the next write (i.e. they only asked
	; for the first block and this dir entry is overflowing into the 2nd)
	; we compensate by starting odd blocks off with a zero word
	clr	r5		;creation date=0
	call	putw
	cmp	diroff,#2000-<16+2> ;space for one more entry and EOS marker?
	bhis	90$		;no, write end-of-segment marker now
	br	20$		;back for next file
80$:	mov	(pc),dirdon	;done reading dir
90$:	; finish off segment
	mov	#4000,r5	;mark end of segment
	call	putw
100$:	bit	#777,diroff	;finished filling out blk?
	beq	120$		;yes
110$:	clr	r5		;write a 0
	call	putw
	br	100$
120$:	; prepare for next segment
	inc	dirblk		;bump to next blk
	cmp	diroff,#2000	;had we finished padding whole segment?
	bne	110$		;no, go pad second block of seg
	clr	diroff		;reinit offset
	tst	q$wcnt(r4)	;req finished?
	beq	130$		;yes
	jmp	dir
130$:	jmp	dodone		;finish up
;+
;
; Store a byte in the user's buffer.
;
; r5	byte to store
; r4	I/O qel
;
; R0-R4 preserved, Z=1 on return if buf is full.
;
;-
putb:
.if ne mmg$t
	movb	r5,-(sp)	;store byte
	call	@$ptbyt		;(and update Q$BUFF)
.iff
	movb	r5,@q$buff(r4)	;store byte
	inc	q$buff(r4)
.endc
	dec	q$wcnt(r4)	;done all?  (Z=1 if so)
	rts	pc
;+
;
; As above, but stores a word, updates DIROFF, and punts if out of space.
;
;-
putw:	cmp	q$wcnt(r4),#2	;got space?
	blo	10$
	add	#2,diroff	;update offset
	call	putb		;write low byte
	swab	r5		;get high byte
	br	putb		;write that too, return
10$:	tst	(sp)+		;flush return address
	jmp	dodone		;say I/O is finished
;+
;
; Get filename passed by monitor.
;
;-
getnam:
.if ne mmg$t
	mova	file50,r5	;point at buffer
	mov	#3*2,r0		;counter (# bytes to copy)
10$:	call	@$gtbyt		;get a byte
	movb	(sp)+,(r5)+	;catch it
	dec	r0		;loop through all
	bne	10$
	sub	#6,r5		;point at filename
.iff
	mov	q$buff(r4),r5	;get ptr to .RAD50 name
.endc
	mova	file,r4		;point at filename buf
	mov	r4,cmdba	;set addr
	clr	cmdbae
	call	rad$		;filename
	call	rad$
	movb	#'.,(r4)+	;.
	call	rad$		;ext
	clrb	(r4)
	sub	cmdba,r4	;find length
	mov	r4,cmdlen	;save length
	rts	pc
;+
;
; Set up a "seek" command packet for file addr in qel at R4.
;
;-
seek:	call	gethnd		;get handle
	bcs	10$		;not open
	mov	q$blkn(r4),r0	;get starting block #
	mov	r0,r1		;copy
	swab	r0		;left 8
	clrb	r0		;(clear LSB)
	asl	r0		;left 9
	mov	r0,cmdba	;set low addr
	swab	r1		;right 8, old b7 in b15
	asl	r1		;right 7, old b7 in C
	adc	r1		;old b7 in b0
	bic	#^C777,r1	;isolate high 9
	mov	r1,cmdbae
	clr	cmdprm		;seek from BOF (C=0)
	mov	#dosek,cmdcmd	;[func=seek]
10$:	rts	pc
;+
;
; Load up address (etc.) information for DOS file device access.
;
; r4	qel (preserved)
;
;-
addr:
.if ne mmg$t
	mov	r4,r5		;copy qel ptr
	add	#q$buff,r5	;index to buf addr
	call	@$mpptr		;call $MPPHY
	mov	(sp)+,cmdba	;low addr
	mov	(sp)+,r5	;catch high addr
	asr	r5		;right-justify
	asr	r5
	asr	r5
	asr	r5
	mov	r5,cmdbae	;save
.iff
	mov	q$buff(r4),cmdba ;save addr
	clr	cmdbae		;high bits = 0
.endc
	mov	q$wcnt(r4),cmdlen ;length
	rts	pc
;+
;
; Get file handle for innermost open file.
;
; Returned in CMDHND, or C=1 if raw device or no file.
;
; R0 trashed, others preserved.
;
;-
gethnd:	tst	opensp		;could there be files open?
	beq	10$		;no
	mova	flgstk,r0	;point at file flag stack
	add	opensp,r0	;index to our entry
	tst	-(r0)		;open file?
	beq	10$		;no
	mov	hndstk-flgstk(r0),cmdhnd ;yes, fetch the handle
	tst	(pc)+		;C=0, skip SEC
10$:	 sec			;no file
	rts	pc
;+
;
; Save file handle for newly opened file.
;
; Stack space already checked by NULFIL.
;
; R0 trashed, others preserved.
;
;-
savhnd:	add	#2,opensp	;advance to next
	mova	flgstk,r0	;point at file flag stack
	add	opensp,r0	;index to our entry
	mov	#1,-(r0)	;say open file
	mov	cmdhnd,hndstk-flgstk(r0) ;save handle
	rts	pc
;+
;
; Parse filename, see if they're opening the null filename.
;
; Return C=1 if so (or if out of stack space), stack entry created.
; C=0 if not, so open the file and add its stack entry.
;
;-
nulfil:	cmp	opensp,#nstmax*2 ;stack full?
	beq	10$		;yes, give up now
	call	getnam		;convert filename
	cmp	cmdlen,#1	;anything besides the "." we added?
	bne	20$		;yes, C=0
	add	#2,opensp	;advance to next
	mova	flgstk,r0	;point at file flag stack
	add	opensp,r0	;index to our entry
	clr	-(r0)		;say no open file
10$:	sec
20$:	rts	pc
;
docsr:	.word	do$csr		;addrs of device regs
doba:	.word	do$ba
dobae:	.word	do$bae
	.even
;+
;
; Do the current command, appear to return when done.
;
; On return, only R4/R5 may be used (because we're on an interrupt).
; C=1 if DOS file device returned error, R4 points at I/O qel.
;
;-
docmd:	mov	(sp)+,docont	;save continuation addr
	mova	cmdpkt,r0	;point at packet buf
	mov	r0,@doba	;set packet addr
	clr	@dobae
	mov	#do$vec,r0	;get vector
	asr	r0		;/4
	asr	r0
	swab	r0		;in LH
	bis	#<4*dopri>!dogo!doie,r0 ;set PRI=4, GO, IE
	mov	r0,@docsr	;start command
	rts	pc
;+
;
; Interrupt service routine.
;
;-
	.drast	do,4,doabrt
	clr	@docsr		;kill further ints
	tst	docont		;spurious int?
	beq	10$		;yes
	mov	docqe,r4	;get qel back
	cmp	#1,cmdsts	;C=1 if CMDSTS is non-zero
	jmp	@docont		;continue
10$:	rts	pc		;dismiss
;
doabrt:	clr	@docsr		;kill ints
;;; no way to abort cmd in progress?
	rts	pc
;+
;
; Convert from ASCII to .RAD50.
;
; r0	returns first 3 chars
; r1	returns second 3 chars
; r2	returns terminator (non-RAD50 char that we stopped on)
; r5	pointer into .ASCIZ string (updated on return)
;
; R3/R4 preserved.
;
;-
getrad:	mov	r3,-(sp)	;save
	call	20$		;get first 3
	mov	r1,r0		;save
	call	20$		;get last 3
10$:	call	40$		;try to get another char
	tst	r2		;was it a .RAD50 char?
	bne	10$		;yes, skip it
	movb	(r5)+,r2	;get terminator
	mov	(sp)+,r3
	rts	pc
;
20$:	; get 3 .RAD50 hars from (R5)+ into R1
	call	40$		;get 1st char
	mov	r2,r1		;copy
	call	30$		;add 2nd char, then drop through for 3rd
30$:	asl	r1		;*10
	asl	r1
	asl	r1
	mov	r1,r2		;save value *10
	asl	r1		;*40
	asl	r1
	add	r2,r1		;*50
	call	40$		;get new char
	add	r2,r1		;add it in
	rts	pc
;
40$:	; get .RAD50 char from (R5)+ into R2, trashes R3
	movb	(r5)+,r3	;get char
	mov	#<^R  $>,r2	;try $
	cmp	r3,#'$
	beq	60$
;;	mov	#<^R  .>,r2	;try .
;;	cmp	r3,#'.
;;	beq	60$
	mov	r3,r2		;try digit
	add	#<^R  0>-'0,r2	;convert value if digit
	cmp	r3,#'0
	blo	50$
	cmp	r3,#'9
	blos	60$		;yes it's a digit
	add	#<<^R  A>-'A>-<<^R  0>-'0>,r2 ;translate to letter
	cmp	r3,#'A		;is it a letter?
	blo	50$
	cmp	r3,#'Z
	blos	60$
	sub	#40,r2		;suppose it's lower case somehow
	cmp	r3,#'a
	blo	50$
	cmp	r3,#'z
	blos	60$
50$:	dec	r5		;none of the above, it's a delimiter
	clr	r2		;return a blank to pad out field
60$:	rts	pc
;+
;
; Routine to convert a word from .RAD50 to ASCII.
;
; r5	input ptr (+2 on return)
; r4	output ptr (+0:3 on return)
;
;-
rad$:	mov	r5,-(sp)	;save
	mov	(r5),r5		;fetch word
	mova	bittab,r3	;pt at table
	br	40$		;jump into loop
10$:	; next digit
	clr	r0		;clear it
	mov	#6,r2		;bit count (each .RAD50 dig is 5.625 bits)
20$:	asl	r0		;*2
	cmp	r1,r5		;OK?
	bhi	30$		;no (C=0)
	sub	r1,r5		;remove the bit (C=0)
	inc	r0		;count it
30$:	ror	r1		;/2 (C=0 either way above)
	dec	r2		;loop
	bne	20$
	add	pc,r0		;index into R50
	add	#r50-.,r0
	movb	(r0),(r4)+	;convert, save
	bne	40$
	 dec	r4		;un-put NUL
40$:	mov	(r3)+,r1	;get next value
	bne	10$		;loop if non-zero
	add	pc,r5		;index into R50
	add	#r50-.,r5
	movb	(r5),(r4)+	;convert last, save
	bne	50$
	 dec	r4		;un-put NUL
50$:	mov	(sp)+,r5	;restore
	tst	(r5)+		;skip the word we just did
	rts	pc
;
bittab:	.word	40*50*50,40*50,0
r50:	.ascii	<0>/abcdefghijklmnopqrstuvwxyz$.?0123456789/
strstr:	.asciz	/*.*/		;wildcard for dir lookup
;
docont:	.word	0		;continuation address (called on int)
				;or 0 if no int expected
;
opensp:	.word	0		;offset into "open file" stacks
hndstk:	.blkw	nstmax		;DOS handle for each file
flgstk:	.blkw	nstmax		;NZ if handle is valid (0 => opened raw dev)
;
dirblk:	.word	0		;next block # in sequence during dir lookup
				;(or 0 if not in dir lookup)
diroff:	.blkw			;offset into current 1 KB dir segment, 0-1777
dirdon:	.blkw			;NZ => finished reading dir
;
;unit:	.blkw			;current DOn: unit # *2 (used as table index)
;
.if ne mmg$t
file50:	.blkw	3		;FILNAMEXT buffer
.endc
	.even			;DIR expects an even addr
file:	.blkb	10.		;FILNAM.EXT buffer
	.blkb	128.-10.	;more space for pathname in DOFST/DONXT
lfile=	.-file			;length of buf
efile:	.blkb			;guaranteed NUL at end
;
	.even
cmdpkt:	; command packet goes here
cmdcmd:	.blkw			;command code, 0-8.
cmdsts:	.blkw			;status (-1=timeout, 0=OK, >0=DOS err)
cmdhnd:	.blkw			;DOS file handle
cmdprm:	.blkw			;parameter (if defined by cmd)
cmdlen:	.blkw			;length of data buffer in bytes
cmdba:	.blkw			;22-bit absolute addr of data buffer
cmdbae:	.blkw
;
	.even
;
	.drend	do
	.end
