	TITLE	DSKDRV - FORTRAN-80 DISK DRIVER

	.8080

MAXLUN	EQU	10		;MAX # OF LUN'S ALLOWED

;BDOS FUNCTION CALLS, FCB OFFSETS

BDOS	EQU	5		;CP/M BDOS ENTRY POINT

.RSET	EQU	13		;DISK RESET
.SELCT	EQU	14		;SELECT DISK
.OPEN	EQU	15		;OPEN FILE
.CLOSE	EQU	16		;CLOSE FILE
.DELET	EQU	19		;DELETE FILE
.MAKE	EQU	22		;CREATE FILE
.STDMA	EQU	26		;SET DMA ADDRESS

FCB.FT	EQU	9		;FILE TYPE
FCB.EX	EQU	12		;EXTENT BYTE
FCB.RC	EQU	15		;RECORD COUNT BYTE
FCB.NR	EQU	32		;NEXT RECORD  BYTE
FCB.RR	EQU	33		;RANDOM RECORD NUMBER (2.X)
FCBLEN	EQU	36		;FCB SIZE

SECSIZ	EQU	128		;SECTOR SIZE (RECORD LENGTH)

; GLOBAL DEFINITIONS

	EXTRN	$CPMVN,$CPMRF,$CPMWF,$BL,$BF,$ERR
	EXTRN	$IOERR,$REC,$UN,$LUNTB,$CLSFL

	ENTRY	$DSKER,$FLFLG,$MEMRY,DSKDRV

	DSEG			;DATA AREA

;  I/O ERROR CODE DEFNS

OBOVF	EQU	016Q		;OUTPUT BUFFER LIMIT EXCEEDED
IRECER	EQU	022Q		;INPUT RECORD TOO LONG
NOFILE	EQU	236Q		;FILE NOT FOUND
FULERR	EQU	237Q		;DISK FULL
LUNERR	EQU	240Q		;LUN TOO LARGE
NOMEM	EQU	241Q		;OUT-OF-MEMORY

;  I/O MODE DEFN BYTE 

MD.ALC	EQU	80H		;80H  ALLOCATED BUFFER AND FCB
MD.OPN	EQU	40H		;40H  FILE IS OPEN
MD.OUT	EQU	20H		;20H  IF OUTPUT
MD.BIN	EQU	10H		;10H  IF UNFORMATTED I/O

MD.WRT	EQU	08H		;08H  WRITE-DATA-IN-BUFFER
MD.RND	EQU	04H		;04H  IF RANDOM I/O

$FLFLG:	DS	MAXLUN		;I/O MODE BYTE FOR EACH LUN
$FLCNT:	DS	MAXLUN		;I/O BUFFER INDEX FOR EACH LUN
$FLBUF:	DS	MAXLUN*2	;BUFFER LOCATION FOR EACH LUN
$FLFCB:	DS	MAXLUN*2	;FCB LOCATION FOR EACH LUN

$DSKER:	DS	1		;STATUS OF LAST I/O
CLSADR:	DS	2

$MEMRY:	DS	2		;FOR LOADER TO STORE TOP OF MEM INTO

	CSEG			;CODE AREA

FILTXT:	DB	"FORT",0	;DEFAULT FILENAME TEXT
FILEXT:	DB	"DAT",0

	PAGE

;  I/O DISPATCH TABLE

;  LUN'S 6 THRU MAXLUN POINT TO THIS TABLE VIA $LUNTB.
;  AN EXPLICIT OPEN VIA CALL OPEN (<FILESPEC>) ALLOW
;  OTHER UNITS TO USE THE DISK ALSO...

DSKDRV:	DW	DSKFRD		;FORMATTED READ
	DW	DSKFWR		;FORMATTED WRITE
	DW	DSKURD		;UNFORMATTED READ
	DW	DSKUWR		;UNFORMATTED WRITE
	DW	DSKREW		;REWIND
	DW	$IOERR		;BACKSPACE (NOT SUPPORTED).
	DW	DSKCLS		;ENDFILE

FNFERR:	CALL	$ERR
	DB	NOFILE		;FILE NOT FOUND

DSKFUL:	CALL	$ERR
	DB	FULERR		;DISK FULL

LUNOVF:	CALL	$ERR
	DB	LUNERR		;LUN TOO LARGE

MEMERR:	CALL	$ERR
	DB	NOMEM		;OUT-OF-MEMORY

;------------------------------------------------------
;
;  GET MODE BYTE(LUN) FROM $FLFLG
;
GTMODE:	LXI	H,$FLFLG-1
	LDA	$UN		;GET UNIT #
	MVI	D,0
	MOV	E,A
	DAD	D		;[HL] POINTS TO FLAG
	MOV	A,M		;GET FLAG
	RET
;------------------------------------------------------
;
;  SET DMA TO DATA BUFFER(LUN)
;
SETBUF:	PUSH	B
	PUSH	D
	CALL	GETBUF		;GET BUFFER ADR
	MVI	C,.STDMA
	CALL	BDOS
	POP	D
	POP	B
	RET
;------------------------------------------------------
GETBUF:	LDA	$UN
	ADD	A
	MOV	E,A
	MVI	D,0
	LXI	H,$FLBUF-2
	DAD	D
	MOV	E,M
	INX	H
	MOV	D,M
	RET

;------------------------------------------------------
;
;  GET DATA.BUFFER.OFFSET(LUN)
;
GTBOFF:
	LXI	H,$FLCNT-1
	LDA	$UN
	MVI	D,0
	MOV	E,A
	DAD	D
	MOV	A,M		;GET OFFSET
	RET

;------------------------------------------------------
;
;  GET ADR OF FCB(LUN) IN [DE]
;
GTFCB:	LXI	H,$FLFCB-2
	LDA	$UN
	ADD	A
	MOV	E,A
	MVI	D,0
	DAD	D		;POINT TO ADR OF FCB
	MOV	E,M
	INX	H
	MOV	D,M
	RET

;------------------------------------------------------
;
;  GET READ/WRITE RECORD NUMBER FOR LUN
;  ON RETURN:
;		[HL] = FCB.RR(LUN) ADR OF RND REC NUM.
;		[DE] = $REC	   00  OR RND REC NUM.
;
GTREC:
	CALL	GTFCB
	LXI	H,FCB.RR
	DAD	D
	XCHG
	LHLD	$REC
	XCHG
	RET
;------------------------------------------------------
;  ZERO FCB FROM EXTENT BYTE TO END.
;
CLRFCB:
	CALL	GTFCB
	LXI	H,FCB.EX
	DAD	D
	LXI	B,FCBLEN-FCB.EX
CLRFCL:
	MOV	M,B
	INX	H
	DCR	C
	JNZ	CLRFCL
	RET
;------------------------------------------------------
;  COPY BYTES FROM [DE] TO [HL] UNTIL NULL (00).
;
CPYTXT:
	MOV	M,A		;COPY FROM [DE] TO [HL]
	INX	H		;UNTIL NULL.
	INX	D
	LDAX	D
	ORA	A
	JNZ	CPYTXT
	RET

	PAGE
;------------------------------------------------------
;
;  OPNCHK - ASSURE THAT FILE IS OPEN AND BUFFERS ALLOCATED.
;	    IF NOT THEN:
;	1.  ALLOCATE DATA BUFFER AND FCB FROM $MEMRY.
;	2.  OPEN FILE WITH NAME OF FORT##.DAT WHERE:
;	    ## IS LOGICAL-UNIT-NUMBER (LUN) OF FILE.

OPNCHK:
	LDA	$UN
	CPI	MAXLUN+1	;
	JNC	LUNOVF		;LUN IS TOO LARGE
;	----------------
	PUSH	B		;SAVE OPEN MODE REQUEST.
	CALL	GTMODE		;GET FLAG
	ORA	A		;ALLOCATED BUFFER,FCB?
	CP	ALCBUF		;NO, GET SPACE AND SET PTRS
;	----------------
	POP	B		;GET I/O MODE IN [C]
	ANI	MD.OPN		;WAS OPEN?
	CZ	OPNFIL		;NO, OPEN FILE.

;------------------------------------------------------
;
;  SETREC - SET UP RECORD NUMBER
;
SETREC:
	CALL	GTREC		;GET CURRENT & LAST RECORD NUMBERS
	MOV	A,D		;[DE] = $REC
	ORA	E		;IF RANDOM I/O
	JNZ	SETRND		;USE VALUE IN $REC
;	----------------
	MOV	E,M		;..ELSE
	INX	H		;USE CURRENT RECORD
	MOV	D,M		;SET BY SEQ READ/WRITE.
	PUSH	D		;SAVE REC NO.
	CALL	GTMODE
	ANI	NOT MD.RND	;SET SEQUENTIAL MODE
	MOV	M,A
	POP	D		;RESTORE REC NO.
	JMP	SETSEQ
;	----------------
SETRND:
	CALL	GTMODE
	ANI	MD.WRT		;WRITE-DATA-IN-BUFFER?
	CNZ	FRCOUT		;YES, FLUSH IT FIRST.
	CALL	GTMODE
	ORI	MD.RND		;SET RANDOM MODE
	MOV	M,A
	CALL	GTREC		;RESTORE REC NO'S.
	DCX	D		;ADJUST RANDOM REC NO.
	MOV	M,E
	INX	H		;STORE AT FCB.RR FOR NEXT I/O
	MOV	M,D
	INX	H
	MVI	M,0
;	----------------
SETSEQ:
	LDA	$CPMVN		;CP/M VERSION FLAG
	ORA	A		;VERSION 1.X ?
	RZ			;NO, VERSION 2.X NOTHING ELSE NEEDED.
;------------------------------------------------------
;
;  SPLIT RANDOM RECORD INTO EXTENT AND RELATIVE RECORD
;	FOR VERSION 1.X
;
SETV1:
	MOV	A,E
	RAL
	MOV	A,D
	RAL
	MOV	B,A
	MOV	A,E
	ANI	X'7F'
	MOV	C,A
;	----------------
	CALL	GTFCB		;[DE] = ADR OF FCB(LUN)
	LXI	H,FCB.EX
	DAD	D
	MOV	A,M		;FETCH CURRENT EXTENT
	CMP	B
	JZ	SAMEXT		;IF SAME AS REQUESTED EXTENT
;
;  CLOSE CURRENT EXTENT, OPEN REQUESTED ONE
;
	PUSH	B		;EXT/REC
	PUSH	D		;FCB ADR
	LXI	H,FCB.NR
	DAD	D
	MVI	M,0		;ZERO THE RECORD NUMBER
	LHLD	$MEMRY		;GET SCRATCH BUFFER
	XCHG			;FROM TOP OF HEAP FOR OPEN/CLOSE
	MVI	C,.STDMA
	CALL	BDOS
	CALL	GTMODE
	ANI	MD.OUT		;OPEN FOR OUTPUT?
	JZ	OPNXT		;NO, SKIP THIS CLOSE
	POP	D
	PUSH	D		;FCB
	MVI	C,.CLOSE
	CALL	BDOS
OPNXT:
	POP	D		;FCB
	POP	B		;EXT/REC
	PUSH	B
	PUSH	D
	LXI	H,FCB.EX
	DAD	D
	MOV	M,B		;SET NEW EXTENT NUMBER
	MVI	C,.OPEN
	CALL	BDOS
	INR	A
	JNZ	SKEXT		;IF IT EXISTS
	POP	D
	PUSH	D		;FCB
	CALL	MAKEXT		;CREATE NEW EXTENT
SKEXT:
	POP	D		;FCB
	POP	B		;EXT/REC
SAMEXT:
	LXI	H,FCB.NR
	DAD	D
	MOV	M,C		;SET RECORD NUMBER
	RET

	PAGE
;------------------------------------------------------
;  ALLOCATE FCB AND BUFFER FOR UNIT . ALLOCATES UP FROM
;  $MEMRY AND STORES PTRS IN TABLE.

ALCBUF:	PUSH	H
	LHLD	$MEMRY		;GET CURRENT TOP
	XCHG			;IN [DE]
;	----------------
	LXI	H,-256
	DAD	SP
	MOV	A,L
	SUB	E		;IF MEMTOP-256 < $MEMRY,
	MOV	A,H		;THEN GIVE OUT-OF-MEMORY
	SBB	D		;ERROR AND EXIT...
	JC	MEMERR
;	----------------
	LXI	H,$FLBUF-2
	LDA	$UN
	ADD	A
	MVI	B,0
	MOV	C,A		;[BC]= 2*UNIT#
	DAD	B		;GET ADR OF PTR TO BUFFER
	MOV	M,E
	INX	H
	MOV	M,D		;STORE $MEMRY AS ADR
	LXI	H,SECSIZ
	DAD	D		;GET NEW TOP
	PUSH	H		;SAVE FCB ADR
	CALL	GTFCB		;GET [HL]=ENTRY+1
	POP	D
	PUSH	D		;SAVE FCB ADR
	MOV	M,D
	DCX	H
	MOV	M,E		;STORE ADR OF FCB
	LXI	H,FCBLEN
	DAD	D		;GET NEW TOP OF ALLOC
	SHLD	$MEMRY		;STORE AWAY
;	----------------
	POP	H		;FCB ADR.
	MVI	M,0		;DEFAULT TO CURRENT DISK
	INX	H
	LXI	D,FILTXT	;DEFAULT NAME INITIALLY FORT##.DAT
	LDAX	D
	CALL	CPYTXT		;MOVE "FORT" TO FCB
	LDA	$UN
	MOV	B,A		;SAVE UNIT #
	SUI	10		;CARRY SET IF NOT UNIT 10
	SBB	A
	ADI	"1"		;"1" IF 10 ELSE "0"
	MOV	M,A		;STORE DIGIT 1 OF #
	MOV	A,B
	CPI	10		;WAS IT 10?
	JC	SKPSTZ		;NO, DON'T SET ZERO
	XRA	A
SKPSTZ:	ADI	"0"		;GET 2ND DIGIT
	INX	H
	MOV	M,A
	MVI	A," "
	INX	H
	MOV	M,A
	INX	H
	LXI	D,FILEXT-1	;EXTENSION
	CALL	CPYTXT		;MOVE "DAT" TO FCB
;	----------------
	POP	H		;GET PTR TO FLAG
	MVI	A,MD.ALC	;NOW ALLOCATED.
	MOV	M,A		;SET FLAG
	RET

	PAGE
;------------------------------------------------------
;
;  OPEN FILE GIVEN BY LUN AND CLEAR BUFFER INDEX.
;  ENTRY:	[DE] = ADR OF FCB
;  EXIT:	[HL] = ADR OF MODE BYTE
;		[A]  = MODE BYTE -  ALLOCATED & OPEN.
;
OPNFIL:
	PUSH	B		;SAVE I/O MODE
	CALL	GTBOFF		;GET BUFFER.OFFSET(LUN)
	MOV	M,D		;CLEAR BUFFER INDEX
;	----------------
	LHLD	$CLSFL
	LXI	D,CLSALL
	MOV	A,H
	SUB	D
	JNZ	STRADR		;NOT US, STORE ROUTINE ADR
	MOV	A,L
	SUB	E
	JZ	STRCLS		;US, DON'T STORE AGAIN
STRADR:	SHLD	CLSADR
STRCLS:	XCHG
	SHLD	$CLSFL		;ON EXIT, CLOSE ALL FILES
	CALL	CLRFCB		;ZERO FCB & RECORD NUMBER.
	CALL	SETBUF		;SET DMA TO FILE BUFFER.
;	----------------
	POP	B		;I/O MODE
	PUSH	B
	MOV	A,C
	ANI	MD.OUT
	JZ	OPNINP		;BRIF OPEN INPUT
;------------------------------------------------------
;
;  OPEN NEW FILE FOR OUTPUT OR RANDOM.
;
	PUSH	D		;SAVE FCB ADR
	MVI	C,.DELET	;DELETE OLD FILE
	CALL	BDOS
	POP	D
	PUSH	D
	CALL	MAKEXT		;CREATE NEW FILE.
	POP	D		;RESTORE FCB ADR
;------------------------------------------------------
;
;  OPEN EXISTING FILE FOR INPUT OR RANDOM.
;
OPNINP:
	MVI	C,.OPEN		;OPEN FILE
	CALL	BDOS
	INR	A
	JZ	FNFERR		;BRIF FILE NOT FOUND (FATAL).

OPNDON:
	CALL	GTMODE
	POP	B		;GET I/O MODE.
	MOV	A,C
	ORI	MD.ALC+MD.OPN
	MOV	M,A		;MODE ALLOCATED/OPEN + I/O
	RET

;------------------------------------------------------
;
;  MAKE NEW FILE OR EXTENT.
;
MAKEXT:
	MVI	C,.MAKE		;CREATE NEW FILE
	CALL	BDOS
	INR	A
	JZ	DSKFUL		;BRIF DISK FULL ERROR.
	RET

	PAGE
;------------------------------------------------------
;
;  REWIND UNIT #
;
DSKREW:	CALL	GTMODE
	ANI	MD.OPN		;FILE OPEN?
	CNZ	DSKCLS		;YES, CLOSE IT
NOCLOS:	XRA	A		;GOOD RETURN
	RET

;------------------------------------------------------
;
;  ENDFILE UNIT #
;
DSKCLS:	CALL	GTMODE
	ADD	A		;OPEN?
	JP	NOCLOS		;NO, DON'T CLOSE
	MVI	M,MD.ALC	;FLAG CLOSED NOW
	ADD	A		;OUTPUT FILE?
	CM	FRCBUF		;YES, DUMP LAST IF NEEDED
	CALL	GTFCB		;GET ADDR OF FCB
	CALL	SETBUF		;SET DMA ADR
	MVI	C,.CLOSE	;CLOSE FILE
	CALL	BDOS
	XRA	A		;NEVER AN ERROR
	RET

;------------------------------------------------------
;
;  CLOSE ALL FILES. CALLED FROM EXIT
;
CLSALL:	LXI	D,1
CLSAL1:	LXI	H,$FLFLG-1
	DAD	D
	MOV	A,M
	ADD	A		;SET MINUS IF OPEN
	PUSH	D		;SAVE UNIT #
	MOV	A,E
	STA	$UN		;SET UP FOR OTHERS
	CM	DSKCLS		;CLOSE FILE IF OPEN
	POP	D		;GET # BACK
	INX	D		;BUMP IT
	LDA	$LUNTB		;GET MAX LUN
	CMP	E		;DONE ALL?
	JNZ	CLSAL1		;NO, DO NEXT
	RET			;RETURN

	PAGE
;------------------------------------------------------
;
;  UNFORMATTED WRITE
;
DSKUWR:
	MVI	C,MD.OUT+MD.BIN
	CALL	OPNCHK		;OPEN IF NOT OPEN
	LHLD	$BF		;GET BUFFER ADR
	PUSH	H
	XCHG
	LHLD	$BL		;GET LENGTH OF DATA
	MVI	H,0		;# OF BYTES
	XCHG
	DAD	D		;PTR TO 1ST TO CLEAR
	XRA	A
	DCR	E
DSKWCL:
	INR	E		;CLEARED REST OF BUFFER?
	JM	DSKUW1		;YES, DONE
	MOV	M,A
	INX	H
	JMP	DSKWCL		;CLEAR END OF BUFFER
DSKUW1:
	POP	D		;GET BUFFER ADR
	MVI	C,.STDMA	;SET DMA TO BUFFER
	CALL	BDOS
	CALL	WRITE		;WRITE RECORD
	ORA	A		;ERROR?
	RZ			;NO, GOOD RETURN
	STC
	RET

	PAGE
;------------------------------------------------------
;
;  UNFORMATTED READ
;
DSKURD:
	MVI	C,MD.BIN
	CALL	OPNCHK		;OPEN IF NEEDED
	LHLD	$BF
	XCHG
	MVI	C,.STDMA	;SET DMA TO $BF
	CALL	BDOS
	MVI	A,128
	STA	$BL		;ALWAYS 1 SECTOR
	CALL	READ		;READ RECORD INTO $BF
	ORA	A		;EOF OR GOOD
	RZ
	CPI	2
	CMC
	RET
;
	PAGE
;------------------------------------------------------
;
;  FORMATTED WRITE
;
DSKFWR:
	MVI	C,MD.OUT
	CALL	OPNCHK		;OPEN FILE IF NEEDED
	XRA	A
	STA	$DSKER		;CLEAR COUNT
	LDA	$BL		;GET # TO WRITE
	ORA	A
	RZ			;IGNORE NULL BUFFERS
	LDA	$BL
	ORA	A
	JP	DSKFW0
	CALL	$ERR		;WARN USER OF..
	DB	OBOVF		;OUTPUT BUFFER LIMIT EXCEEDED
	MVI	A,127		;AND TRUNCATE TO 127 BYTES.
DSKFW0:
	CALL	RNDCHK		;IF RND MODE, SET OFFSET TO 0.
	LHLD	$BF		;GET BUFFER PTR
DSKFW2:
	PUSH	PSW		;SAVE COUNT
	MOV	A,M
	CALL	DSKOUT		;SEND OUT BYTE
	INX	H		;INCREMENT BUFFER PTR
	POP	PSW		;RETRIEVE COUNT
	DCR	A		;DECREMENT COUNT
	JNZ	DSKFW2		;ONE MORE TIME
;
DSKWDN:	MVI	A,15Q
	CALL	DSKOUT		;PUT OUT <CR>
	CALL	GTMODE		;MARK MODE BYTE WITH
	ORI	MD.WRT		;WRITE-DATA-IN-BUFFER.
	MOV	M,A
	LDA	$DSKER		;GET $DSKEROR STATUS
	ORA	A		;ERROR?
	RZ			;NO
	STC			;YES
	RET
;
	PAGE
;------------------------------------------------------
;
;  FORMATTED READ
;
DSKFRD:
	MVI	C,0
	CALL	OPNCHK		;OPEN IF NEEDED
	XRA	A
	STA	$BL		;CLEAR LENGTH OF RECORD
	STA	$DSKER		;CLEAR $DSKEROR
	CALL	RNDCHK		;IF RND MODE, SET OFFSET TO 0.
DSKFR1:
	CALL	DSKIN		;GET CHAR
	JC	DSKRDN		;JUMP IF EOF
	CPI	32Q		;CP/M EOF?
	JZ	DSKEOF		;YES
	LHLD	$BL
	MVI	H,0
	XCHG
	LHLD	$BF		;BASE
	DAD	D		;GET WHERE TO PUT BYTE
	MOV	M,A		;STORE CHAR
	INX	D		;BUMP OFFSET
	XCHG
	SHLD	$BL		;UPDATE OFFSET
	CPI	15Q		;<CR>?
	JZ	DSKRDN		;YES, HAVE RECORD
	MOV	A,L		;GET OFFSET
	CPI	128		;BUFFER FULL?
	JC	DSKFR1		;NO, GET NEXT
	CALL	$ERR		;WARN USER OF..
	DB	IRECER		;INPUT RECORD TOO LONG.
	XRA	A
	RET
;
DSKRDN:	LDA	$DSKER		;GET STATUS
	ORA	A
	RZ			;NO ERROR
	CPI	2
	CMC
	RET
DSKEOF:	ORA	A		;SET CC'S
	RET
;
	PAGE
;------------------------------------------------------
;
;  RESET BUFFER OFFSET IF RANDOM READ OR WRITE
;
RNDCHK:
	PUSH	PSW
	CALL	GTMODE
	ANI	MD.RND
	JZ	RNDCHX		;DO NOTHING IF SEQUENTIAL MODE.
	CALL	GTBOFF		;ELSE ZERO BUFFER OFFSET SO..
	MVI	M,0		;NEXT READ/WRITE STARTS AT BEGINNING.
RNDCHX:
	POP	PSW
	RET
;------------------------------------------------------
;
;  DISK INPUT ROUTINE
;
DSKIN:
	CALL	GTBOFF		;BUFFER.OFFSET(LUN)
	ORA	A		;BUFFER EMPTY?
	CZ	REDBUF		;YES, REFIL
	RC			;EOF
	DCR	M		;DECR. # LEFT
	LXI	H,$FLBUF-2
	DAD	D
	DAD	D		;GET $FLBUF PTR
	CMA
	ADI	129		;128-#LEFT=OFFSET
	ADD	M
	MOV	E,A
	INX	H
	MVI	A,0
	ADC	M
	MOV	D,A
	LDAX	D		;GET CHAR
	ORA	A
	RET
;------------------------------------------------------
;
;  READ BUFFER FROM DISK
;
REDBUF:	PUSH	H
	PUSH	D
	CALL	SETBUF
	CALL	READ		;READ RECORD
	STA	$DSKER		;STORE STATUS
	ORA	A
	POP	D
	POP	H
	MVI	A,128		;FULL BUFFER
	MOV	M,A		;STORE COUNT
	RZ			;IF NO ERROR
	STC
	RET

;------------------------------------------------------
;
;  DISK OUTPUT ROUTINE
;
DSKOUT:	PUSH	H
	PUSH	PSW
	CALL	GTBOFF		;GET BUFFER OFFSET
	ORA	A		;BUFFER FULL?
	CM	DMPBUF		;YES, DUMP
	INR	M
	LXI	H,$FLBUF-2
	DAD	D
	DAD	D		;POINT TO ADR OF BUFFER
	ADD	M
	MOV	E,A
	INX	H
	MVI	A,0
	ADC	M
	MOV	D,A		;POINTS TO FREE
	POP	PSW		;GET CHAR BACK
	STAX	D		;STORE CHAR
	POP	H
	RET

;------------------------------------------------------
;
;  FORCE FORMATTED BUFFER OUT ON CLOSE
;
FRCBUF:	ADD	A		;UNFORMATTED I/O?
	JM	FRCOUT		;YES, FORCE OUT LAST IF NEEDED
	MVI	A,32Q
	CALL	DSKOUT		;SET END OF FILE

FRCOUT:	CALL	GTBOFF		;BUFFER.OFFSET(LUN)
	ORA	A		;EMPTY?
	MVI	M,0		;CLEAR OFFSET
	RZ			;YES, DO NOTHING
	PUSH	PSW		;SAVE OFFSET
	CALL	GETBUF		;GET BUFFER ADR
	POP	PSW
	MOV	L,A
	MVI	H,0
	DAD	D		;POINT TO 1ST UNUSED
CHKFIL:	ORA	A		;SET MINUS IF FULL
	JM	DMPBF1		;NOTHING TO FILL
	MVI	M,0		;CLEAR BYTE
	INX	H
	INR	A		;BUMP OFFSET
	JMP	CHKFIL
;------------------------------------------------------
;
;  DUMP BUFFER TO DISK
;
DMPBF1:
	DCX	H		;SO THAT DRIVE # ISN'T CLEARED
DMPBUF:
	PUSH	H
	PUSH	D
	CALL	SETBUF
	CALL	WRITE		;WRITE RECORD
	STA	$DSKER		;STORE STATUS
	CALL	GTMODE
	ANI	NOT MD.WRT	;CLEAR WRITE-DATA BIT
	MOV	M,A
	POP	D
	POP	H
	XRA	A
	MOV	M,A		;CLEAR BUFFER OFFSET
	RET

;------------------------------------------------------
;
;  READ RECORD/WRITE RECORD PRIMITIVES
;
READ:	CALL	GTFCB		;GET ADDR OF FCB
	LDA	$CPMRF		;CP/M 1.X OR 2.X
	MOV	C,A		;RANDOM READ FUNCTION
	JMP	IOCALL

WRITE:	CALL	GTFCB		;GET ADDR OF FCB
	LDA	$CPMWF		;CP/M 1.X OR 2.X
	MOV	C,A		;RANDOM WRITE FUNCTION

IOCALL:	CALL	BDOS		;DO APPROPRIATE FUNCTION
	PUSH	PSW		;SAVE ERROR RESULT
	CALL	GTREC		;POINT TO RECORD NUMBER
	INR	M		;UPDATE SEQUENTIAL
	JNZ	POPART		; RECORD NUMBER
	INX	H
	INR	M

POPART:	POP	PSW		;RESTORE ERROR
	RET

	PAGE
;------------------------------------------------------
;
;  CALL OPEN(UNIT #,FILENAME,DRIVE #)
;
	ENTRY	OPEN
	EXT	$IOINIT

OPEN:	LDA	$LUNTB		;GET MAX LUN
	DCR	A
	CMP	M		;OUT OF RANGE?
	JC	LUNOVF		;YES, LUN TOO LARGE.

	MOV	A,M		;FETCH LUN
	STA	$UN		;SET IT UP
	PUSH	D		;SAVE REGISTERS
	PUSH	B		;SAVE FILPTR
	CALL	$IOINIT		;INITIALIZE IF NOT ALREADY DONE
	CALL	GTMODE
	ORA	A		;ALLOCATED?
	CP	ALCBUF		;NO, ALLOCATE
	POP	B
	CALL	GTFCB
	XCHG
	POP	D		;[DE]=NAME PTR
	LDAX	B		;GET DRIVE #
	MOV	M,A		;STORE DRIVE #
	MVI	B,11		;COPY FILE NAME TO FCB
FILLOP:	LDAX	D
	ORA	A		;ZERO BYTE?
	JZ	FILLEN		;YES, LEAVE REST AS BEFORE
	INX	D
	INX	H
	MOV	M,A
	DCR	B		;FINISHED MOVING NAME?
	JNZ	FILLOP		;NO, DO REST
FILLEN:	LDA	$UN		;GET UNIT #
	ADD	A		;*2 FOR TABLE INDEX
	MVI	D,0
	MOV	E,A
	LXI	H,$LUNTB	;INDEX INTO LUN TABLE
	DAD	D
	LXI	B,DSKDRV	;REPLACE CURRENT LUNTB ENTRY
	MOV	M,B		; WITH ADDRESS OF DISK DRIVER
	DCX	H		; DISPATCH ADDRESS
	MOV	M,C
	RET

	END
