.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. ; ;-- .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 #-.,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