ZZ80CPM22.asm
Plasmode_ZZ80RC/SysSoftware/ZZ80CPM22.asm
;**************************************************************
;*
;*             C P / M   version   2 . 2
;*
;*   Reconstructed from memory image on February 27, 1981
;*
;*                by Clark A. Calkins
;*
;**************************************************************
;
;   Set memory limit here. This is the amount of contigeous
; ram starting from 0000. CP/M will reside at the end of this space.
;
MEM	EQU	62		;for a 62k system (TS802 TEST - WORKS OK).
;
IOBYTE	EQU	3		;i/o definition byte.
TDRIVE	EQU	4		;current drive name and user number.
ENTRY	EQU	5		;entry point for the cp/m bdos.
TFCB	EQU	5CH		;default file control block.
TBUFF	EQU	80H		;i/o buffer and command line storage.
TBASE	EQU	100H		;transiant program storage area.
;
;   Set control character equates.
;
CNTRLC	EQU	3		;control-c
CNTRLE	EQU	05H		;control-e
BS	EQU	08H		;backspace
TAB	EQU	09H		;tab
LF	EQU	0AH		;line feed
FF	EQU	0CH		;form feed
CR	EQU	0DH		;carriage return
CNTRLP	EQU	10H		;control-p
CNTRLR	EQU	12H		;control-r
CNTRLS	EQU	13H		;control-s
CNTRLU	EQU	15H		;control-u
CNTRLX	EQU	18H		;control-x
CNTRLZ	EQU	1AH		;control-z (end-of-file mark)
DEL	EQU	7FH		;rubout
;
;   Set origin for CP/M
;
	ORG	(MEM-7)*1024
;
CBASE:	JP	COMMAND		;execute command processor (ccp).
	JP	CLEARBUF	;entry to empty input buffer before starting ccp.

;
;   Standard cp/m ccp input buffer. Format is (max length),
; (actual length), (char #1), (char #2), (char #3), etc.
;
INBUFF:	db	127		;length of input buffer.
	db	0		;current length of contents.
	db	"Copyright"
	db	" 1979 (c) by Digital Research      "
	db	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	db	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	db	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	db	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
INPOINT:	dw	INBUFF+2	;input line pointer
NAMEPNT:	dw	0		;input line pointer used for error message. Points to
;			;start of name in error.
;
;   Routine to print (A) on the console. All registers used.
;
PRINT:	LD	E,A		;setup bdos call.
	LD	C,2
	JP	ENTRY
;
;   Routine to print (A) on the console and to save (BC).
;
PRINTB:	PUSH	BC
	CALL	PRINT
	POP	BC
	RET	
;
;   Routine to send a carriage return, line feed combination
; to the console.
;
CRLF:	LD	A,CR
	CALL	PRINTB
	LD	A,LF
	JP	PRINTB
;
;   Routine to send one space to the console and save (BC).
;
SPACE:	LD	A,' '
	JP	PRINTB
;
;   Routine to print character string pointed to be (BC) on the
; console. It must terminate with a null byte.
;
PLINE:	PUSH	BC
	CALL	CRLF
	POP	HL
PLINE2:	LD	A,(HL)
	OR	A
	RET	Z
	INC	HL
	PUSH	HL
	CALL	PRINT
	POP	HL
	JP	PLINE2
;
;   Routine to reset the disk system.
;
RESDSK:	LD	C,13
	JP	ENTRY
;
;   Routine to select disk (A).
;
DSKSEL:	LD	E,A
	LD	C,14
	JP	ENTRY
;
;   Routine to call bdos and save the return code. The zero
; flag is set on a return of 0ffh.
;
ENTRY1:	CALL	ENTRY
	LD	(RTNCODE),A	;save return code.
	INC	A		;set zero if 0ffh returned.
	RET	
;
;   Routine to open a file. (DE) must point to the FCB.
;
OPEN:	LD	C,15
	JP	ENTRY1
;
;   Routine to open file at (FCB).
;
OPENFCB:XOR	A		;clear the record number byte at fcb+32
	LD	(FCB+32),A
	LD	DE,FCB
	JP	OPEN
;
;   Routine to close a file. (DE) points to FCB.
;
CLOSE:	LD	C,16
	JP	ENTRY1
;
;   Routine to search for the first file with ambigueous name
; (DE).
;
SRCHFST:LD	C,17
	JP	ENTRY1
;
;   Search for the next ambigeous file name.
;
SRCHNXT:LD	C,18
	JP	ENTRY1
;
;   Search for file at (FCB).
;
SRCHFCB:LD	DE,FCB
	JP	SRCHFST
;
;   Routine to delete a file pointed to by (DE).
;
DELETE:	LD	C,19
	JP	ENTRY
;
;   Routine to call the bdos and set the zero flag if a zero
; status is returned.
;
ENTRY2:	CALL	ENTRY
	OR	A		;set zero flag if appropriate.
	RET	
;
;   Routine to read the next record from a sequential file.
; (DE) points to the FCB.
;
RDREC:	LD	C,20
	JP	ENTRY2
;
;   Routine to read file at (FCB).
;
READFCB:LD	DE,FCB
	JP	RDREC
;
;   Routine to write the next record of a sequential file.
; (DE) points to the FCB.
;
WRTREC:	LD	C,21
	JP	ENTRY2
;
;   Routine to create the file pointed to by (DE).
;
CREATE:	LD	C,22
	JP	ENTRY1
;
;   Routine to rename the file pointed to by (DE). Note that
; the new name starts at (DE+16).
;
RENAM:	LD	C,23
	JP	ENTRY
;
;   Get the current user code.
;
GETUSR:	LD	E,0FFH
;
;   Routne to get or set the current user code.
; If (E) is FF then this is a GET, else it is a SET.
;
GETSETUC: LD	C,32
	JP	ENTRY
;
;   Routine to set the current drive byte at (TDRIVE).
;
SETCDRV:CALL	GETUSR		;get user number
	ADD	A,A		;and shift into the upper 4 bits.
	ADD	A,A
	ADD	A,A
	ADD	A,A
	LD	HL,CDRIVE	;now add in the current drive number.
	OR	(HL)
	LD	(TDRIVE),A	;and save.
	RET	
;
;   Move currently active drive down to (TDRIVE).
;
MOVECD:	LD	A,(CDRIVE)
	LD	(TDRIVE),A
	RET	
;
;   Routine to convert (A) into upper case ascii. Only letters
; are affected.
;
UPPER:	CP	'a'		;check for letters in the range of 'a' to 'z'.
	RET	C
	CP	'{'
	RET	NC
	AND	5FH		;convert it if found.
	RET	
;
;   Routine to get a line of input. We must check to see if the
; user is in (BATCH) mode. If so, then read the input from file
; ($$$.SUB). At the end, reset to console input.
;
GETINP:	LD	A,(BATCH)	;if =0, then use console input.
	OR	A
	JP	Z,GETINP1
;
;   Use the submit file ($$$.sub) which is prepared by a
; SUBMIT run. It must be on drive (A) and it will be deleted
; if and error occures (like eof).
;
	LD	A,(CDRIVE)	;select drive 0 if need be.
	OR	A
	LD	A,0		;always use drive A for submit.
	CALL	NZ,DSKSEL	;select it if required.
	LD	DE,BATCHFCB
	CALL	OPEN		;look for it.
	JP	Z,GETINP1	;if not there, use normal input.
	LD	A,(BATCHFCB+15)	;get last record number+1.
	DEC	A
	LD	(BATCHFCB+32),A
	LD	DE,BATCHFCB
	CALL	RDREC		;read last record.
	JP	NZ,GETINP1	;quit on end of file.
;
;   Move this record into input buffer.
;
	LD	DE,INBUFF+1
	LD	HL,TBUFF	;data was read into buffer here.
	LD	B,128		;all 128 characters may be used.
	CALL	HL2DE		;(HL) to (DE), (B) bytes.
	LD	HL,BATCHFCB+14
	LD	(HL),0		;zero out the 's2' byte.
	INC	HL		;and decrement the record count.
	DEC	(HL)
	LD	DE,BATCHFCB	;close the batch file now.
	CALL	CLOSE
	JP	Z,GETINP1	;quit on an error.
	LD	A,(CDRIVE)	;re-select previous drive if need be.
	OR	A
	CALL	NZ,DSKSEL	;don't do needless selects.
;
;   Print line just read on console.
;
	LD	HL,INBUFF+2
	CALL	PLINE2
	CALL	CHKCON		;check console, quit on a key.
	JP	Z,GETINP2	;jump if no key is pressed.
;
;   Terminate the submit job on any keyboard input. Delete this
; file such that it is not re-started and jump to normal keyboard
; input section.
;
	CALL	DELBATCH	;delete the batch file.
	JP	CMMND1		;and restart command input.
;
;   Get here for normal keyboard input. Delete the submit file
; incase there was one.
;
GETINP1:CALL	DELBATCH	;delete file ($$$.sub).
	CALL	SETCDRV		;reset active disk.
	LD	C,10		;get line from console device.
	LD	DE,INBUFF
	CALL	ENTRY
	CALL	MOVECD		;reset current drive (again).
;
;   Convert input line to upper case.
;
GETINP2:LD	HL,INBUFF+1
	LD	B,(HL)		;(B)=character counter.
GETINP3:INC	HL
	LD	A,B		;end of the line?
	OR	A
	JP	Z,GETINP4
	LD	A,(HL)		;convert to upper case.
	CALL	UPPER
	LD	(HL),A
	DEC	B		;adjust character count.
	JP	GETINP3
GETINP4:LD	(HL),A		;add trailing null.
	LD	HL,INBUFF+2
	LD	(INPOINT),HL	;reset input line pointer.
	RET	
;
;   Routine to check the console for a key pressed. The zero
; flag is set is none, else the character is returned in (A).
;
CHKCON:	LD	C,11		;check console.
	CALL	ENTRY
	OR	A
	RET	Z		;return if nothing.
	LD	C,1		;else get character.
	CALL	ENTRY
	OR	A		;clear zero flag and return.
	RET	
;
;   Routine to get the currently active drive number.
;
GETDSK:	LD	C,25
	JP	ENTRY
;
;   Set the stabdard dma address.
;
STDDMA:	LD	DE,TBUFF
;
;   Routine to set the dma address to (DE).
;
DMASET:	LD	C,26
	JP	ENTRY
;
;  Delete the batch file created by SUBMIT.
;
DELBATCH: LD	HL,BATCH	;is batch active?
	LD	A,(HL)
	OR	A
	RET	Z
	LD	(HL),0		;yes, de-activate it.
	XOR	A
	CALL	DSKSEL		;select drive 0 for sure.
	LD	DE,BATCHFCB	;and delete this file.
	CALL	DELETE
	LD	A,(CDRIVE)	;reset current drive.
	JP	DSKSEL
;
;   Check to two strings at (PATTRN1) and (PATTRN2). They must be
; the same or we halt....
;
VERIFY:	LD	DE,PATTRN1	;these are the serial number bytes.
	LD	HL,PATTRN2	;ditto, but how could they be different?
	LD	B,6		;6 bytes each.
VERIFY1:LD	A,(DE)
	CP	(HL)
	JP	NZ,HALT		;jump to halt routine.
	INC	DE
	INC	HL
	DEC	B
	JP	NZ,VERIFY1
	RET	
;
;   Print back file name with a '?' to indicate a syntax error.
;
SYNERR:	CALL	CRLF		;end current line.
	LD	HL,(NAMEPNT)	;this points to name in error.
SYNERR1:LD	A,(HL)		;print it until a space or null is found.
	CP	' '
	JP	Z,SYNERR2
	OR	A
	JP	Z,SYNERR2
	PUSH	HL
	CALL	PRINT
	POP	HL
	INC	HL
	JP	SYNERR1
SYNERR2:LD	A,'?'		;add trailing '?'.
	CALL	PRINT
	CALL	CRLF
	CALL	DELBATCH	;delete any batch file.
	JP	CMMND1		;and restart from console input.
;
;   Check character at (DE) for legal command input. Note that the
; zero flag is set if the character is a delimiter.
;
CHECK:	LD	A,(DE)
	OR	A
	RET	Z
	CP	' '		;control characters are not legal here.
	JP	C,SYNERR
	RET	Z		;check for valid delimiter.
	CP	'='
	RET	Z
	CP	'_'
	RET	Z
	CP	'.'
	RET	Z
	CP	':'
	RET	Z
	CP	';'
	RET	Z
	CP	'<'
	RET	Z
	CP	'>'
	RET	Z
	RET	
;
;   Get the next non-blank character from (DE).
;
NONBLANK: LD	A,(DE)
	OR	A		;string ends with a null.
	RET	Z
	CP	' '
	RET	NZ
	INC	DE
	JP	NONBLANK
;
;   Add (HL)=(HL)+(A)
;
ADDHL:	ADD	A,L
	LD	L,A
	RET	NC		;take care of any carry.
	INC	H
	RET	
;
;   Convert the first name in (FCB).
;
CONVFST:LD	A,0
;
;   Format a file name (convert * to '?', etc.). On return,
; (A)=0 is an unambigeous name was specified. Enter with (A) equal to
; the position within the fcb for the name (either 0 or 16).
;
CONVERT:LD	HL,FCB
	CALL	ADDHL
	PUSH	HL
	PUSH	HL
	XOR	A
	LD	(CHGDRV),A	;initialize drive change flag.
	LD	HL,(INPOINT)	;set (HL) as pointer into input line.
	EX	DE,HL
	CALL	NONBLANK	;get next non-blank character.
	EX	DE,HL
	LD	(NAMEPNT),HL	;save pointer here for any error message.
	EX	DE,HL
	POP	HL
	LD	A,(DE)		;get first character.
	OR	A
	JP	Z,CONVRT1
	SBC	A,'A'-1		;might be a drive name, convert to binary.
	LD	B,A		;and save.
	INC	DE		;check next character for a ':'.
	LD	A,(DE)
	CP	':'
	JP	Z,CONVRT2
	DEC	DE		;nope, move pointer back to the start of the line.
CONVRT1:LD	A,(CDRIVE)
	LD	(HL),A
	JP	CONVRT3
CONVRT2:LD	A,B
	LD	(CHGDRV),A	;set change in drives flag.
	LD	(HL),B
	INC	DE
;
;   Convert the basic file name.
;
CONVRT3:LD	B,08H
CONVRT4:CALL	CHECK
	JP	Z,CONVRT8
	INC	HL
	CP	'*'		;note that an '*' will fill the remaining
	JP	NZ,CONVRT5	;field with '?'.
	LD	(HL),'?'
	JP	CONVRT6
CONVRT5:LD	(HL),A
	INC	DE
CONVRT6:DEC	B
	JP	NZ,CONVRT4
CONVRT7:CALL	CHECK		;get next delimiter.
	JP	Z,GETEXT
	INC	DE
	JP	CONVRT7
CONVRT8:INC	HL		;blank fill the file name.
	LD	(HL),' '
	DEC	B
	JP	NZ,CONVRT8
;
;   Get the extension and convert it.
;
GETEXT:	LD	B,03H
	CP	'.'
	JP	NZ,GETEXT5
	INC	DE
GETEXT1:CALL	CHECK
	JP	Z,GETEXT5
	INC	HL
	CP	'*'
	JP	NZ,GETEXT2
	LD	(HL),'?'
	JP	GETEXT3
GETEXT2:LD	(HL),A
	INC	DE
GETEXT3:DEC	B
	JP	NZ,GETEXT1
GETEXT4:CALL	CHECK
	JP	Z,GETEXT6
	INC	DE
	JP	GETEXT4
GETEXT5:INC	HL
	LD	(HL),' '
	DEC	B
	JP	NZ,GETEXT5
GETEXT6:LD	B,3
GETEXT7:INC	HL
	LD	(HL),0
	DEC	B
	JP	NZ,GETEXT7
	EX	DE,HL
	LD	(INPOINT),HL	;save input line pointer.
	POP	HL
;
;   Check to see if this is an ambigeous file name specification.
; Set the (A) register to non zero if it is.
;
	LD	BC,11		;set name length.
GETEXT8:INC	HL
	LD	A,(HL)
	CP	'?'		;any question marks?
	JP	NZ,GETEXT9
	INC	B		;count them.
GETEXT9:DEC	C
	JP	NZ,GETEXT8
	LD	A,B
	OR	A
	RET	
;
;   CP/M command table. Note commands can be either 3 or 4 characters long.
;
NUMCMDS EQU	6		;number of commands
CMDTBL:	db	"DIR "
	db	"ERA "
	db	"TYPE"
	db	"SAVE"
	db	"REN "
	db	"USER"
;
;   The following six bytes must agree with those at (PATTRN2)
; or cp/m will HALT. Why?
;
PATTRN1:db	0,22,0,0,0,0	;(* serial number bytes *).
;
;   Search the command table for a match with what has just
; been entered. If a match is found, then we jump to the
; proper section. Else jump to (UNKNOWN).
; On return, the (C) register is set to the command number
; that matched (or NUMCMDS+1 if no match).
;
SEARCH:	LD	HL,CMDTBL
	LD	C,0
SEARCH1:LD	A,C
	CP	NUMCMDS		;this commands exists.
	RET	NC
	LD	DE,FCB+1	;check this one.
	LD	B,4		;max command length.
SEARCH2:LD	A,(DE)
	CP	(HL)
	JP	NZ,SEARCH3	;not a match.
	INC	DE
	INC	HL
	DEC	B
	JP	NZ,SEARCH2
	LD	A,(DE)		;allow a 3 character command to match.
	CP	' '
	JP	NZ,SEARCH4
	LD	A,C		;set return register for this command.
	RET	
SEARCH3:INC	HL
	DEC	B
	JP	NZ,SEARCH3
SEARCH4:INC	C
	JP	SEARCH1
;
;   Set the input buffer to empty and then start the command
; processor (ccp).
;
CLEARBUF: XOR	A
	LD	(INBUFF+1),A	;second byte is actual length.
;
;**************************************************************
;*
;*
;* C C P  -   C o n s o l e   C o m m a n d   P r o c e s s o r
;*
;**************************************************************
;*
COMMAND:	LD	SP,CCPSTACK	;setup stack area.
	PUSH	BC		;note that (C) should be equal to:
	LD	A,C		;(uuuudddd) where 'uuuu' is the user number
	RRA			;and 'dddd' is the drive number.
	RRA	
	RRA	
	RRA	
	AND	0FH		;isolate the user number.
	LD	E,A
	CALL	GETSETUC	;and set it.
	CALL	RESDSK		;reset the disk system.
	LD	(BATCH),A	;clear batch mode flag.
	POP	BC
	LD	A,C
	AND	0FH		;isolate the drive number.
	LD	(CDRIVE),A	;and save.
	CALL	DSKSEL		;...and select.
	LD	A,(INBUFF+1)
	OR	A		;anything in input buffer already?
	JP	NZ,CMMND2	;yes, we just process it.
;
;   Entry point to get a command line from the console.
;
CMMND1:	LD	SP,CCPSTACK	;set stack straight.
	CALL	CRLF		;start a new line on the screen.
	CALL	GETDSK		;get current drive.
	ADD	A,'a'
	CALL	PRINT		;print current drive.
	LD	A,'>'
	CALL	PRINT		;and add prompt.
	CALL	GETINP		;get line from user.
;
;   Process command line here.
;
CMMND2:	LD	DE,TBUFF
	CALL	DMASET		;set standard dma address.
	CALL	GETDSK
	LD	(CDRIVE),A	;set current drive.
	CALL	CONVFST		;convert name typed in.
	CALL	NZ,SYNERR	;wild cards are not allowed.
	LD	A,(CHGDRV)	;if a change in drives was indicated,
	OR	A		;then treat this as an unknown command
	JP	NZ,UNKNOWN	;which gets executed.
	CALL	SEARCH		;else search command table for a match.
;
;   Note that an unknown command returns
; with (A) pointing to the last address
; in our table which is (UNKNOWN).
;
	LD	HL,CMDADR	;now, look thru our address table for command (A).
	LD	E,A		;set (DE) to command number.
	LD	D,0
	ADD	HL,DE
	ADD	HL,DE		;(HL)=(CMDADR)+2*(command number).
	LD	A,(HL)		;now pick out this address.
	INC	HL
	LD	H,(HL)
	LD	L,A
	JP	(HL)		;now execute it.
;
;   CP/M command address table.
;
CMDADR:	dw	DIRECT,ERASE,TYPE,SAVE
	dw	RENAME,USER,UNKNOWN
;
;   Halt the system. Reason for this is unknown at present.
;
HALT:	LD	HL,76F3H	;'DI HLT' instructions.
	LD	(CBASE),HL
	LD	HL,CBASE
	JP	(HL)
;
;   Read error while TYPEing a file.
;
RDERROR:LD	BC,RDERR
	JP	PLINE
RDERR:	db	"Read error",0
;
;   Required file was not located.
;
NONE:	LD	BC,NOFILE
	JP	PLINE
NOFILE:	db	"No file",0
;
;   Decode a command of the form 'A>filename number{ filename}.
; Note that a drive specifier is not allowed on the first file
; name. On return, the number is in register (A). Any error
; causes 'filename?' to be printed and the command is aborted.
;
DECODE:	CALL	CONVFST		;convert filename.
	LD	A,(CHGDRV)	;do not allow a drive to be specified.
	OR	A
	JP	NZ,SYNERR
	LD	HL,FCB+1	;convert number now.
	LD	BC,11		;(B)=sum register, (C)=max digit count.
DECODE1:LD	A,(HL)
	CP	' '		;a space terminates the numeral.
	JP	Z,DECODE3
	INC	HL
	SUB	'0'		;make binary from ascii.
	CP	10		;legal digit?
	JP	NC,SYNERR
	LD	D,A		;yes, save it in (D).
	LD	A,B		;compute (B)=(B)*10 and check for overflow.
	AND	0E0H
	JP	NZ,SYNERR
	LD	A,B
	RLCA	
	RLCA	
	RLCA			;(A)=(B)*8
	ADD	A,B		;.......*9
	JP	C,SYNERR
	ADD	A,B		;.......*10
	JP	C,SYNERR
	ADD	A,D		;add in new digit now.
DECODE2:JP	C,SYNERR
	LD	B,A		;and save result.
	DEC	C		;only look at 11 digits.
	JP	NZ,DECODE1
	RET	
DECODE3:LD	A,(HL)		;spaces must follow (why?).
	CP	' '
	JP	NZ,SYNERR
	INC	HL
DECODE4:DEC	C
	JP	NZ,DECODE3
	LD	A,B		;set (A)=the numeric value entered.
	RET	
;
;   Move 3 bytes from (HL) to (DE). Note that there is only
; one reference to this at (A2D5h).
;
MOVE3:	LD	B,3
;
;   Move (B) bytes from (HL) to (DE).
;
HL2DE:	LD	A,(HL)
	LD	(DE),A
	INC	HL
	INC	DE
	DEC	B
	JP	NZ,HL2DE
	RET	
;
;   Compute (HL)=(TBUFF)+(A)+(C) and get the byte that's here.
;
EXTRACT:LD	HL,TBUFF
	ADD	A,C
	CALL	ADDHL
	LD	A,(HL)
	RET	
;
;  Check drive specified. If it means a change, then the new
; drive will be selected. In any case, the drive byte of the
; fcb will be set to null (means use current drive).
;
DSELECT:XOR	A		;null out first byte of fcb.
	LD	(FCB),A
	LD	A,(CHGDRV)	;a drive change indicated?
	OR	A
	RET	Z
	DEC	A		;yes, is it the same as the current drive?
	LD	HL,CDRIVE
	CP	(HL)
	RET	Z
	JP	DSKSEL		;no. Select it then.
;
;   Check the drive selection and reset it to the previous
; drive if it was changed for the preceeding command.
;
RESETDR:LD	A,(CHGDRV)	;drive change indicated?
	OR	A
	RET	Z
	DEC	A		;yes, was it a different drive?
	LD	HL,CDRIVE
	CP	(HL)
	RET	Z
	LD	A,(CDRIVE)	;yes, re-select our old drive.
	JP	DSKSEL
;
;**************************************************************
;*
;*           D I R E C T O R Y   C O M M A N D
;*
;**************************************************************
;
DIRECT:	CALL	CONVFST		;convert file name.
	CALL	DSELECT		;select indicated drive.
	LD	HL,FCB+1	;was any file indicated?
	LD	A,(HL)
	CP	' '
	JP	NZ,DIRECT2
	LD	B,11		;no. Fill field with '?' - same as *.*.
DIRECT1:LD	(HL),'?'
	INC	HL
	DEC	B
	JP	NZ,DIRECT1
DIRECT2:LD	E,0		;set initial cursor position.
	PUSH	DE
	CALL	SRCHFCB		;get first file name.
	CALL	Z,NONE		;none found at all?
DIRECT3:JP	Z,DIRECT9	;terminate if no more names.
	LD	A,(RTNCODE)	;get file's position in segment (0-3).
	RRCA	
	RRCA	
	RRCA	
	AND	60H		;(A)=position*32
	LD	C,A
	LD	A,10
	CALL	EXTRACT		;extract the tenth entry in fcb.
	RLA			;check system file status bit.
	JP	C,DIRECT8	;we don't list them.
	POP	DE
	LD	A,E		;bump name count.
	INC	E
	PUSH	DE
	AND	03H		;at end of line?
	PUSH	AF
	JP	NZ,DIRECT4
	CALL	CRLF		;yes, end this line and start another.
	PUSH	BC
	CALL	GETDSK		;start line with ('A:').
	POP	BC
	ADD	A,'A'
	CALL	PRINTB
	LD	A,':'
	CALL	PRINTB
	JP	DIRECT5
DIRECT4:CALL	SPACE		;add seperator between file names.
	LD	A,':'
	CALL	PRINTB
DIRECT5:CALL	SPACE
	LD	B,1		;'extract' each file name character at a time.
DIRECT6:LD	A,B
	CALL	EXTRACT
	AND	7FH		;strip bit 7 (status bit).
	CP	' '		;are we at the end of the name?
	JP	NZ,DRECT65
	POP	AF		;yes, don't print spaces at the end of a line.
	PUSH	AF
	CP	3
	JP	NZ,DRECT63
	LD	A,9		;first check for no extension.
	CALL	EXTRACT
	AND	7FH
	CP	' '
	JP	Z,DIRECT7	;don't print spaces.
DRECT63:LD	A,' '		;else print them.
DRECT65:CALL	PRINTB
	INC	B		;bump to next character psoition.
	LD	A,B
	CP	12		;end of the name?
	JP	NC,DIRECT7
	CP	9		;nope, starting extension?
	JP	NZ,DIRECT6
	CALL	SPACE		;yes, add seperating space.
	JP	DIRECT6
DIRECT7:POP	AF		;get the next file name.
DIRECT8:CALL	CHKCON		;first check console, quit on anything.
	JP	NZ,DIRECT9
	CALL	SRCHNXT		;get next name.
	JP	DIRECT3		;and continue with our list.
DIRECT9:POP	DE		;restore the stack and return to command level.
	JP	GETBACK
;
;**************************************************************
;*
;*                E R A S E   C O M M A N D
;*
;**************************************************************
;
ERASE:	CALL	CONVFST		;convert file name.
	CP	11		;was '*.*' entered?
	JP	NZ,ERASE1
	LD	BC,YESNO	;yes, ask for confirmation.
	CALL	PLINE
	CALL	GETINP
	LD	HL,INBUFF+1
	DEC	(HL)		;must be exactly 'y'.
	JP	NZ,CMMND1
	INC	HL
	LD	A,(HL)
	CP	'Y'
	JP	NZ,CMMND1
	INC	HL
	LD	(INPOINT),HL	;save input line pointer.
ERASE1:	CALL	DSELECT		;select desired disk.
	LD	DE,FCB
	CALL	DELETE		;delete the file.
	INC	A
	CALL	Z,NONE		;not there?
	JP	GETBACK		;return to command level now.
YESNO:	db	"All (y/n)?",0
;
;**************************************************************
;*
;*            T Y P E   C O M M A N D
;*
;**************************************************************
;
TYPE:	CALL	CONVFST		;convert file name.
	JP	NZ,SYNERR	;wild cards not allowed.
	CALL	DSELECT		;select indicated drive.
	CALL	OPENFCB		;open the file.
	JP	Z,TYPE5		;not there?
	CALL	CRLF		;ok, start a new line on the screen.
	LD	HL,NBYTES	;initialize byte counter.
	LD	(HL),0FFH	;set to read first sector.
TYPE1:	LD	HL,NBYTES
TYPE2:	LD	A,(HL)		;have we written the entire sector?
	CP	128
	JP	C,TYPE3
	PUSH	HL		;yes, read in the next one.
	CALL	READFCB
	POP	HL
	JP	NZ,TYPE4	;end or error?
	XOR	A		;ok, clear byte counter.
	LD	(HL),A
TYPE3:	INC	(HL)		;count this byte.
	LD	HL,TBUFF	;and get the (A)th one from the buffer (TBUFF).
	CALL	ADDHL
	LD	A,(HL)
	CP	CNTRLZ		;end of file mark?
	JP	Z,GETBACK
	CALL	PRINT		;no, print it.
	CALL	CHKCON		;check console, quit if anything ready.
	JP	NZ,GETBACK
	JP	TYPE1
;
;   Get here on an end of file or read error.
;
TYPE4:	DEC	A		;read error?
	JP	Z,GETBACK
	CALL	RDERROR		;yes, print message.
TYPE5:	CALL	RESETDR		;and reset proper drive
	JP	SYNERR		;now print file name with problem.
;
;**************************************************************
;*
;*            S A V E   C O M M A N D
;*
;**************************************************************
;
SAVE:	CALL	DECODE		;get numeric number that follows SAVE.
	PUSH	AF		;save number of pages to write.
	CALL	CONVFST		;convert file name.
	JP	NZ,SYNERR	;wild cards not allowed.
	CALL	DSELECT		;select specified drive.
	LD	DE,FCB		;now delete this file.
	PUSH	DE
	CALL	DELETE
	POP	DE
	CALL	CREATE		;and create it again.
	JP	Z,SAVE3		;can't create?
	XOR	A		;clear record number byte.
	LD	(FCB+32),A
	POP	AF		;convert pages to sectors.
	LD	L,A
	LD	H,0
	ADD	HL,HL		;(HL)=number of sectors to write.
	LD	DE,TBASE	;and we start from here.
SAVE1:	LD	A,H		;done yet?
	OR	L
	JP	Z,SAVE2
	DEC	HL		;nope, count this and compute the start
	PUSH	HL		;of the next 128 byte sector.
	LD	HL,128
	ADD	HL,DE
	PUSH	HL		;save it and set the transfer address.
	CALL	DMASET
	LD	DE,FCB		;write out this sector now.
	CALL	WRTREC
	POP	DE		;reset (DE) to the start of the last sector.
	POP	HL		;restore sector count.
	JP	NZ,SAVE3	;write error?
	JP	SAVE1
;
;   Get here after writing all of the file.
;
SAVE2:	LD	DE,FCB		;now close the file.
	CALL	CLOSE
	INC	A		;did it close ok?
	JP	NZ,SAVE4
;
;   Print out error message (no space).
;
SAVE3:	LD	BC,NOSPACE
	CALL	PLINE
SAVE4:	CALL	STDDMA		;reset the standard dma address.
	JP	GETBACK
NOSPACE:db	"No space",0
;
;**************************************************************
;*
;*           R E N A M E   C O M M A N D
;*
;**************************************************************
;
RENAME:	CALL	CONVFST		;convert first file name.
	JP	NZ,SYNERR	;wild cards not allowed.
	LD	A,(CHGDRV)	;remember any change in drives specified.
	PUSH	AF
	CALL	DSELECT		;and select this drive.
	CALL	SRCHFCB		;is this file present?
	JP	NZ,RENAME6	;yes, print error message.
	LD	HL,FCB		;yes, move this name into second slot.
	LD	DE,FCB+16
	LD	B,16
	CALL	HL2DE
	LD	HL,(INPOINT)	;get input pointer.
	EX	DE,HL
	CALL	NONBLANK	;get next non blank character.
	CP	'='		;only allow an '=' or '_' seperator.
	JP	Z,RENAME1
	CP	'_'
	JP	NZ,RENAME5
RENAME1:EX	DE,HL
	INC	HL		;ok, skip seperator.
	LD	(INPOINT),HL	;save input line pointer.
	CALL	CONVFST		;convert this second file name now.
	JP	NZ,RENAME5	;again, no wild cards.
	POP	AF		;if a drive was specified, then it
	LD	B,A		;must be the same as before.
	LD	HL,CHGDRV
	LD	A,(HL)
	OR	A
	JP	Z,RENAME2
	CP	B
	LD	(HL),B
	JP	NZ,RENAME5	;they were different, error.
RENAME2:LD	(HL),B		;	reset as per the first file specification.
	XOR	A
	LD	(FCB),A		;clear the drive byte of the fcb.
RENAME3:CALL	SRCHFCB		;and go look for second file.
	JP	Z,RENAME4	;doesn't exist?
	LD	DE,FCB
	CALL	RENAM		;ok, rename the file.
	JP	GETBACK
;
;   Process rename errors here.
;
RENAME4:CALL	NONE		;file not there.
	JP	GETBACK
RENAME5:CALL	RESETDR		;bad command format.
	JP	SYNERR
RENAME6:LD	BC,EXISTS	;destination file already exists.
	CALL	PLINE
	JP	GETBACK
EXISTS:	db	"File exists",0
;
;**************************************************************
;*
;*             U S E R   C O M M A N D
;*
;**************************************************************
;
USER:	CALL	DECODE		;get numeric value following command.
	CP	16		;legal user number?
	JP	NC,SYNERR
	LD	E,A		;yes but is there anything else?
	LD	A,(FCB+1)
	CP	' '
	JP	Z,SYNERR	;yes, that is not allowed.
	CALL	GETSETUC	;ok, set user code.
	JP	GETBACK1
;
;**************************************************************
;*
;*        T R A N S I A N T   P R O G R A M   C O M M A N D
;*
;**************************************************************
;
UNKNOWN:CALL	VERIFY		;check for valid system (why?).
	LD	A,(FCB+1)	;anything to execute?
	CP	' '
	JP	NZ,UNKWN1
	LD	A,(CHGDRV)	;nope, only a drive change?
	OR	A
	JP	Z,GETBACK1	;neither???
	DEC	A
	LD	(CDRIVE),A	;ok, store new drive.
	CALL	MOVECD		;set (TDRIVE) also.
	CALL	DSKSEL		;and select this drive.
	JP	GETBACK1	;then return.
;
;   Here a file name was typed. Prepare to execute it.
;
UNKWN1:	LD	DE,FCB+9	;an extension specified?
	LD	A,(DE)
	CP	' '
	JP	NZ,SYNERR	;yes, not allowed.
UNKWN2:	PUSH	DE
	CALL	DSELECT		;select specified drive.
	POP	DE
	LD	HL,COMFILE	;set the extension to 'COM'.
	CALL	MOVE3
	CALL	OPENFCB		;and open this file.
	JP	Z,UNKWN9	;not present?
;
;   Load in the program.
;
	LD	HL,TBASE	;store the program starting here.
UNKWN3:	PUSH	HL
	EX	DE,HL
	CALL	DMASET		;set transfer address.
	LD	DE,FCB		;and read the next record.
	CALL	RDREC
	JP	NZ,UNKWN4	;end of file or read error?
	POP	HL		;nope, bump pointer for next sector.
	LD	DE,128
	ADD	HL,DE
	LD	DE,CBASE	;enough room for the whole file?
	LD	A,L
	SUB	E
	LD	A,H
	SBC	A,D
	JP	NC,UNKWN0	;no, it can't fit.
	JP	UNKWN3
;
;   Get here after finished reading.
;
UNKWN4:	POP	HL
	DEC	A		;normal end of file?
	JP	NZ,UNKWN0
	CALL	RESETDR		;yes, reset previous drive.
	CALL	CONVFST		;convert the first file name that follows
	LD	HL,CHGDRV	;command name.
	PUSH	HL
	LD	A,(HL)		;set drive code in default fcb.
	LD	(FCB),A
	LD	A,16		;put second name 16 bytes later.
	CALL	CONVERT		;convert second file name.
	POP	HL
	LD	A,(HL)		;and set the drive for this second file.
	LD	(FCB+16),A
	XOR	A		;clear record byte in fcb.
	LD	(FCB+32),A
	LD	DE,TFCB		;move it into place at(005Ch).
	LD	HL,FCB
	LD	B,33
	CALL	HL2DE
	LD	HL,INBUFF+2	;now move the remainder of the input
UNKWN5:	LD	A,(HL)		;line down to (0080h). Look for a non blank.
	OR	A		;or a null.
	JP	Z,UNKWN6
	CP	' '
	JP	Z,UNKWN6
	INC	HL
	JP	UNKWN5
;
;   Do the line move now. It ends in a null byte.
;
UNKWN6:	LD	B,0		;keep a character count.
	LD	DE,TBUFF+1	;data gets put here.
UNKWN7:	LD	A,(HL)		;move it now.
	LD	(DE),A
	OR	A
	JP	Z,UNKWN8
	INC	B
	INC	HL
	INC	DE
	JP	UNKWN7
UNKWN8:	LD	A,B		;now store the character count.
	LD	(TBUFF),A
	CALL	CRLF		;clean up the screen.
	CALL	STDDMA		;set standard transfer address.
	CALL	SETCDRV		;reset current drive.
	CALL	TBASE		;and execute the program.
;
;   Transiant programs return here (or reboot).
;
	LD	SP,BATCH	;set stack first off.
	CALL	MOVECD		;move current drive into place (TDRIVE).
	CALL	DSKSEL		;and reselect it.
	JP	CMMND1		;back to comand mode.
;
;   Get here if some error occured.
;
UNKWN9:	CALL	RESETDR		;inproper format.
	JP	SYNERR
UNKWN0:	LD	BC,BADLOAD	;read error or won't fit.
	CALL	PLINE
	JP	GETBACK
BADLOAD:db	"Bad load",0
COMFILE:db	"COM"		;command file extension.
;
;   Get here to return to command level. We will reset the
; previous active drive and then either return to command
; level directly or print error message and then return.
;
GETBACK:CALL	RESETDR		;reset previous drive.
GETBACK1: CALL	CONVFST		;convert first name in (FCB).
	LD	A,(FCB+1)	;if this was just a drive change request,
	SUB	' '		;make sure it was valid.
	LD	HL,CHGDRV
	OR	(HL)
	JP	NZ,SYNERR
	JP	CMMND1		;ok, return to command level.
;
;   ccp stack area.
;
	db	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
CCPSTACK EQU	$	;end of ccp stack area.
;
;   Batch (or SUBMIT) processing information storage.
;
BATCH:	db	0		;batch mode flag (0=not active).
BATCHFCB: db	0,"$$$     SUB",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;
;   File control block setup by the CCP.
;
FCB:	db	0,"           ",0,0,0,0,0,"           ",0,0,0,0,0
RTNCODE:db	0		;status returned from bdos call.
CDRIVE:	db	0		;currently active drive.
CHGDRV:	db	0		;change in drives flag (0=no change).
NBYTES:	dw	0		;byte counter used by TYPE.
;
;   Room for expansion?
;
	db	0,0,0,0,0,0,0,0,0,0,0,0,0
;
;   Note that the following six bytes must match those at
; (PATTRN1) or cp/m will HALT. Why?
;
PATTRN2:db	0,22,0,0,0,0	;(* serial number bytes *).
;
;**************************************************************
;*
;*                    B D O S   E N T R Y
;*
;**************************************************************
;
FBASE:	JP	FBASE1
;
;   Bdos error table.
;
BADSCTR:dw	ERROR1		;bad sector on read or write.
BADSLCT:dw	ERROR2		;bad disk select.
RODISK:	dw	ERROR3		;disk is read only.
ROFILE:	dw	ERROR4		;file is read only.
;
;   Entry into bdos. (DE) or (E) are the parameters passed. The
; function number desired is in register (C).
;
FBASE1:	EX	DE,HL		;save the (DE) parameters.
	LD	(PARAMS),HL
	EX	DE,HL
	LD	A,E		;and save register (E) in particular.
	LD	(EPARAM),A
	LD	HL,0
	LD	(STATUS),HL	;clear return status.
	ADD	HL,SP
	LD	(USRSTACK),HL	;save users stack pointer.
	LD	SP,STKAREA	;and set our own.
	XOR	A		;clear auto select storage space.
	LD	(AUTOFLAG),A
	LD	(AUTO),A
	LD	HL,GOBACK	;set return address.
	PUSH	HL
	LD	A,C		;get function number.
	CP	NFUNCTS		;valid function number?
	RET	NC
	LD	C,E		;keep single register function here.
	LD	HL,FUNCTNS	;now look thru the function table.
	LD	E,A
	LD	D,0		;(DE)=function number.
	ADD	HL,DE
	ADD	HL,DE		;(HL)=(start of table)+2*(function number).
	LD	E,(HL)
	INC	HL
	LD	D,(HL)		;now (DE)=address for this function.
	LD	HL,(PARAMS)	;retrieve parameters.
	EX	DE,HL		;now (DE) has the original parameters.
	JP	(HL)		;execute desired function.
;
;   BDOS function jump table.
;
NFUNCTS EQU	41		;number of functions in followin table.
;
FUNCTNS:dw	WBOOT,GETCON,OUTCON,GETRDR,PUNCH,LIST,DIRCIO,GETIOB
	dw	SETIOB,PRTSTR,RDBUFF,GETCSTS,GETVER,RSTDSK,SETDSK,OPENFIL
	dw	CLOSEFIL,GETFST,GETNXT,DELFILE,READSEQ,WRTSEQ,FCREATE
	dw	RENFILE,GETLOG,GETCRNT,PUTDMA,GETALOC,WRTPRTD,GETROV,SETATTR
	dw	GETPARM,GETUSER,RDRANDOM,WTRANDOM,FILESIZE,SETRAN,LOGOFF,RTN
	dw	RTN,WTSPECL
;
;   Bdos error message section.
;
ERROR1:	LD	HL,BADSEC	;bad sector message.
	CALL	PRTERR		;print it and get a 1 char responce.
	CP	CNTRLC		;re-boot request (control-c)?
	JP	Z,0		;yes.
	RET			;no, return to retry i/o function.
;
ERROR2:	LD	HL,BADSEL	;bad drive selected.
	JP	ERROR5
;
ERROR3:	LD	HL,DISKRO	;disk is read only.
	JP	ERROR5
;
ERROR4:	LD	HL,FILERO	;file is read only.
;
ERROR5:	CALL	PRTERR
	JP	0		;always reboot on these errors.
;
BDOSERR:db	"Bdos Err On "
BDOSDRV:db	" : $"
BADSEC:	db	"Bad Sector$"
BADSEL:	db	"Select$"
FILERO:	db	"File "
DISKRO:	db	"R/O$"
;
;   Print bdos error message.
;
PRTERR:	PUSH	HL		;save second message pointer.
	CALL	OUTCRLF		;send (cr)(lf).
	LD	A,(ACTIVE)	;get active drive.
	ADD	A,'A'		;make ascii.
	LD	(BDOSDRV),A	;and put in message.
	LD	BC,BDOSERR	;and print it.
	CALL	PRTMESG
	POP	BC		;print second message line now.
	CALL	PRTMESG
;
;   Get an input character. We will check our 1 character
; buffer first. This may be set by the console status routine.
;
GETCHAR:LD	HL,CHARBUF	;check character buffer.
	LD	A,(HL)		;anything present already?
	LD	(HL),0		;...either case clear it.
	OR	A
	RET	NZ		;yes, use it.
	JP	CONIN		;nope, go get a character responce.
;
;   Input and echo a character.
;
GETECHO:CALL	GETCHAR		;input a character.
	CALL	CHKCHAR		;carriage control?
	RET	C		;no, a regular control char so don't echo.
	PUSH	AF		;ok, save character now.
	LD	C,A
	CALL	OUTCON		;and echo it.
	POP	AF		;get character and return.
	RET	
;
;   Check character in (A). Set the zero flag on a carriage
; control character and the carry flag on any other control
; character.
;
CHKCHAR:CP	CR		;check for carriage return, line feed, backspace,
	RET	Z		;or a tab.
	CP	LF
	RET	Z
	CP	TAB
	RET	Z
	CP	BS
	RET	Z
	CP	' '		;other control char? Set carry flag.
	RET	
;
;   Check the console during output. Halt on a control-s, then
; reboot on a control-c. If anything else is ready, clear the
; zero flag and return (the calling routine may want to do
; something).
;
CKCONSOL: LD	A,(CHARBUF)	;check buffer.
	OR	A		;if anything, just return without checking.
	JP	NZ,CKCON2
	CALL	CONST		;nothing in buffer. Check console.
	AND	01H		;look at bit 0.
	RET	Z		;return if nothing.
	CALL	CONIN		;ok, get it.
	CP	CNTRLS		;if not control-s, return with zero cleared.
	JP	NZ,CKCON1
	CALL	CONIN		;halt processing until another char
	CP	CNTRLC		;is typed. Control-c?
	JP	Z,0		;yes, reboot now.
	XOR	A		;no, just pretend nothing was ever ready.
	RET	
CKCON1:	LD	(CHARBUF),A	;save character in buffer for later processing.
CKCON2:	LD	A,1		;set (A) to non zero to mean something is ready.
	RET	
;
;   Output (C) to the screen. If the printer flip-flop flag
; is set, we will send character to printer also. The console
; will be checked in the process.
;
OUTCHAR:LD	A,(OUTFLAG)	;check output flag.
	OR	A		;anything and we won't generate output.
	JP	NZ,OUTCHR1
	PUSH	BC
	CALL	CKCONSOL	;check console (we don't care whats there).
	POP	BC
	PUSH	BC
	CALL	CONOUT		;output (C) to the screen.
	POP	BC
	PUSH	BC
	LD	A,(PRTFLAG)	;check printer flip-flop flag.
	OR	A
	CALL	NZ,LIST		;print it also if non-zero.
	POP	BC
OUTCHR1:LD	A,C		;update cursors position.
	LD	HL,CURPOS
	CP	DEL		;rubouts don't do anything here.
	RET	Z
	INC	(HL)		;bump line pointer.
	CP	' '		;and return if a normal character.
	RET	NC
	DEC	(HL)		;restore and check for the start of the line.
	LD	A,(HL)
	OR	A
	RET	Z		;ingnore control characters at the start of the line.
	LD	A,C
	CP	BS		;is it a backspace?
	JP	NZ,OUTCHR2
	DEC	(HL)		;yes, backup pointer.
	RET	
OUTCHR2:CP	LF		;is it a line feed?
	RET	NZ		;ignore anything else.
	LD	(HL),0		;reset pointer to start of line.
	RET	
;
;   Output (A) to the screen. If it is a control character
; (other than carriage control), use ^x format.
;
SHOWIT:	LD	A,C
	CALL	CHKCHAR		;check character.
	JP	NC,OUTCON	;not a control, use normal output.
	PUSH	AF
	LD	C,'^'		;for a control character, preceed it with '^'.
	CALL	OUTCHAR
	POP	AF
	OR	'@'		;and then use the letter equivelant.
	LD	C,A
;
;   Function to output (C) to the console device and expand tabs
; if necessary.
;
OUTCON:	LD	A,C
	CP	TAB		;is it a tab?
	JP	NZ,OUTCHAR	;use regular output.
OUTCON1:LD	C,' '		;yes it is, use spaces instead.
	CALL	OUTCHAR
	LD	A,(CURPOS)	;go until the cursor is at a multiple of 8

	AND	07H		;position.
	JP	NZ,OUTCON1
	RET	
;
;   Echo a backspace character. Erase the prevoius character
; on the screen.
;
BACKUP:	CALL	BACKUP1		;backup the screen 1 place.
	LD	C,' '		;then blank that character.
	CALL	CONOUT
BACKUP1:LD	C,BS		;then back space once more.
	JP	CONOUT
;
;   Signal a deleted line. Print a '#' at the end and start
; over.
;
NEWLINE:LD	C,'#'
	CALL	OUTCHAR		;print this.
	CALL	OUTCRLF		;start new line.
NEWLN1:	LD	A,(CURPOS)	;move the cursor to the starting position.
	LD	HL,STARTING
	CP	(HL)
	RET	NC		;there yet?
	LD	C,' '
	CALL	OUTCHAR		;nope, keep going.
	JP	NEWLN1
;
;   Output a (cr) (lf) to the console device (screen).
;
OUTCRLF:LD	C,CR
	CALL	OUTCHAR
	LD	C,LF
	JP	OUTCHAR
;
;   Print message pointed to by (BC). It will end with a '$'.
;
PRTMESG:LD	A,(BC)		;check for terminating character.
	CP	'$'
	RET	Z
	INC	BC
	PUSH	BC		;otherwise, bump pointer and print it.
	LD	C,A
	CALL	OUTCON
	POP	BC
	JP	PRTMESG
;
;   Function to execute a buffered read.
;
RDBUFF:	LD	A,(CURPOS)	;use present location as starting one.
	LD	(STARTING),A
	LD	HL,(PARAMS)	;get the maximum buffer space.
	LD	C,(HL)
	INC	HL		;point to first available space.
	PUSH	HL		;and save.
	LD	B,0		;keep a character count.
RDBUF1:	PUSH	BC
	PUSH	HL
RDBUF2:	CALL	GETCHAR		;get the next input character.
	AND	7FH		;strip bit 7.
	POP	HL		;reset registers.
	POP	BC
	CP	CR		;en of the line?
	JP	Z,RDBUF17
	CP	LF
	JP	Z,RDBUF17
	CP	BS		;how about a backspace?
	JP	NZ,RDBUF3
	LD	A,B		;yes, but ignore at the beginning of the line.
	OR	A
	JP	Z,RDBUF1
	DEC	B		;ok, update counter.
	LD	A,(CURPOS)	;if we backspace to the start of the line,
	LD	(OUTFLAG),A	;treat as a cancel (control-x).
	JP	RDBUF10
RDBUF3:	CP	DEL		;user typed a rubout?
	JP	NZ,RDBUF4
	LD	A,B		;ignore at the start of the line.
	OR	A
	JP	Z,RDBUF1
	LD	A,(HL)		;ok, echo the prevoius character.
	DEC	B		;and reset pointers (counters).
	DEC	HL
	JP	RDBUF15
RDBUF4:	CP	CNTRLE		;physical end of line?
	JP	NZ,RDBUF5
	PUSH	BC		;yes, do it.
	PUSH	HL
	CALL	OUTCRLF
	XOR	A		;and update starting position.
	LD	(STARTING),A
	JP	RDBUF2
RDBUF5:	CP	CNTRLP		;control-p?
	JP	NZ,RDBUF6
	PUSH	HL		;yes, flip the print flag filp-flop byte.
	LD	HL,PRTFLAG
	LD	A,1		;PRTFLAG=1-PRTFLAG
	SUB	(HL)
	LD	(HL),A
	POP	HL
	JP	RDBUF1
RDBUF6:	CP	CNTRLX		;control-x (cancel)?
	JP	NZ,RDBUF8
	POP	HL
RDBUF7:	LD	A,(STARTING)	;yes, backup the cursor to here.
	LD	HL,CURPOS
	CP	(HL)
	JP	NC,RDBUFF	;done yet?
	DEC	(HL)		;no, decrement pointer and output back up one space.
	CALL	BACKUP
	JP	RDBUF7
RDBUF8:	CP	CNTRLU		;cntrol-u (cancel line)?
	JP	NZ,RDBUF9
	CALL	NEWLINE		;start a new line.
	POP	HL
	JP	RDBUFF
RDBUF9:	CP	CNTRLR		;control-r?
	JP	NZ,RDBUF14
RDBUF10:PUSH	BC		;yes, start a new line and retype the old one.
	CALL	NEWLINE
	POP	BC
	POP	HL
	PUSH	HL
	PUSH	BC
RDBUF11:LD	A,B		;done whole line yet?
	OR	A
	JP	Z,RDBUF12
	INC	HL		;nope, get next character.
	LD	C,(HL)
	DEC	B		;count it.
	PUSH	BC
	PUSH	HL
	CALL	SHOWIT		;and display it.
	POP	HL
	POP	BC
	JP	RDBUF11
RDBUF12:PUSH	HL		;done with line. If we were displaying
	LD	A,(OUTFLAG)	;then update cursor position.
	OR	A
	JP	Z,RDBUF2
	LD	HL,CURPOS	;because this line is shorter, we must
	SUB	(HL)		;back up the cursor (not the screen however)
	LD	(OUTFLAG),A	;some number of positions.
RDBUF13:CALL	BACKUP		;note that as long as (OUTFLAG) is non
	LD	HL,OUTFLAG	;zero, the screen will not be changed.
	DEC	(HL)
	JP	NZ,RDBUF13
	JP	RDBUF2		;now just get the next character.
;
;   Just a normal character, put this in our buffer and echo.
;
RDBUF14:INC	HL
	LD	(HL),A		;store character.
	INC	B		;and count it.
RDBUF15:PUSH	BC
	PUSH	HL
	LD	C,A		;echo it now.
	CALL	SHOWIT
	POP	HL
	POP	BC
	LD	A,(HL)		;was it an abort request?
	CP	CNTRLC		;control-c abort?
	LD	A,B
	JP	NZ,RDBUF16
	CP	1		;only if at start of line.
	JP	Z,0
RDBUF16:CP	C		;nope, have we filled the buffer?
	JP	C,RDBUF1
RDBUF17:POP	HL		;yes end the line and return.
	LD	(HL),B
	LD	C,CR
	JP	OUTCHAR		;output (cr) and return.
;
;   Function to get a character from the console device.
;
GETCON:	CALL	GETECHO		;get and echo.
	JP	SETSTAT		;save status and return.
;
;   Function to get a character from the tape reader device.
;
GETRDR:	CALL	READER		;get a character from reader, set status and return.
	JP	SETSTAT
;
;  Function to perform direct console i/o. If (C) contains (FF)
; then this is an input request. If (C) contains (FE) then
; this is a status request. Otherwise we are to output (C).
;
DIRCIO:	LD	A,C		;test for (FF).
	INC	A
	JP	Z,DIRC1
	INC	A		;test for (FE).
	JP	Z,CONST
	JP	CONOUT		;just output (C).
DIRC1:	CALL	CONST		;this is an input request.
	OR	A
	JP	Z,GOBACK1	;not ready? Just return (directly).
	CALL	CONIN		;yes, get character.
	JP	SETSTAT		;set status and return.
;
;   Function to return the i/o byte.
;
GETIOB:	LD	A,(IOBYTE)
	JP	SETSTAT
;
;   Function to set the i/o byte.
;
SETIOB:	LD	HL,IOBYTE
	LD	(HL),C
	RET	
;
;   Function to print the character string pointed to by (DE)
; on the console device. The string ends with a '$'.
;
PRTSTR:	EX	DE,HL
	LD	C,L
	LD	B,H		;now (BC) points to it.
	JP	PRTMESG
;
;   Function to interigate the console device.
;
GETCSTS:CALL	CKCONSOL
;
;   Get here to set the status and return to the cleanup
; section. Then back to the user.
;
SETSTAT:LD	(STATUS),A
RTN:	RET	
;
;   Set the status to 1 (read or write error code).
;
IOERR1:	LD	A,1
	JP	SETSTAT
;
OUTFLAG:db	0		;output flag (non zero means no output).
STARTING: db	2		;starting position for cursor.
CURPOS:	db	0		;cursor position (0=start of line).
PRTFLAG:db	0		;printer flag (control-p toggle). List if non zero.
CHARBUF:db	0		;single input character buffer.
;
;   Stack area for BDOS calls.
;
USRSTACK: dw	0		;save users stack pointer here.
;
	db	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
	db	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
STKAREA EQU	$		;end of stack area.
;
USERNO:	db	0		;current user number.
ACTIVE:	db	0		;currently active drive.
PARAMS:	dw	0		;save (DE) parameters here on entry.
STATUS:	dw	0		;status returned from bdos function.
;
;   Select error occured, jump to error routine.
;
SLCTERR:LD	HL,BADSLCT
;
;   Jump to (HL) indirectly.
;
JUMPHL:	LD	E,(HL)
	INC	HL
	LD	D,(HL)		;now (DE) contain the desired address.
	EX	DE,HL
	JP	(HL)
;
;   Block move. (DE) to (HL), (C) bytes total.
;
DE2HL:	INC	C		;is count down to zero?
DE2HL1:	DEC	C
	RET	Z		;yes, we are done.
	LD	A,(DE)		;no, move one more byte.
	LD	(HL),A
	INC	DE
	INC	HL
	JP	DE2HL1		;and repeat.
;
;   Select the desired drive.
;
SELECT:	LD	A,(ACTIVE)	;get active disk.
	LD	C,A
	CALL	SELDSK		;select it.
	LD	A,H		;valid drive?
	OR	L		;valid drive?
	RET	Z		;return if not.
;
;   Here, the BIOS returned the address of the parameter block
; in (HL). We will extract the necessary pointers and save them.
;
	LD	E,(HL)		;yes, get address of translation table into (DE).
	INC	HL
	LD	D,(HL)
	INC	HL
	LD	(SCRATCH1),HL	;save pointers to scratch areas.
	INC	HL
	INC	HL
	LD	(SCRATCH2),HL	;ditto.
	INC	HL
	INC	HL
	LD	(SCRATCH3),HL	;ditto.
	INC	HL
	INC	HL
	EX	DE,HL		;now save the translation table address.
	LD	(XLATE),HL
	LD	HL,DIRBUF	;put the next 8 bytes here.
	LD	C,8		;they consist of the directory buffer
	CALL	DE2HL		;pointer, parameter block pointer,
	LD	HL,(DISKPB)	;check and allocation vectors.
	EX	DE,HL
	LD	HL,SECTORS	;move parameter block into our ram.
	LD	C,15		;it is 15 bytes long.
	CALL	DE2HL
	LD	HL,(DSKSIZE)	;check disk size.
	LD	A,H		;more than 256 blocks on this?
	LD	HL,BIGDISK
	LD	(HL),0FFH	;set to samll.
	OR	A
	JP	Z,SELECT1
	LD	(HL),0		;wrong, set to large.
SELECT1:LD	A,0FFH		;clear the zero flag.
	OR	A
	RET	
;
;   Routine to home the disk track head and clear pointers.
;
HOMEDRV:CALL	HOME		;home the head.
	XOR	A
	LD	HL,(SCRATCH2)	;set our track pointer also.
	LD	(HL),A
	INC	HL
	LD	(HL),A
	LD	HL,(SCRATCH3)	;and our sector pointer.
	LD	(HL),A
	INC	HL
	LD	(HL),A
	RET	
;
;   Do the actual disk read and check the error return status.
;
DOREAD:	CALL	READ
	JP	IORET
;
;   Do the actual disk write and handle any bios error.
;
DOWRITE:CALL	WRITE
IORET:	OR	A
	RET	Z		;return unless an error occured.
	LD	HL,BADSCTR	;bad read/write on this sector.
	JP	JUMPHL
;
;   Routine to select the track and sector that the desired
; block number falls in.
;
TRKSEC:	LD	HL,(FILEPOS)	;get position of last accessed file
	LD	C,2		;in directory and compute sector #.
	CALL	SHIFTR		;sector #=file-position/4.
	LD	(BLKNMBR),HL	;save this as the block number of interest.
	LD	(CKSUMTBL),HL	;what's it doing here too?
;
;   if the sector number has already been set (BLKNMBR), enter
; at this point.
;
TRKSEC1:LD	HL,BLKNMBR
	LD	C,(HL)		;move sector number into (BC).
	INC	HL
	LD	B,(HL)
	LD	HL,(SCRATCH3)	;get current sector number and
	LD	E,(HL)		;move this into (DE).
	INC	HL
	LD	D,(HL)
	LD	HL,(SCRATCH2)	;get current track number.
	LD	A,(HL)		;and this into (HL).
	INC	HL
	LD	H,(HL)
	LD	L,A
TRKSEC2:LD	A,C		;is desired sector before current one?
	SUB	E
	LD	A,B
	SBC	A,D
	JP	NC,TRKSEC3
	PUSH	HL		;yes, decrement sectors by one track.
	LD	HL,(SECTORS)	;get sectors per track.
	LD	A,E
	SUB	L
	LD	E,A
	LD	A,D
	SBC	A,H
	LD	D,A		;now we have backed up one full track.
	POP	HL
	DEC	HL		;adjust track counter.
	JP	TRKSEC2
TRKSEC3:PUSH	HL		;desired sector is after current one.
	LD	HL,(SECTORS)	;get sectors per track.
	ADD	HL,DE		;bump sector pointer to next track.
	JP	C,TRKSEC4
	LD	A,C		;is desired sector now before current one?
	SUB	L
	LD	A,B
	SBC	A,H
	JP	C,TRKSEC4
	EX	DE,HL		;not yes, increment track counter
	POP	HL		;and continue until it is.
	INC	HL
	JP	TRKSEC3
;
;   here we have determined the track number that contains the
; desired sector.
;
TRKSEC4:POP	HL		;get track number (HL).
	PUSH	BC
	PUSH	DE
	PUSH	HL
	EX	DE,HL
	LD	HL,(OFFSET)	;adjust for first track offset.
	ADD	HL,DE
	LD	B,H
	LD	C,L
	CALL	SETTRK		;select this track.
	POP	DE		;reset current track pointer.
	LD	HL,(SCRATCH2)
	LD	(HL),E
	INC	HL
	LD	(HL),D
	POP	DE
	LD	HL,(SCRATCH3)	;reset the first sector on this track.
	LD	(HL),E
	INC	HL
	LD	(HL),D
	POP	BC
	LD	A,C		;now subtract the desired one.
	SUB	E		;to make it relative (1-# sectors/track).
	LD	C,A
	LD	A,B
	SBC	A,D
	LD	B,A
	LD	HL,(XLATE)	;translate this sector according to this table.
	EX	DE,HL
	CALL	SECTRAN		;let the bios translate it.
	LD	C,L
	LD	B,H
	JP	SETSEC		;and select it.
;
;   Compute block number from record number (SAVNREC) and
; extent number (SAVEXT).
;
GETBLOCK: LD	HL,BLKSHFT	;get logical to physical conversion.
	LD	C,(HL)		;note that this is base 2 log of ratio.
	LD	A,(SAVNREC)	;get record number.
GETBLK1:OR	A		;compute (A)=(A)/2^BLKSHFT.
	RRA	
	DEC	C
	JP	NZ,GETBLK1
	LD	B,A		;save result in (B).
	LD	A,8
	SUB	(HL)
	LD	C,A		;compute (C)=8-BLKSHFT.
	LD	A,(SAVEXT)
GETBLK2:DEC	C		;compute (A)=SAVEXT*2^(8-BLKSHFT).
	JP	Z,GETBLK3
	OR	A
	RLA	
	JP	GETBLK2
GETBLK3:ADD	A,B
	RET	
;
;   Routine to extract the (BC) block byte from the fcb pointed
; to by (PARAMS). If this is a big-disk, then these are 16 bit
; block numbers, else they are 8 bit numbers.
; Number is returned in (HL).
;
EXTBLK:	LD	HL,(PARAMS)	;get fcb address.
	LD	DE,16		;block numbers start 16 bytes into fcb.
	ADD	HL,DE
	ADD	HL,BC
	LD	A,(BIGDISK)	;are we using a big-disk?
	OR	A
	JP	Z,EXTBLK1
	LD	L,(HL)		;no, extract an 8 bit number from the fcb.
	LD	H,0
	RET	
EXTBLK1:ADD	HL,BC		;yes, extract a 16 bit number.
	LD	E,(HL)
	INC	HL
	LD	D,(HL)
	EX	DE,HL		;return in (HL).
	RET	
;
;   Compute block number.
;
COMBLK:	CALL	GETBLOCK
	LD	C,A
	LD	B,0
	CALL	EXTBLK
	LD	(BLKNMBR),HL
	RET	
;
;   Check for a zero block number (unused).
;
CHKBLK:	LD	HL,(BLKNMBR)
	LD	A,L		;is it zero?
	OR	H
	RET	
;
;   Adjust physical block (BLKNMBR) and convert to logical
; sector (LOGSECT). This is the starting sector of this block.
; The actual sector of interest is then added to this and the
; resulting sector number is stored back in (BLKNMBR). This
; will still have to be adjusted for the track number.
;
LOGICAL:LD	A,(BLKSHFT)	;get log2(physical/logical sectors).
	LD	HL,(BLKNMBR)	;get physical sector desired.
LOGICL1:ADD	HL,HL		;compute logical sector number.
	DEC	A		;note logical sectors are 128 bytes long.
	JP	NZ,LOGICL1
	LD	(LOGSECT),HL	;save logical sector.
	LD	A,(BLKMASK)	;get block mask.
	LD	C,A
	LD	A,(SAVNREC)	;get next sector to access.
	AND	C		;extract the relative position within physical block.
	OR	L		;and add it too logical sector.
	LD	L,A
	LD	(BLKNMBR),HL	;and store.
	RET	
;
;   Set (HL) to point to extent byte in fcb.
;
SETEXT:	LD	HL,(PARAMS)
	LD	DE,12		;it is the twelth byte.
	ADD	HL,DE
	RET	
;
;   Set (HL) to point to record count byte in fcb and (DE) to
; next record number byte.
;
SETHLDE:LD	HL,(PARAMS)
	LD	DE,15		;record count byte (#15).
	ADD	HL,DE
	EX	DE,HL
	LD	HL,17		;next record number (#32).
	ADD	HL,DE
	RET	
;
;   Save current file data from fcb.
;
STRDATA:CALL	SETHLDE
	LD	A,(HL)		;get and store record count byte.
	LD	(SAVNREC),A
	EX	DE,HL
	LD	A,(HL)		;get and store next record number byte.
	LD	(SAVNXT),A
	CALL	SETEXT		;point to extent byte.
	LD	A,(EXTMASK)	;get extent mask.
	AND	(HL)
	LD	(SAVEXT),A	;and save extent here.
	RET	
;
;   Set the next record to access. If (MODE) is set to 2, then
; the last record byte (SAVNREC) has the correct number to access.
; For sequential access, (MODE) will be equal to 1.
;
SETNREC:CALL	SETHLDE
	LD	A,(MODE)	;get sequential flag (=1).
	CP	2		;a 2 indicates that no adder is needed.
	JP	NZ,STNREC1
	XOR	A		;clear adder (random access?).
STNREC1:LD	C,A
	LD	A,(SAVNREC)	;get last record number.
	ADD	A,C		;increment record count.
	LD	(HL),A		;and set fcb's next record byte.
	EX	DE,HL
	LD	A,(SAVNXT)	;get next record byte from storage.
	LD	(HL),A		;and put this into fcb as number of records used.
	RET	
;
;   Shift (HL) right (C) bits.
;
SHIFTR:	INC	C
SHIFTR1:DEC	C
	RET	Z
	LD	A,H
	OR	A
	RRA	
	LD	H,A
	LD	A,L
	RRA	
	LD	L,A
	JP	SHIFTR1
;
;   Compute the check-sum for the directory buffer. Return
; integer sum in (A).
;
CHECKSUM: LD	C,128		;length of buffer.
	LD	HL,(DIRBUF)	;get its location.
	XOR	A		;clear summation byte.
CHKSUM1:	ADD	A,(HL)		;and compute sum ignoring carries.
	INC	HL
	DEC	C
	JP	NZ,CHKSUM1
	RET	
;
;   Shift (HL) left (C) bits.
;
SHIFTL:	INC	C
SHIFTL1:DEC	C
	RET	Z
	ADD	HL,HL		;shift left 1 bit.
	JP	SHIFTL1
;
;   Routine to set a bit in a 16 bit value contained in (BC).
; The bit set depends on the current drive selection.
;
SETBIT:	PUSH	BC		;save 16 bit word.
	LD	A,(ACTIVE)	;get active drive.
	LD	C,A
	LD	HL,1
	CALL	SHIFTL		;shift bit 0 into place.
	POP	BC		;now 'or' this with the original word.
	LD	A,C
	OR	L
	LD	L,A		;low byte done, do high byte.
	LD	A,B
	OR	H
	LD	H,A
	RET	
;
;   Extract the write protect status bit for the current drive.
; The result is returned in (A), bit 0.
;
GETWPRT:LD	HL,(WRTPRT)	;get status bytes.
	LD	A,(ACTIVE)	;which drive is current?
	LD	C,A
	CALL	SHIFTR		;shift status such that bit 0 is the
	LD	A,L		;one of interest for this drive.
	AND	01H		;and isolate it.
	RET	
;
;   Function to write protect the current disk.
;
WRTPRTD:LD	HL,WRTPRT	;point to status word.
	LD	C,(HL)		;set (BC) equal to the status.
	INC	HL
	LD	B,(HL)
	CALL	SETBIT		;and set this bit according to current drive.
	LD	(WRTPRT),HL	;then save.
	LD	HL,(DIRSIZE)	;now save directory size limit.
	INC	HL		;remember the last one.
	EX	DE,HL
	LD	HL,(SCRATCH1)	;and store it here.
	LD	(HL),E		;put low byte.
	INC	HL
	LD	(HL),D		;then high byte.
	RET	
;
;   Check for a read only file.
;
CHKROFL:CALL	FCB2HL		;set (HL) to file entry in directory buffer.
CKROF1:	LD	DE,9		;look at bit 7 of the ninth byte.
	ADD	HL,DE
	LD	A,(HL)
	RLA	
	RET	NC		;return if ok.
	LD	HL,ROFILE	;else, print error message and terminate.
	JP	JUMPHL
;
;   Check the write protect status of the active disk.
;
CHKWPRT:CALL	GETWPRT
	RET	Z		;return if ok.
	LD	HL,RODISK	;else print message and terminate.
	JP	JUMPHL
;
;   Routine to set (HL) pointing to the proper entry in the
; directory buffer.
;
FCB2HL:	LD	HL,(DIRBUF)	;get address of buffer.
	LD	A,(FCBPOS)	;relative position of file.
;
;   Routine to add (A) to (HL).
;
ADDA2HL:ADD	A,L
	LD	L,A
	RET	NC
	INC	H		;take care of any carry.
	RET	
;
;   Routine to get the 's2' byte from the fcb supplied in
; the initial parameter specification.
;
GETS2:	LD	HL,(PARAMS)	;get address of fcb.
	LD	DE,14		;relative position of 's2'.
	ADD	HL,DE
	LD	A,(HL)		;extract this byte.
	RET	
;
;   Clear the 's2' byte in the fcb.
;
CLEARS2:CALL	GETS2		;this sets (HL) pointing to it.
	LD	(HL),0		;now clear it.
	RET	
;
;   Set bit 7 in the 's2' byte of the fcb.
;
SETS2B7:CALL	GETS2		;get the byte.
	OR	80H		;and set bit 7.
	LD	(HL),A		;then store.
	RET	
;
;   Compare (FILEPOS) with (SCRATCH1) and set flags based on
; the difference. This checks to see if there are more file
; names in the directory. We are at (FILEPOS) and there are
; (SCRATCH1) of them to check.
;
MOREFLS:LD	HL,(FILEPOS)	;we are here.
	EX	DE,HL
	LD	HL,(SCRATCH1)	;and don't go past here.
	LD	A,E		;compute difference but don't keep.
	SUB	(HL)
	INC	HL
	LD	A,D
	SBC	A,(HL)		;set carry if no more names.
	RET	
;
;   Call this routine to prevent (SCRATCH1) from being greater
; than (FILEPOS).
;
CHKNMBR:CALL	MOREFLS		;SCRATCH1 too big?
	RET	C
	INC	DE		;yes, reset it to (FILEPOS).
	LD	(HL),D
	DEC	HL
	LD	(HL),E
	RET	
;
;   Compute (HL)=(DE)-(HL)
;
SUBHL:	LD	A,E		;compute difference.
	SUB	L
	LD	L,A		;store low byte.
	LD	A,D
	SBC	A,H
	LD	H,A		;and then high byte.
	RET	
;
;   Set the directory checksum byte.
;
SETDIR:	LD	C,0FFH
;
;   Routine to set or compare the directory checksum byte. If
; (C)=0ffh, then this will set the checksum byte. Else the byte
; will be checked. If the check fails (the disk has been changed),
; then this disk will be write protected.
;
CHECKDIR: LD	HL,(CKSUMTBL)
	EX	DE,HL
	LD	HL,(ALLOC1)
	CALL	SUBHL
	RET	NC		;ok if (CKSUMTBL) > (ALLOC1), so return.
	PUSH	BC
	CALL	CHECKSUM	;else compute checksum.
	LD	HL,(CHKVECT)	;get address of checksum table.
	EX	DE,HL
	LD	HL,(CKSUMTBL)
	ADD	HL,DE		;set (HL) to point to byte for this drive.
	POP	BC
	INC	C		;set or check ?
	JP	Z,CHKDIR1
	CP	(HL)		;check them.
	RET	Z		;return if they are the same.
	CALL	MOREFLS		;not the same, do we care?
	RET	NC
	CALL	WRTPRTD		;yes, mark this as write protected.
	RET	
CHKDIR1:LD	(HL),A		;just set the byte.
	RET	
;
;   Do a write to the directory of the current disk.
;
DIRWRITE: CALL	SETDIR		;set checksum byte.
	CALL	DIRDMA		;set directory dma address.
	LD	C,1		;tell the bios to actually write.
	CALL	DOWRITE		;then do the write.
	JP	DEFDMA
;
;   Read from the directory.
;
DIRREAD:CALL	DIRDMA		;set the directory dma address.
	CALL	DOREAD		;and read it.
;
;   Routine to set the dma address to the users choice.
;
DEFDMA:	LD	HL,USERDMA	;reset the default dma address and return.
	JP	DIRDMA1
;
;   Routine to set the dma address for directory work.
;
DIRDMA:	LD	HL,DIRBUF
;
;   Set the dma address. On entry, (HL) points to
; word containing the desired dma address.
;
DIRDMA1:LD	C,(HL)
	INC	HL
	LD	B,(HL)		;setup (BC) and go to the bios to set it.
	JP	SETDMA
;
;   Move the directory buffer into user's dma space.
;
MOVEDIR:LD	HL,(DIRBUF)	;buffer is located here, and
	EX	DE,HL
	LD	HL,(USERDMA)	; put it here.
	LD	C,128		;this is its length.
	JP	DE2HL		;move it now and return.
;
;   Check (FILEPOS) and set the zero flag if it equals 0ffffh.
;
CKFILPOS: LD	HL,FILEPOS
	LD	A,(HL)
	INC	HL
	CP	(HL)		;are both bytes the same?
	RET	NZ
	INC	A		;yes, but are they each 0ffh?
	RET	
;
;   Set location (FILEPOS) to 0ffffh.
;
STFILPOS: LD	HL,0FFFFH
	LD	(FILEPOS),HL
	RET	
;
;   Move on to the next file position within the current
; directory buffer. If no more exist, set pointer to 0ffffh
; and the calling routine will check for this. Enter with (C)
; equal to 0ffh to cause the checksum byte to be set, else we
; will check this disk and set write protect if checksums are
; not the same (applies only if another directory sector must
; be read).
;
NXENTRY:LD	HL,(DIRSIZE)	;get directory entry size limit.
	EX	DE,HL
	LD	HL,(FILEPOS)	;get current count.
	INC	HL		;go on to the next one.
	LD	(FILEPOS),HL
	CALL	SUBHL		;(HL)=(DIRSIZE)-(FILEPOS)
	JP	NC,NXENT1	;is there more room left?
	JP	STFILPOS	;no. Set this flag and return.
NXENT1:	LD	A,(FILEPOS)	;get file position within directory.
	AND	03H		;only look within this sector (only 4 entries fit).
	LD	B,5		;convert to relative position (32 bytes each).
NXENT2:	ADD	A,A		;note that this is not efficient code.
	DEC	B		;5 'ADD A's would be better.
	JP	NZ,NXENT2
	LD	(FCBPOS),A	;save it as position of fcb.
	OR	A
	RET	NZ		;return if we are within buffer.
	PUSH	BC
	CALL	TRKSEC		;we need the next directory sector.
	CALL	DIRREAD
	POP	BC
	JP	CHECKDIR
;
;   Routine to to get a bit from the disk space allocation
; map. It is returned in (A), bit position 0. On entry to here,
; set (BC) to the block number on the disk to check.
; On return, (D) will contain the original bit position for
; this block number and (HL) will point to the address for it.
;
CKBITMAP: LD	A,C		;determine bit number of interest.
	AND	07H		;compute (D)=(E)=(C and 7)+1.
	INC	A
	LD	E,A		;save particular bit number.
	LD	D,A
;
;   compute (BC)=(BC)/8.
;
	LD	A,C
	RRCA			;now shift right 3 bits.
	RRCA	
	RRCA	
	AND	1FH		;and clear bits 7,6,5.
	LD	C,A
	LD	A,B
	ADD	A,A		;now shift (B) into bits 7,6,5.
	ADD	A,A
	ADD	A,A
	ADD	A,A
	ADD	A,A
	OR	C		;and add in (C).
	LD	C,A		;ok, (C) ha been completed.
	LD	A,B		;is there a better way of doing this?
	RRCA	
	RRCA	
	RRCA	
	AND	1FH
	LD	B,A		;and now (B) is completed.
;
;   use this as an offset into the disk space allocation
; table.
;
	LD	HL,(ALOCVECT)
	ADD	HL,BC
	LD	A,(HL)		;now get correct byte.
CKBMAP1:RLCA			;get correct bit into position 0.
	DEC	E
	JP	NZ,CKBMAP1
	RET	
;
;   Set or clear the bit map such that block number (BC) will be marked
; as used. On entry, if (E)=0 then this bit will be cleared, if it equals
; 1 then it will be set (don't use anyother values).
;
STBITMAP: PUSH	DE
	CALL	CKBITMAP	;get the byte of interest.
	AND	0FEH		;clear the affected bit.
	POP	BC
	OR	C		;and now set it acording to (C).
;
;  entry to restore the original bit position and then store
; in table. (A) contains the value, (D) contains the bit
; position (1-8), and (HL) points to the address within the
; space allocation table for this byte.
;
STBMAP1:RRCA			;restore original bit position.
	DEC	D
	JP	NZ,STBMAP1
	LD	(HL),A		;and stor byte in table.
	RET	
;
;   Set/clear space used bits in allocation map for this file.
; On entry, (C)=1 to set the map and (C)=0 to clear it.
;
SETFILE:CALL	FCB2HL		;get address of fcb
	LD	DE,16
	ADD	HL,DE		;get to block number bytes.
	PUSH	BC
	LD	C,17		;check all 17 bytes (max) of table.
SETFL1:	POP	DE
	DEC	C		;done all bytes yet?
	RET	Z
	PUSH	DE
	LD	A,(BIGDISK)	;check disk size for 16 bit block numbers.
	OR	A
	JP	Z,SETFL2
	PUSH	BC		;only 8 bit numbers. set (BC) to this one.
	PUSH	HL
	LD	C,(HL)		;get low byte from table, always
	LD	B,0		;set high byte to zero.
	JP	SETFL3
SETFL2:	DEC	C		;for 16 bit block numbers, adjust counter.
	PUSH	BC
	LD	C,(HL)		;now get both the low and high bytes.
	INC	HL
	LD	B,(HL)
	PUSH	HL
SETFL3:	LD	A,C		;block used?
	OR	B
	JP	Z,SETFL4
	LD	HL,(DSKSIZE)	;is this block number within the
	LD	A,L		;space on the disk?
	SUB	C
	LD	A,H
	SBC	A,B
	CALL	NC,STBITMAP	;yes, set the proper bit.
SETFL4:	POP	HL		;point to next block number in fcb.
	INC	HL
	POP	BC
	JP	SETFL1
;
;   Construct the space used allocation bit map for the active
; drive. If a file name starts with '$' and it is under the
; current user number, then (STATUS) is set to minus 1. Otherwise
; it is not set at all.
;
BITMAP:	LD	HL,(DSKSIZE)	;compute size of allocation table.
	LD	C,3
	CALL	SHIFTR		;(HL)=(HL)/8.
	INC	HL		;at lease 1 byte.
	LD	B,H
	LD	C,L		;set (BC) to the allocation table length.
;
;   Initialize the bitmap for this drive. Right now, the first
; two bytes are specified by the disk parameter block. However
; a patch could be entered here if it were necessary to setup
; this table in a special mannor. For example, the bios could
; determine locations of 'bad blocks' and set them as already
; 'used' in the map.
;
	LD	HL,(ALOCVECT)	;now zero out the table now.
BITMAP1:LD	(HL),0
	INC	HL
	DEC	BC
	LD	A,B
	OR	C
	JP	NZ,BITMAP1
	LD	HL,(ALLOC0)	;get initial space used by directory.
	EX	DE,HL
	LD	HL,(ALOCVECT)	;and put this into map.
	LD	(HL),E
	INC	HL
	LD	(HL),D
;
;   End of initialization portion.
;
	CALL	HOMEDRV		;now home the drive.
	LD	HL,(SCRATCH1)
	LD	(HL),3		;force next directory request to read
	INC	HL		;in a sector.
	LD	(HL),0
	CALL	STFILPOS	;clear initial file position also.
BITMAP2:LD	C,0FFH		;read next file name in directory
	CALL	NXENTRY		;and set checksum byte.
	CALL	CKFILPOS	;is there another file?
	RET	Z
	CALL	FCB2HL		;yes, get its address.
	LD	A,0E5H
	CP	(HL)		;empty file entry?
	JP	Z,BITMAP2
	LD	A,(USERNO)	;no, correct user number?
	CP	(HL)
	JP	NZ,BITMAP3
	INC	HL
	LD	A,(HL)		;yes, does name start with a '$'?
	SUB	'$'
	JP	NZ,BITMAP3
	DEC	A		;yes, set atatus to minus one.
	LD	(STATUS),A
BITMAP3:LD	C,1		;now set this file's space as used in bit map.
	CALL	SETFILE
	CALL	CHKNMBR		;keep (SCRATCH1) in bounds.
	JP	BITMAP2
;
;   Set the status (STATUS) and return.
;
STSTATUS: LD	A,(FNDSTAT)
	JP	SETSTAT
;
;   Check extents in (A) and (C). Set the zero flag if they
; are the same. The number of 16k chunks of disk space that
; the directory extent covers is expressad is (EXTMASK+1).
; No registers are modified.
;
SAMEXT:	PUSH	BC
	PUSH	AF
	LD	A,(EXTMASK)	;get extent mask and use it to
	CPL			;to compare both extent numbers.
	LD	B,A		;save resulting mask here.
	LD	A,C		;mask first extent and save in (C).
	AND	B
	LD	C,A
	POP	AF		;now mask second extent and compare
	AND	B		;with the first one.
	SUB	C
	AND	1FH		;(* only check buts 0-4 *)
	POP	BC		;the zero flag is set if they are the same.
	RET			;restore (BC) and return.
;
;   Search for the first occurence of a file name. On entry,
; register (C) should contain the number of bytes of the fcb
; that must match.
;
FINDFST:LD	A,0FFH
	LD	(FNDSTAT),A
	LD	HL,COUNTER	;save character count.
	LD	(HL),C
	LD	HL,(PARAMS)	;get filename to match.
	LD	(SAVEFCB),HL	;and save.
	CALL	STFILPOS	;clear initial file position (set to 0ffffh).
	CALL	HOMEDRV		;home the drive.
;
;   Entry to locate the next occurence of a filename within the
; directory. The disk is not expected to have been changed. If
; it was, then it will be write protected.
;
FINDNXT:LD	C,0		;write protect the disk if changed.
	CALL	NXENTRY		;get next filename entry in directory.
	CALL	CKFILPOS	;is file position = 0ffffh?
	JP	Z,FNDNXT6	;yes, exit now then.
	LD	HL,(SAVEFCB)	;set (DE) pointing to filename to match.
	EX	DE,HL
	LD	A,(DE)
	CP	0E5H		;empty directory entry?
	JP	Z,FNDNXT1	;(* are we trying to reserect erased entries? *)
	PUSH	DE
	CALL	MOREFLS		;more files in directory?
	POP	DE
	JP	NC,FNDNXT6	;no more. Exit now.
FNDNXT1:CALL	FCB2HL		;get address of this fcb in directory.
	LD	A,(COUNTER)	;get number of bytes (characters) to check.
	LD	C,A
	LD	B,0		;initialize byte position counter.
FNDNXT2:LD	A,C		;are we done with the compare?
	OR	A
	JP	Z,FNDNXT5
	LD	A,(DE)		;no, check next byte.
	CP	'?'		;don't care about this character?
	JP	Z,FNDNXT4
	LD	A,B		;get bytes position in fcb.
	CP	13		;don't care about the thirteenth byte either.
	JP	Z,FNDNXT4
	CP	12		;extent byte?
	LD	A,(DE)
	JP	Z,FNDNXT3
	SUB	(HL)		;otherwise compare characters.
	AND	7FH
	JP	NZ,FINDNXT	;not the same, check next entry.
	JP	FNDNXT4		;so far so good, keep checking.
FNDNXT3:PUSH	BC		;check the extent byte here.
	LD	C,(HL)
	CALL	SAMEXT
	POP	BC
	JP	NZ,FINDNXT	;not the same, look some more.
;
;   So far the names compare. Bump pointers to the next byte
; and continue until all (C) characters have been checked.
;
FNDNXT4:INC	DE		;bump pointers.
	INC	HL
	INC	B
	DEC	C		;adjust character counter.
	JP	FNDNXT2
FNDNXT5:LD	A,(FILEPOS)	;return the position of this entry.
	AND	03H
	LD	(STATUS),A
	LD	HL,FNDSTAT
	LD	A,(HL)
	RLA	
	RET	NC
	XOR	A
	LD	(HL),A
	RET	
;
;   Filename was not found. Set appropriate status.
;
FNDNXT6:CALL	STFILPOS	;set (FILEPOS) to 0ffffh.
	LD	A,0FFH		;say not located.
	JP	SETSTAT
;
;   Erase files from the directory. Only the first byte of the
; fcb will be affected. It is set to (E5).
;
ERAFILE:CALL	CHKWPRT		;is disk write protected?
	LD	C,12		;only compare file names.
	CALL	FINDFST		;get first file name.
ERAFIL1:CALL	CKFILPOS	;any found?
	RET	Z		;nope, we must be done.
	CALL	CHKROFL		;is file read only?
	CALL	FCB2HL		;nope, get address of fcb and
	LD	(HL),0E5H	;set first byte to 'empty'.
	LD	C,0		;clear the space from the bit map.
	CALL	SETFILE
	CALL	DIRWRITE	;now write the directory sector back out.
	CALL	FINDNXT		;find the next file name.
	JP	ERAFIL1		;and repeat process.
;
;   Look through the space allocation map (bit map) for the
; next available block. Start searching at block number (BC-1).
; The search procedure is to look for an empty block that is
; before the starting block. If not empty, look at a later
; block number. In this way, we return the closest empty block
; on either side of the 'target' block number. This will speed
; access on random devices. For serial devices, this should be
; changed to look in the forward direction first and then start
; at the front and search some more.
;
;   On return, (DE)= block number that is empty and (HL) =0
; if no empry block was found.
;
FNDSPACE: LD	D,B		;set (DE) as the block that is checked.
	LD	E,C
;
;   Look before target block. Registers (BC) are used as the lower
; pointer and (DE) as the upper pointer.
;
FNDSPA1:LD	A,C		;is block 0 specified?
	OR	B
	JP	Z,FNDSPA2
	DEC	BC		;nope, check previous block.
	PUSH	DE
	PUSH	BC
	CALL	CKBITMAP
	RRA			;is this block empty?
	JP	NC,FNDSPA3	;yes. use this.
;
;   Note that the above logic gets the first block that it finds
; that is empty. Thus a file could be written 'backward' making
; it very slow to access. This could be changed to look for the
; first empty block and then continue until the start of this
; empty space is located and then used that starting block.
; This should help speed up access to some files especially on
; a well used disk with lots of fairly small 'holes'.
;
	POP	BC		;nope, check some more.
	POP	DE
;
;   Now look after target block.
;
FNDSPA2:LD	HL,(DSKSIZE)	;is block (DE) within disk limits?
	LD	A,E
	SUB	L
	LD	A,D
	SBC	A,H
	JP	NC,FNDSPA4
	INC	DE		;yes, move on to next one.
	PUSH	BC
	PUSH	DE
	LD	B,D
	LD	C,E
	CALL	CKBITMAP	;check it.
	RRA			;empty?
	JP	NC,FNDSPA3
	POP	DE		;nope, continue searching.
	POP	BC
	JP	FNDSPA1
;
;   Empty block found. Set it as used and return with (HL)
; pointing to it (true?).
;
FNDSPA3:RLA			;reset byte.
	INC	A		;and set bit 0.
	CALL	STBMAP1		;update bit map.
	POP	HL		;set return registers.
	POP	DE
	RET	
;
;   Free block was not found. If (BC) is not zero, then we have
; not checked all of the disk space.
;
FNDSPA4:LD	A,C
	OR	B
	JP	NZ,FNDSPA1
	LD	HL,0		;set 'not found' status.
	RET	
;
;   Move a complete fcb entry into the directory and write it.
;
FCBSET:	LD	C,0
	LD	E,32		;length of each entry.
;
;   Move (E) bytes from the fcb pointed to by (PARAMS) into
; fcb in directory starting at relative byte (C). This updated
; directory buffer is then written to the disk.
;
UPDATE:	PUSH	DE
	LD	B,0		;set (BC) to relative byte position.
	LD	HL,(PARAMS)	;get address of fcb.
	ADD	HL,BC		;compute starting byte.
	EX	DE,HL
	CALL	FCB2HL		;get address of fcb to update in directory.
	POP	BC		;set (C) to number of bytes to change.
	CALL	DE2HL
UPDATE1:CALL	TRKSEC		;determine the track and sector affected.
	JP	DIRWRITE	;then write this sector out.
;
;   Routine to change the name of all files on the disk with a
; specified name. The fcb contains the current name as the
; first 12 characters and the new name 16 bytes into the fcb.
;
CHGNAMES: CALL	CHKWPRT		;check for a write protected disk.
	LD	C,12		;match first 12 bytes of fcb only.
	CALL	FINDFST		;get first name.
	LD	HL,(PARAMS)	;get address of fcb.
	LD	A,(HL)		;get user number.
	LD	DE,16		;move over to desired name.
	ADD	HL,DE
	LD	(HL),A		;keep same user number.
CHGNAM1:CALL	CKFILPOS	;any matching file found?
	RET	Z		;no, we must be done.
	CALL	CHKROFL		;check for read only file.
	LD	C,16		;start 16 bytes into fcb.
	LD	E,12		;and update the first 12 bytes of directory.
	CALL	UPDATE
	CALL	FINDNXT		;get te next file name.
	JP	CHGNAM1		;and continue.
;
;   Update a files attributes. The procedure is to search for
; every file with the same name as shown in fcb (ignoring bit 7)
; and then to update it (which includes bit 7). No other changes
; are made.
;
SAVEATTR: LD	C,12		;match first 12 bytes.
	CALL	FINDFST		;look for first filename.
SAVATR1:CALL	CKFILPOS	;was one found?
	RET	Z		;nope, we must be done.
	LD	C,0		;yes, update the first 12 bytes now.
	LD	E,12
	CALL	UPDATE		;update filename and write directory.
	CALL	FINDNXT		;and get the next file.
	JP	SAVATR1		;then continue until done.
;
;  Open a file (name specified in fcb).
;
OPENIT:	LD	C,15		;compare the first 15 bytes.
	CALL	FINDFST		;get the first one in directory.
	CALL	CKFILPOS	;any at all?
	RET	Z
OPENIT1:CALL	SETEXT		;point to extent byte within users fcb.
	LD	A,(HL)		;and get it.
	PUSH	AF		;save it and address.
	PUSH	HL
	CALL	FCB2HL		;point to fcb in directory.
	EX	DE,HL
	LD	HL,(PARAMS)	;this is the users copy.
	LD	C,32		;move it into users space.
	PUSH	DE
	CALL	DE2HL
	CALL	SETS2B7		;set bit 7 in 's2' byte (unmodified).
	POP	DE		;now get the extent byte from this fcb.
	LD	HL,12
	ADD	HL,DE
	LD	C,(HL)		;into (C).
	LD	HL,15		;now get the record count byte into (B).
	ADD	HL,DE
	LD	B,(HL)
	POP	HL		;keep the same extent as the user had originally.
	POP	AF
	LD	(HL),A
	LD	A,C		;is it the same as in the directory fcb?
	CP	(HL)
	LD	A,B		;if yes, then use the same record count.
	JP	Z,OPENIT2
	LD	A,0		;if the user specified an extent greater than
	JP	C,OPENIT2	;the one in the directory, then set record count to 0.
	LD	A,128		;otherwise set to maximum.
OPENIT2:LD	HL,(PARAMS)	;set record count in users fcb to (A).
	LD	DE,15
	ADD	HL,DE		;compute relative position.
	LD	(HL),A		;and set the record count.
	RET	
;
;   Move two bytes from (DE) to (HL) if (and only if) (HL)
; point to a zero value (16 bit).
;   Return with zero flag set it (DE) was moved. Registers (DE)
; and (HL) are not changed. However (A) is.
;
MOVEWORD: LD	A,(HL)		;check for a zero word.
	INC	HL
	OR	(HL)		;both bytes zero?
	DEC	HL
	RET	NZ		;nope, just return.
	LD	A,(DE)		;yes, move two bytes from (DE) into
	LD	(HL),A		;this zero space.
	INC	DE
	INC	HL
	LD	A,(DE)
	LD	(HL),A
	DEC	DE		;don't disturb these registers.
	DEC	HL
	RET	
;
;   Get here to close a file specified by (fcb).
;
CLOSEIT:XOR	A		;clear status and file position bytes.
	LD	(STATUS),A
	LD	(FILEPOS),A
	LD	(FILEPOS+1),A
	CALL	GETWPRT		;get write protect bit for this drive.
	RET	NZ		;just return if it is set.
	CALL	GETS2		;else get the 's2' byte.
	AND	80H		;and look at bit 7 (file unmodified?).
	RET	NZ		;just return if set.
	LD	C,15		;else look up this file in directory.
	CALL	FINDFST
	CALL	CKFILPOS	;was it found?
	RET	Z		;just return if not.
	LD	BC,16		;set (HL) pointing to records used section.
	CALL	FCB2HL
	ADD	HL,BC
	EX	DE,HL
	LD	HL,(PARAMS)	;do the same for users specified fcb.
	ADD	HL,BC
	LD	C,16		;this many bytes are present in this extent.
CLOSEIT1: LD	A,(BIGDISK)	;8 or 16 bit record numbers?
	OR	A
	JP	Z,CLOSEIT4
	LD	A,(HL)		;just 8 bit. Get one from users fcb.
	OR	A
	LD	A,(DE)		;now get one from directory fcb.
	JP	NZ,CLOSEIT2
	LD	(HL),A		;users byte was zero. Update from directory.
CLOSEIT2: OR	A
	JP	NZ,CLOSEIT3
	LD	A,(HL)		;directories byte was zero, update from users fcb.
	LD	(DE),A
CLOSEIT3: CP	(HL)		;if neither one of these bytes were zero,
	JP	NZ,CLOSEIT7	;then close error if they are not the same.
	JP	CLOSEIT5	;ok so far, get to next byte in fcbs.
CLOSEIT4: CALL	MOVEWORD	;update users fcb if it is zero.
	EX	DE,HL
	CALL	MOVEWORD	;update directories fcb if it is zero.
	EX	DE,HL
	LD	A,(DE)		;if these two values are no different,
	CP	(HL)		;then a close error occured.
	JP	NZ,CLOSEIT7
	INC	DE		;check second byte.
	INC	HL
	LD	A,(DE)
	CP	(HL)
	JP	NZ,CLOSEIT7
	DEC	C		;remember 16 bit values.
CLOSEIT5: INC	DE		;bump to next item in table.
	INC	HL
	DEC	C		;there are 16 entries only.
	JP	NZ,CLOSEIT1	;continue if more to do.
	LD	BC,0FFECH	;backup 20 places (extent byte).
	ADD	HL,BC
	EX	DE,HL
	ADD	HL,BC
	LD	A,(DE)
	CP	(HL)		;directory's extent already greater than the
	JP	C,CLOSEIT6	;users extent?
	LD	(HL),A		;no, update directory extent.
	LD	BC,3		;and update the record count byte in
	ADD	HL,BC		;directories fcb.
	EX	DE,HL
	ADD	HL,BC
	LD	A,(HL)		;get from user.
	LD	(DE),A		;and put in directory.
CLOSEIT6: LD	A,0FFH		;set 'was open and is now closed' byte.
	LD	(CLOSEFLG),A
	JP	UPDATE1		;update the directory now.
CLOSEIT7: LD	HL,STATUS	;set return status and then return.
	DEC	(HL)
	RET	
;
;   Routine to get the next empty space in the directory. It
; will then be cleared for use.
;
GETEMPTY: CALL	CHKWPRT		;make sure disk is not write protected.
	LD	HL,(PARAMS)	;save current parameters (fcb).
	PUSH	HL
	LD	HL,EMPTYFCB	;use special one for empty space.
	LD	(PARAMS),HL
	LD	C,1		;search for first empty spot in directory.
	CALL	FINDFST		;(* only check first byte *)
	CALL	CKFILPOS	;none?
	POP	HL
	LD	(PARAMS),HL	;restore original fcb address.
	RET	Z		;return if no more space.
	EX	DE,HL
	LD	HL,15		;point to number of records for this file.
	ADD	HL,DE
	LD	C,17		;and clear all of this space.
	XOR	A
GETMT1:	LD	(HL),A
	INC	HL
	DEC	C
	JP	NZ,GETMT1
	LD	HL,13		;clear the 's1' byte also.
	ADD	HL,DE
	LD	(HL),A
	CALL	CHKNMBR		;keep (SCRATCH1) within bounds.
	CALL	FCBSET		;write out this fcb entry to directory.
	JP	SETS2B7		;set 's2' byte bit 7 (unmodified at present).
;
;   Routine to close the current extent and open the next one
; for reading.
;
GETNEXT:XOR	A
	LD	(CLOSEFLG),A	;clear close flag.
	CALL	CLOSEIT		;close this extent.
	CALL	CKFILPOS
	RET	Z		;not there???
	LD	HL,(PARAMS)	;get extent byte.
	LD	BC,12
	ADD	HL,BC
	LD	A,(HL)		;and increment it.
	INC	A
	AND	1FH		;keep within range 0-31.
	LD	(HL),A
	JP	Z,GTNEXT1	;overflow?
	LD	B,A		;mask extent byte.
	LD	A,(EXTMASK)
	AND	B
	LD	HL,CLOSEFLG	;check close flag (0ffh is ok).
	AND	(HL)
	JP	Z,GTNEXT2	;if zero, we must read in next extent.
	JP	GTNEXT3		;else, it is already in memory.
GTNEXT1:LD	BC,2		;Point to the 's2' byte.
	ADD	HL,BC
	INC	(HL)		;and bump it.
	LD	A,(HL)		;too many extents?
	AND	0FH
	JP	Z,GTNEXT5	;yes, set error code.
;
;   Get here to open the next extent.
;
GTNEXT2:LD	C,15		;set to check first 15 bytes of fcb.
	CALL	FINDFST		;find the first one.
	CALL	CKFILPOS	;none available?
	JP	NZ,GTNEXT3
	LD	A,(RDWRTFLG)	;no extent present. Can we open an empty one?
	INC	A		;0ffh means reading (so not possible).
	JP	Z,GTNEXT5	;or an error.
	CALL	GETEMPTY	;we are writing, get an empty entry.
	CALL	CKFILPOS	;none?
	JP	Z,GTNEXT5	;error if true.
	JP	GTNEXT4		;else we are almost done.
GTNEXT3:CALL	OPENIT1		;open this extent.
GTNEXT4:CALL	STRDATA		;move in updated data (rec #, extent #, etc.)
	XOR	A		;clear status and return.
	JP	SETSTAT
;
;   Error in extending the file. Too many extents were needed
; or not enough space on the disk.
;
GTNEXT5:CALL	IOERR1		;set error code, clear bit 7 of 's2'
	JP	SETS2B7		;so this is not written on a close.
;
;   Read a sequential file.
;
RDSEQ:	LD	A,1		;set sequential access mode.
	LD	(MODE),A
RDSEQ1:	LD	A,0FFH		;don't allow reading unwritten space.
	LD	(RDWRTFLG),A
	CALL	STRDATA		;put rec# and ext# into fcb.
	LD	A,(SAVNREC)	;get next record to read.
	LD	HL,SAVNXT	;get number of records in extent.
	CP	(HL)		;within this extent?
	JP	C,RDSEQ2
	CP	128		;no. Is this extent fully used?
	JP	NZ,RDSEQ3	;no. End-of-file.
	CALL	GETNEXT		;yes, open the next one.
	XOR	A		;reset next record to read.
	LD	(SAVNREC),A
	LD	A,(STATUS)	;check on open, successful?
	OR	A
	JP	NZ,RDSEQ3	;no, error.
RDSEQ2:	CALL	COMBLK		;ok. compute block number to read.
	CALL	CHKBLK		;check it. Within bounds?
	JP	Z,RDSEQ3	;no, error.
	CALL	LOGICAL		;convert (BLKNMBR) to logical sector (128 byte).
	CALL	TRKSEC1		;set the track and sector for this block #.
	CALL	DOREAD		;and read it.
	JP	SETNREC		;and set the next record to be accessed.
;
;   Read error occured. Set status and return.
;
RDSEQ3:	JP	IOERR1
;
;   Write the next sequential record.
;
WTSEQ:	LD	A,1		;set sequential access mode.
	LD	(MODE),A
WTSEQ1:	LD	A,0		;allow an addition empty extent to be opened.
	LD	(RDWRTFLG),A
	CALL	CHKWPRT		;check write protect status.
	LD	HL,(PARAMS)
	CALL	CKROF1		;check for read only file, (HL) already set to fcb.
	CALL	STRDATA		;put updated data into fcb.
	LD	A,(SAVNREC)	;get record number to write.
	CP	128		;within range?
	JP	NC,IOERR1	;no, error(?).
	CALL	COMBLK		;compute block number.
	CALL	CHKBLK		;check number.
	LD	C,0		;is there one to write to?
	JP	NZ,WTSEQ6	;yes, go do it.
	CALL	GETBLOCK	;get next block number within fcb to use.
	LD	(RELBLOCK),A	;and save.
	LD	BC,0		;start looking for space from the start
	OR	A		;if none allocated as yet.
	JP	Z,WTSEQ2
	LD	C,A		;extract previous block number from fcb
	DEC	BC		;so we can be closest to it.
	CALL	EXTBLK
	LD	B,H
	LD	C,L
WTSEQ2:	CALL	FNDSPACE	;find the next empty block nearest number (BC).
	LD	A,L		;check for a zero number.
	OR	H
	JP	NZ,WTSEQ3
	LD	A,2		;no more space?
	JP	SETSTAT
WTSEQ3:	LD	(BLKNMBR),HL	;save block number to access.
	EX	DE,HL		;put block number into (DE).
	LD	HL,(PARAMS)	;now we must update the fcb for this
	LD	BC,16		;newly allocated block.
	ADD	HL,BC
	LD	A,(BIGDISK)	;8 or 16 bit block numbers?
	OR	A
	LD	A,(RELBLOCK)	;(* update this entry *)
	JP	Z,WTSEQ4	;zero means 16 bit ones.
	CALL	ADDA2HL		;(HL)=(HL)+(A)
	LD	(HL),E		;store new block number.
	JP	WTSEQ5
WTSEQ4:	LD	C,A		;compute spot in this 16 bit table.
	LD	B,0
	ADD	HL,BC
	ADD	HL,BC
	LD	(HL),E		;stuff block number (DE) there.
	INC	HL
	LD	(HL),D
WTSEQ5:	LD	C,2		;set (C) to indicate writing to un-used disk space.
WTSEQ6:	LD	A,(STATUS)	;are we ok so far?
	OR	A
	RET	NZ
	PUSH	BC		;yes, save write flag for bios (register C).
	CALL	LOGICAL		;convert (BLKNMBR) over to loical sectors.
	LD	A,(MODE)	;get access mode flag (1=sequential,
	DEC	A		;0=random, 2=special?).
	DEC	A
	JP	NZ,WTSEQ9
;
;   Special random i/o from function #40. Maybe for M/PM, but the
; current block, if it has not been written to, will be zeroed
; out and then written (reason?).
;
	POP	BC
	PUSH	BC
	LD	A,C		;get write status flag (2=writing unused space).
	DEC	A
	DEC	A
	JP	NZ,WTSEQ9
	PUSH	HL
	LD	HL,(DIRBUF)	;zero out the directory buffer.
	LD	D,A		;note that (A) is zero here.
WTSEQ7:	LD	(HL),A
	INC	HL
	INC	D		;do 128 bytes.
	JP	P,WTSEQ7
	CALL	DIRDMA		;tell the bios the dma address for directory access.
	LD	HL,(LOGSECT)	;get sector that starts current block.
	LD	C,2		;set 'writing to unused space' flag.
WTSEQ8:	LD	(BLKNMBR),HL	;save sector to write.
	PUSH	BC
	CALL	TRKSEC1		;determine its track and sector numbers.
	POP	BC
	CALL	DOWRITE		;now write out 128 bytes of zeros.
	LD	HL,(BLKNMBR)	;get sector number.
	LD	C,0		;set normal write flag.
	LD	A,(BLKMASK)	;determine if we have written the entire
	LD	B,A		;physical block.
	AND	L
	CP	B
	INC	HL		;prepare for the next one.
	JP	NZ,WTSEQ8	;continue until (BLKMASK+1) sectors written.
	POP	HL		;reset next sector number.
	LD	(BLKNMBR),HL
	CALL	DEFDMA		;and reset dma address.
;
;   Normal disk write. Set the desired track and sector then
; do the actual write.
;
WTSEQ9:	CALL	TRKSEC1		;determine track and sector for this write.
	POP	BC		;get write status flag.
	PUSH	BC
	CALL	DOWRITE		;and write this out.
	POP	BC
	LD	A,(SAVNREC)	;get number of records in file.
	LD	HL,SAVNXT	;get last record written.
	CP	(HL)
	JP	C,WTSEQ10
	LD	(HL),A		;we have to update record count.
	INC	(HL)
	LD	C,2
;
;*   This area has been patched to correct disk update problem
;* when using blocking and de-blocking in the BIOS.
;
WTSEQ10:NOP			;was 'dcr c'
	NOP			;was 'dcr c'
	LD	HL,0		;was 'jnz wtseq99'
;
; *   End of patch.
;
	PUSH	AF
	CALL	GETS2		;set 'extent written to' flag.
	AND	7FH		;(* clear bit 7 *)
	LD	(HL),A
	POP	AF		;get record count for this extent.
WTSEQ99:CP	127		;is it full?
	JP	NZ,WTSEQ12
	LD	A,(MODE)	;yes, are we in sequential mode?
	CP	1
	JP	NZ,WTSEQ12
	CALL	SETNREC		;yes, set next record number.
	CALL	GETNEXT		;and get next empty space in directory.
	LD	HL,STATUS	;ok?
	LD	A,(HL)
	OR	A
	JP	NZ,WTSEQ11
	DEC	A		;yes, set record count to -1.
	LD	(SAVNREC),A
WTSEQ11:LD	(HL),0		;clear status.
WTSEQ12:JP	SETNREC		;set next record to access.
;
;   For random i/o, set the fcb for the desired record number
; based on the 'r0,r1,r2' bytes. These bytes in the fcb are
; used as follows:
;
;       fcb+35            fcb+34            fcb+33
;  |     'r-2'      |      'r-1'      |      'r-0'     |
;  |7             0 | 7             0 | 7             0|
;  |0 0 0 0 0 0 0 0 | 0 0 0 0 0 0 0 0 | 0 0 0 0 0 0 0 0|
;  |    overflow   | | extra |  extent   |   record #  |
;  | ______________| |_extent|__number___|_____________|
;                     also 's2'
;
;   On entry, register (C) contains 0ffh if this is a read
; and thus we can not access unwritten disk space. Otherwise,
; another extent will be opened (for writing) if required.
;
POSITION: XOR	A		;set random i/o flag.
	LD	(MODE),A
;
;   Special entry (function #40). M/PM ?
;
POSITN1:PUSH	BC		;save read/write flag.
	LD	HL,(PARAMS)	;get address of fcb.
	EX	DE,HL
	LD	HL,33		;now get byte 'r0'.
	ADD	HL,DE
	LD	A,(HL)
	AND	7FH		;keep bits 0-6 for the record number to access.
	PUSH	AF
	LD	A,(HL)		;now get bit 7 of 'r0' and bits 0-3 of 'r1'.
	RLA	
	INC	HL
	LD	A,(HL)
	RLA	
	AND	1FH		;and save this in bits 0-4 of (C).
	LD	C,A		;this is the extent byte.
	LD	A,(HL)		;now get the extra extent byte.
	RRA	
	RRA	
	RRA	
	RRA	
	AND	0FH
	LD	B,A		;and save it in (B).
	POP	AF		;get record number back to (A).
	INC	HL		;check overflow byte 'r2'.
	LD	L,(HL)
	INC	L
	DEC	L
	LD	L,6		;prepare for error.
	JP	NZ,POSITN5	;out of disk space error.
	LD	HL,32		;store record number into fcb.
	ADD	HL,DE
	LD	(HL),A
	LD	HL,12		;and now check the extent byte.
	ADD	HL,DE
	LD	A,C
	SUB	(HL)		;same extent as before?
	JP	NZ,POSITN2
	LD	HL,14		;yes, check extra extent byte 's2' also.
	ADD	HL,DE
	LD	A,B
	SUB	(HL)
	AND	7FH
	JP	Z,POSITN3	;same, we are almost done then.
;
;  Get here when another extent is required.
;
POSITN2:PUSH	BC
	PUSH	DE
	CALL	CLOSEIT		;close current extent.
	POP	DE
	POP	BC
	LD	L,3		;prepare for error.
	LD	A,(STATUS)
	INC	A
	JP	Z,POSITN4	;close error.
	LD	HL,12		;put desired extent into fcb now.
	ADD	HL,DE
	LD	(HL),C
	LD	HL,14		;and store extra extent byte 's2'.
	ADD	HL,DE
	LD	(HL),B
	CALL	OPENIT		;try and get this extent.
	LD	A,(STATUS)	;was it there?
	INC	A
	JP	NZ,POSITN3
	POP	BC		;no. can we create a new one (writing?).
	PUSH	BC
	LD	L,4		;prepare for error.
	INC	C
	JP	Z,POSITN4	;nope, reading unwritten space error.
	CALL	GETEMPTY	;yes we can, try to find space.
	LD	L,5		;prepare for error.
	LD	A,(STATUS)
	INC	A
	JP	Z,POSITN4	;out of space?
;
;   Normal return location. Clear error code and return.
;
POSITN3:POP	BC		;restore stack.
	XOR	A		;and clear error code byte.
	JP	SETSTAT
;
;   Error. Set the 's2' byte to indicate this (why?).
;
POSITN4:PUSH	HL
	CALL	GETS2
	LD	(HL),0C0H
	POP	HL
;
;   Return with error code (presently in L).
;
POSITN5:POP	BC
	LD	A,L		;get error code.
	LD	(STATUS),A
	JP	SETS2B7
;
;   Read a random record.
;
READRAN:LD	C,0FFH		;set 'read' status.
	CALL	POSITION	;position the file to proper record.
	CALL	Z,RDSEQ1	;and read it as usual (if no errors).
	RET	
;
;   Write to a random record.
;
WRITERAN: LD	C,0		;set 'writing' flag.
	CALL	POSITION	;position the file to proper record.
	CALL	Z,WTSEQ1	;and write as usual (if no errors).
	RET	
;
;   Compute the random record number. Enter with (HL) pointing
; to a fcb an (DE) contains a relative location of a record
; number. On exit, (C) contains the 'r0' byte, (B) the 'r1'
; byte, and (A) the 'r2' byte.
;
;   On return, the zero flag is set if the record is within
; bounds. Otherwise, an overflow occured.
;
COMPRAND: EX	DE,HL		;save fcb pointer in (DE).
	ADD	HL,DE		;compute relative position of record #.
	LD	C,(HL)		;get record number into (BC).
	LD	B,0
	LD	HL,12		;now get extent.
	ADD	HL,DE
	LD	A,(HL)		;compute (BC)=(record #)+(extent)*128.
	RRCA			;move lower bit into bit 7.
	AND	80H		;and ignore all other bits.
	ADD	A,C		;add to our record number.
	LD	C,A
	LD	A,0		;take care of any carry.
	ADC	A,B
	LD	B,A
	LD	A,(HL)		;now get the upper bits of extent into
	RRCA			;bit positions 0-3.
	AND	0FH		;and ignore all others.
	ADD	A,B		;add this in to 'r1' byte.
	LD	B,A
	LD	HL,14		;get the 's2' byte (extra extent).
	ADD	HL,DE
	LD	A,(HL)
	ADD	A,A		;and shift it left 4 bits (bits 4-7).
	ADD	A,A
	ADD	A,A
	ADD	A,A
	PUSH	AF		;save carry flag (bit 0 of flag byte).
	ADD	A,B		;now add extra extent into 'r1'.
	LD	B,A
	PUSH	AF		;and save carry (overflow byte 'r2').
	POP	HL		;bit 0 of (L) is the overflow indicator.
	LD	A,L
	POP	HL		;and same for first carry flag.
	OR	L		;either one of these set?
	AND	01H		;only check the carry flags.
	RET	
;
;   Routine to setup the fcb (bytes 'r0', 'r1', 'r2') to
; reflect the last record used for a random (or other) file.
; This reads the directory and looks at all extents computing
; the largerst record number for each and keeping the maximum
; value only. Then 'r0', 'r1', and 'r2' will reflect this
; maximum record number. This is used to compute the space used
; by a random file.
;
RANSIZE:LD	C,12		;look thru directory for first entry with
	CALL	FINDFST		;this name.
	LD	HL,(PARAMS)	;zero out the 'r0, r1, r2' bytes.
	LD	DE,33
	ADD	HL,DE
	PUSH	HL
	LD	(HL),D		;note that (D)=0.
	INC	HL
	LD	(HL),D
	INC	HL
	LD	(HL),D
RANSIZ1:CALL	CKFILPOS	;is there an extent to process?
	JP	Z,RANSIZ3	;no, we are done.
	CALL	FCB2HL		;set (HL) pointing to proper fcb in dir.
	LD	DE,15		;point to last record in extent.
	CALL	COMPRAND	;and compute random parameters.
	POP	HL
	PUSH	HL		;now check these values against those
	LD	E,A		;already in fcb.
	LD	A,C		;the carry flag will be set if those
	SUB	(HL)		;in the fcb represent a larger size than
	INC	HL		;this extent does.
	LD	A,B
	SBC	A,(HL)
	INC	HL
	LD	A,E
	SBC	A,(HL)
	JP	C,RANSIZ2
	LD	(HL),E		;we found a larger (in size) extent.
	DEC	HL		;stuff these values into fcb.
	LD	(HL),B
	DEC	HL
	LD	(HL),C
RANSIZ2:CALL	FINDNXT		;now get the next extent.
	JP	RANSIZ1		;continue til all done.
RANSIZ3:POP	HL		;we are done, restore the stack and
	RET			;return.
;
;   Function to return the random record position of a given
; file which has been read in sequential mode up to now.
;
SETRAN:	LD	HL,(PARAMS)	;point to fcb.
	LD	DE,32		;and to last used record.
	CALL	COMPRAND	;compute random position.
	LD	HL,33		;now stuff these values into fcb.
	ADD	HL,DE
	LD	(HL),C		;move 'r0'.
	INC	HL
	LD	(HL),B		;and 'r1'.
	INC	HL
	LD	(HL),A		;and lastly 'r2'.
	RET	
;
;   This routine select the drive specified in (ACTIVE) and
; update the login vector and bitmap table if this drive was
; not already active.
;
LOGINDRV: LD	HL,(LOGIN)	;get the login vector.
	LD	A,(ACTIVE)	;get the default drive.
	LD	C,A
	CALL	SHIFTR		;position active bit for this drive
	PUSH	HL		;into bit 0.
	EX	DE,HL
	CALL	SELECT		;select this drive.
	POP	HL
	CALL	Z,SLCTERR	;valid drive?
	LD	A,L		;is this a newly activated drive?
	RRA	
	RET	C
	LD	HL,(LOGIN)	;yes, update the login vector.
	LD	C,L
	LD	B,H
	CALL	SETBIT
	LD	(LOGIN),HL	;and save.
	JP	BITMAP		;now update the bitmap.
;
;   Function to set the active disk number.
;
SETDSK:	LD	A,(EPARAM)	;get parameter passed and see if this
	LD	HL,ACTIVE	;represents a change in drives.
	CP	(HL)
	RET	Z
	LD	(HL),A		;yes it does, log it in.
	JP	LOGINDRV
;
;   This is the 'auto disk select' routine. The firsst byte
; of the fcb is examined for a drive specification. If non
; zero then the drive will be selected and loged in.
;
AUTOSEL:LD	A,0FFH		;say 'auto-select activated'.
	LD	(AUTO),A
	LD	HL,(PARAMS)	;get drive specified.
	LD	A,(HL)
	AND	1FH		;look at lower 5 bits.
	DEC	A		;adjust for (1=A, 2=B) etc.
	LD	(EPARAM),A	;and save for the select routine.
	CP	1EH		;check for 'no change' condition.
	JP	NC,AUTOSL1	;yes, don't change.
	LD	A,(ACTIVE)	;we must change, save currently active
	LD	(OLDDRV),A	;drive.
	LD	A,(HL)		;and save first byte of fcb also.
	LD	(AUTOFLAG),A	;this must be non-zero.
	AND	0E0H		;whats this for (bits 6,7 are used for
	LD	(HL),A		;something)?
	CALL	SETDSK		;select and log in this drive.
AUTOSL1:LD	A,(USERNO)	;move user number into fcb.
	LD	HL,(PARAMS)	;(* upper half of first byte *)
	OR	(HL)
	LD	(HL),A
	RET			;and return (all done).
;
;   Function to return the current cp/m version number.
;
GETVER:	LD	A,022H		;version 2.2
	JP	SETSTAT
;
;   Function to reset the disk system.
;
RSTDSK:	LD	HL,0		;clear write protect status and log
	LD	(WRTPRT),HL	;in vector.
	LD	(LOGIN),HL
	XOR	A		;select drive 'A'.
	LD	(ACTIVE),A
	LD	HL,TBUFF	;setup default dma address.
	LD	(USERDMA),HL
	CALL	DEFDMA
	JP	LOGINDRV	;now log in drive 'A'.
;
;   Function to open a specified file.
;
OPENFIL:CALL	CLEARS2		;clear 's2' byte.
	CALL	AUTOSEL		;select proper disk.
	JP	OPENIT		;and open the file.
;
;   Function to close a specified file.
;
CLOSEFIL: CALL	AUTOSEL		;select proper disk.
	JP	CLOSEIT		;and close the file.
;
;   Function to return the first occurence of a specified file
; name. If the first byte of the fcb is '?' then the name will
; not be checked (get the first entry no matter what).
;
GETFST:	LD	C,0		;prepare for special search.
	EX	DE,HL
	LD	A,(HL)		;is first byte a '?'?
	CP	'?'
	JP	Z,GETFST1	;yes, just get very first entry (zero length match).
	CALL	SETEXT		;get the extension byte from fcb.
	LD	A,(HL)		;is it '?'? if yes, then we want
	CP	'?'		;an entry with a specific 's2' byte.
	CALL	NZ,CLEARS2	;otherwise, look for a zero 's2' byte.
	CALL	AUTOSEL		;select proper drive.
	LD	C,15		;compare bytes 0-14 in fcb (12&13 excluded).
GETFST1:CALL	FINDFST		;find an entry and then move it into
	JP	MOVEDIR		;the users dma space.
;
;   Function to return the next occurence of a file name.
;
GETNXT:	LD	HL,(SAVEFCB)	;restore pointers. note that no
	LD	(PARAMS),HL	;other dbos calls are allowed.
	CALL	AUTOSEL		;no error will be returned, but the
	CALL	FINDNXT		;results will be wrong.
	JP	MOVEDIR
;
;   Function to delete a file by name.
;
DELFILE:CALL	AUTOSEL		;select proper drive.
	CALL	ERAFILE		;erase the file.
	JP	STSTATUS	;set status and return.
;
;   Function to execute a sequential read of the specified
; record number.
;
READSEQ:CALL	AUTOSEL		;select proper drive then read.
	JP	RDSEQ
;
;   Function to write the net sequential record.
;
WRTSEQ:	CALL	AUTOSEL		;select proper drive then write.
	JP	WTSEQ
;
;   Create a file function.
;
FCREATE:CALL	CLEARS2		;clear the 's2' byte on all creates.
	CALL	AUTOSEL		;select proper drive and get the next
	JP	GETEMPTY	;empty directory space.
;
;   Function to rename a file.
;
RENFILE:CALL	AUTOSEL		;select proper drive and then switch
	CALL	CHGNAMES	;file names.
	JP	STSTATUS
;
;   Function to return the login vector.
;
GETLOG:	LD	HL,(LOGIN)
	JP	GETPRM1
;
;   Function to return the current disk assignment.
;
GETCRNT:LD	A,(ACTIVE)
	JP	SETSTAT
;
;   Function to set the dma address.
;
PUTDMA:	EX	DE,HL
	LD	(USERDMA),HL	;save in our space and then get to
	JP	DEFDMA		;the bios with this also.
;
;   Function to return the allocation vector.
;
GETALOC:LD	HL,(ALOCVECT)
	JP	GETPRM1
;
;   Function to return the read-only status vector.
;
GETROV:	LD	HL,(WRTPRT)
	JP	GETPRM1
;
;   Function to set the file attributes (read-only, system).
;
SETATTR:CALL	AUTOSEL		;select proper drive then save attributes.
	CALL	SAVEATTR
	JP	STSTATUS
;
;   Function to return the address of the disk parameter block
; for the current drive.
;
GETPARM:LD	HL,(DISKPB)
GETPRM1:LD	(STATUS),HL
	RET	
;
;   Function to get or set the user number. If (E) was (FF)
; then this is a request to return the current user number.
; Else set the user number from (E).
;
GETUSER:LD	A,(EPARAM)	;get parameter.
	CP	0FFH		;get user number?
	JP	NZ,SETUSER
	LD	A,(USERNO)	;yes, just do it.
	JP	SETSTAT
SETUSER:AND	1FH		;no, we should set it instead. keep low
	LD	(USERNO),A	;bits (0-4) only.
	RET	
;
;   Function to read a random record from a file.
;
RDRANDOM: CALL	AUTOSEL		;select proper drive and read.
	JP	READRAN
;
;   Function to compute the file size for random files.
;
WTRANDOM: CALL	AUTOSEL		;select proper drive and write.
	JP	WRITERAN
;
;   Function to compute the size of a random file.
;
FILESIZE: CALL	AUTOSEL		;select proper drive and check file length
	JP	RANSIZE
;
;   Function #37. This allows a program to log off any drives.
; On entry, set (DE) to contain a word with bits set for those
; drives that are to be logged off. The log-in vector and the
; write protect vector will be updated. This must be a M/PM
; special function.
;
LOGOFF:	LD	HL,(PARAMS)	;get drives to log off.
	LD	A,L		;for each bit that is set, we want
	CPL			;to clear that bit in (LOGIN)
	LD	E,A		;and (WRTPRT).
	LD	A,H
	CPL	
	LD	HL,(LOGIN)	;reset the login vector.
	AND	H
	LD	D,A
	LD	A,L
	AND	E
	LD	E,A
	LD	HL,(WRTPRT)
	EX	DE,HL
	LD	(LOGIN),HL	;and save.
	LD	A,L		;now do the write protect vector.
	AND	E
	LD	L,A
	LD	A,H
	AND	D
	LD	H,A
	LD	(WRTPRT),HL	;and save. all done.
	RET	
;
;   Get here to return to the user.
;
GOBACK:	LD	A,(AUTO)	;was auto select activated?
	OR	A
	JP	Z,GOBACK1
	LD	HL,(PARAMS)	;yes, but was a change made?
	LD	(HL),0		;(* reset first byte of fcb *)
	LD	A,(AUTOFLAG)
	OR	A
	JP	Z,GOBACK1
	LD	(HL),A		;yes, reset first byte properly.
	LD	A,(OLDDRV)	;and get the old drive and select it.
	LD	(EPARAM),A
	CALL	SETDSK
GOBACK1:LD	HL,(USRSTACK)	;reset the users stack pointer.
	LD	SP,HL
	LD	HL,(STATUS)	;get return status.
	LD	A,L		;force version 1.4 compatability.
	LD	B,H
	RET			;and go back to user.
;
;   Function #40. This is a special entry to do random i/o.
; For the case where we are writing to unused disk space, this
; space will be zeroed out first. This must be a M/PM special
; purpose function, because why would any normal program even
; care about the previous contents of a sector about to be
; written over.
;
WTSPECL:CALL	AUTOSEL		;select proper drive.
	LD	A,2		;use special write mode.
	LD	(MODE),A
	LD	C,0		;set write indicator.
	CALL	POSITN1		;position the file.
	CALL	Z,WTSEQ1	;and write (if no errors).
	RET	
;
;**************************************************************
;*
;*     BDOS data storage pool.
;*
;**************************************************************
;
EMPTYFCB: db	0E5H		;empty directory segment indicator.
WRTPRT:	dw	0		;write protect status for all 16 drives.
LOGIN:	dw	0		;drive active word (1 bit per drive).
USERDMA:dw	080H		;user's dma address (defaults to 80h).
;
;   Scratch areas from parameter block.
;
SCRATCH1: dw	0		;relative position within dir segment for file (0-3).
SCRATCH2: dw	0		;last selected track number.
SCRATCH3: dw	0		;last selected sector number.
;
;   Disk storage areas from parameter block.
;
DIRBUF:	dw	0		;address of directory buffer to use.
DISKPB:	dw	0		;contains address of disk parameter block.
CHKVECT:dw	0		;address of check vector.
ALOCVECT: dw	0		;address of allocation vector (bit map).
;
;   Parameter block returned from the bios.
;
SECTORS:dw	0		;sectors per track from bios.
BLKSHFT:db	0		;block shift.
BLKMASK:db	0		;block mask.
EXTMASK:db	0		;extent mask.
DSKSIZE:dw	0		;disk size from bios (number of blocks-1).
DIRSIZE:	dw	0		;directory size.
ALLOC0:	dw	0		;storage for first bytes of bit map (dir space used).
ALLOC1:	dw	0
OFFSET:	dw	0		;first usable track number.
XLATE:	dw	0		;sector translation table address.
;
;
CLOSEFLG: db	0		;close flag (=0ffh is extent written ok).
RDWRTFLG: db	0		;read/write flag (0ffh=read, 0=write).
FNDSTAT:	db	0		;filename found status (0=found first entry).
MODE:	db	0		;I/o mode select (0=random, 1=sequential, 2=special random).
EPARAM:	db	0		;storage for register (E) on entry to bdos.
RELBLOCK: db	0		;relative position within fcb of block number written.
COUNTER:	db	0		;byte counter for directory name searches.
SAVEFCB:	dw	0,0		;save space for address of fcb (for directory searches).
BIGDISK:	db	0		;if =0 then disk is > 256 blocks long.
AUTO:	db	0		;if non-zero, then auto select activated.
OLDDRV:	db	0		;on auto select, storage for previous drive.
AUTOFLAG: db	0		;if non-zero, then auto select changed drives.
SAVNXT:	db	0		;storage for next record number to access.
SAVEXT:	db	0		;storage for extent number of file.
SAVNREC:	dw	0		;storage for number of records in file.
BLKNMBR:	dw	0		;block number (physical sector) used within a file or logical sect
LOGSECT:	dw	0		;starting logical (128 byte) sector of block (physical sector).
FCBPOS:	db	0		;relative position within buffer for fcb of file of interest.
FILEPOS:	dw	0		;files position within directory (0 to max entries -1).
;
;   Disk directory buffer checksum bytes. One for each of the
; 16 possible drives.
;
CKSUMTBL: db	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;
;   Extra space ?
;
	db	0,0,0,0
;
;**************************************************************
;*
;*        B I O S   J U M P   T A B L E
;*
;**************************************************************
;
;;; the jump table start from 0f200h
;;; overlap with jump table of cbiosZZ which also starts from 0f200h
;BOOT:	JP	0		;NOTE WE USE FAKE DESTINATIONS
;WBOOT:	JP	0
;CONST:	JP	0
;CONIN:	JP	0
;CONOUT:	JP	0
;LIST:	JP	0
;PUNCH:	JP	0
;READER:	JP	0
;HOME:	JP	0
;SELDSK:	JP	0
;SETTRK:	JP	0
;SETSEC:	JP	0
;SETDMA:	JP	0
;READ:	JP	0
;WRITE:	JP	0
;PRSTAT:	JP	0
;SECTRN:	JP	0
;
;*
;******************   E N D   O F   C P / M   *****************
;*
; memory organization:
;  0x0-0xFFF is physical page 0 that contains bootstrap code.  It is write protected by MMU
;  0x1000-0xFFFF is page 1 to page F of 64K memory
;  0x10000-0x3BFFF contains CP/M22 system files, drive A:
;  0x3C000-0x3CFFF is logical page 0 that will replace physical page 0 when MMU is enabled
;  0x3D000-0x3FFFF is CP/M 2.2 executable
;  0x40000-0x7FFFF is RAM disk (drive B:)
; 8/5/18 Fork from cpm22all_ramdisk-in-0x80000_3.5meg_RAMdisk
; 2/17/18 pass console input unfiltered
; CBIOS from CPM2 alteration guide, appendix C
; skeletal cbios for first level of cp/m 2.0 
msize 	equ 62	;cp/m version memory size in kilo
; "bias" is address offset from 3400h for memory system other than 16k
bias 	equ (msize-20)*1024
ccp 	equ 3400h+bias		;base of ccp  (0xDC00)
bdos 	equ ccp+806h		;base of bdos (0xE406)
bios 	equ ccp+1600h		;base of bios (0xF200)
cdisk 	equ 0004h		;current disk number 0=a,...,15=p
iobyte 	equ 0003h		;intel i/o byte
;;; Z280 internal UART
UARTconf	equ 10h		; UART configuration reg
RxData  	equ 16h        	; on-chip UART receive register
TxData  	equ 18h        	; on-chip UART transmit register
RxStat  	equ 14h        	; on-chip UART transmitter status/control register
TxStat  	equ 12h        	; 0n-chip UART receiver status/control register
;;; CF interface registers
;CFdata   	equ 0C0h    	;CF data register
;CFerr    	equ 0C2h    	;CF error reg
;CFsectcnt equ 0C5h    	;CF sector count reg
;CF07     	equ 0C7h   	;CF LA0-7
;CF815    	equ 0C9h       	;CF LA8-15
;CF1623   	equ 0CBh       	;CF LA16-23
;CF2427   	equ 0CDh       	;CF LA24-27
;CFstat   	equ 0CFh       	;CF status/command reg
MMUctrl	equ 0f0h		; MMU master control reg
MMUptr	equ 0f1h		; MMU page descriptor reg pointer
MMUsel	equ 0f5h		; MMU descriptor select port
MMUmove	equ 0f4h		; MMU block move port
MMUinv	equ 0f2h		; MMU invalidation port
DMActrl	equ 01fh		; DMA master control reg
DMA0dstL	equ 0		; DMA chan 0 destination reg low
DMA0dstH	equ 1		; DMA chan 0 destination reg high
DMA0srcL	equ 2		; DMA chan 0 source reg low
DMA0srcH	equ 3		; DMA chan 0 source reg high
DMA0cnt	equ 4		; DMA chan 0 count reg
DMA0td	equ 5		; DMA chan 0 transaction descriptor
DMA1dstL	equ 8		; DMA chan 1 destination reg low
DMA1dstH	equ 9		; DMA chan 1 destination reg high
DMA1srcL	equ 0ah		; DMA chan 1 source reg low
DMA1srcH	equ 0bh		; DMA chan 1 source reg high
DMA1cnt	equ 0ch		; DMA chan 1 count reg
DMA1td	equ 0dh		; DMA chan 1 transaction descriptor
DMA2dstL	equ 10h		; DMA chan 2 destination reg low
DMA2dstH	equ 11h		; DMA chan 2 destination reg high
DMA2srcL	equ 12h		; DMA chan 2 source reg low
DMA2srcH	equ 13h		; DMA chan 2 source reg high
DMA2cnt	equ 14h		; DMA chan 2 count reg
DMA2td	equ 15h		; DMA chan 2 transaction descriptor
DMA3dstL	equ 18h		; DMA chan 3 destination reg low
DMA3dstH	equ 19h		; DMA chan 3 destination reg high
DMA3srcL	equ 1ah		; DMA chan 3 source reg low
DMA3srcH	equ 1bh		; DMA chan 3 source reg high
DMA3cnt	equ 1ch		; DMA chan 3 count reg
DMA3td	equ 1dh		; DMA chan 3 transaction descriptor

	org bios		;origin of this program
nsects equ ($-ccp)/128	; warm start sector count
; jump vector for individual subroutines
	jp BOOT		;cold start
wboote:	jp WBOOT		;warm start
	jp CONST		;console status
	jp CONIN		;console char in
	jp CONOUT		;console char out
	jp LIST		;list char out
	jp PUNCH		;punch char out
	jp READER		;reader char out
	jp HOME		;move head to home position
	jp SELDSK		;select disk
	jp SETTRK		;set track number
	jp SETSEC		;set sector number
	jp SETDMA		;set dma address
	jp READ		;read disk
	jp WRITE		;write disk
	jp LISTST		;return list status
	jp SECTRAN	;sector translate
; fixed data tables for 2-drive in RAM
; disk parameter header for disk 00
dpbase:	dw 0,0
	dw 0,0
	dw dirbf,dpblk0
	dw chk00,all00
; disk parameter header for disk 01
	dw 0,0
	dw 0,0
	dw dirbf,dpblk1
	dw chk01,all01
; disk parameter header for disk 02
;	dw 0,0
;	dw 0,0
;	dw dirbf,dpblk2
;	dw chk02,all02
; disk parameter header for disk 03
;	dw 0,0
;	dw 0,0
;	dw dirbf,dpblk3
;	dw chk03,all03
; disk parameter header for disk 04, RAM disk
;	dw 0,0
;	dw 0,0
;	dw dirbf,dpblk4
;	dw chk04,all04

dpblk0:
; drive A: contains cpm2.2 system files.  
; drive A starts from 0x10000
; 16K between the end of drive A and beginning of drive B is CPM2.2 executable & logical page 0
; Make BLS 2048 because cpmtools can't handle BLS of 1024 correctly

	dw 1024		;sectors per track (SPT)
	db 4		; block shift factor (BSH)
	db 15		;block mask (BLM)
	db 1		;null mask (EXM)
	dw 87		;disk size-1 (DSM), 176K bytes		
	dw 63		;directory max (DRM)	
	db 080h		;alloc 0 (AL0)
	db 0		;alloc 1 (AL1)		
	dw 0		;check size (CKS)
	dw 0		; track offset (OFF)
	
	
dpblk1:
; drive B: contains cpm2.2 system files.  The very last 4K of drive A: is 
;  4K physical page that maps to logical page 0
; track is 64K or 512 sectors.  drive B: starts from memory location 0x40000, no offset
; make BLS 2048 because cpmtools can't handle BLS of 1024 correctly

	dw 1024		;sectors per track
	db 4		; block shift factor (BSH)
	db 15		;block mask (BLM)
	db 1		;null mask (EXM)
	dw 127		;disk size-1, 256K bytes
	dw 63		;directory max		
	db 080h		;alloc 0
	db 0		;alloc 1		
	dw 0		;check size
	dw 0		; track offset 
	
; end of fixed tables
; individual subroutines to perform each function
BOOT:
; initialization
	ld sp,100h	;;; stack is at 100h
;	xor a		;;; zero the accumulator
;	ld (track+1),a	;;; clear the MSB of track.  track is only 8-bit value
;	call MMUPage	; initialize MMU
;	call initMMU	; 16 system page descriptor are valid and point to first 64K 
	call UARTPage	;;; default page i/o reg is internal UART
	ld a,0e2h		; initialize the UART configuration register
	out (UARTconf),a
	ld a,80h		;enable UART transmit and receive
	out (TxStat),a
	out (RxStat),a
	ld hl,signon
	call STROUT	;;; print sign on message
	xor a		;zero in the accumulator
	ld (iobyte),a	;clear the iobyte
	ld (cdisk),a	;select disk zero
	jp gocpm		;initialize and go to cp/m
WBOOT:
; simplest case is to read the disk until all sector...
; CP/M is stored in LA 0x80-0x92.  The CCP & BDOS is from LA 0x80-0x91.  Warm boot will load
;  data from LA9 thru LA20.  Note LA9 is CPM sector 37 (sector = 1+ (LA*4)) <-- not true
;  Use sector 36 as LA9 <-- not sure why
	ld sp,80h		;use space below buffer for stack
;	xor a		; zero the accumulator
;	ld (track+1),a	; clear the MSB of track.  track is only 8-bit value
	ld c,0		;select disk 0
	call SELDSK
;	call HOME		;go to track 00


; CP/M executable is stored in 0x3D000-0x3FFFF.  Copy that to 0xDC00-0xFFFF
; 0x3D000 is effectively disk 0, track 1, sector 0x1A0

	ld b,0		; (track+1) is always 0
	ld c,1		; (track) is 1
	call SETTRK
	ld b,nsects	;reg B counts # of sectors to ...
	ld de,1a0h	; sector 0x1A0 is start of CP/M2.2 executable
; note that we begin by reading track 1 sector 0x1A0, the beginning of CPM

	ld hl,ccp
load1:
; load one more sector
	push bc		; save sector count, current track
	push de		; save next sector to read
	push hl		;save dma address
	ld b,d
	ld c,e		;get sector address to reg BC
	call SETSEC	;set sector address from reg
	pop bc		;recall dma address to b,c
	push bc		;replace on stack for later recall
	call SETDMA	;set dma address from b,c
;drive set to 0, track set, sector set, dma address
	call READ
	cp 0		;any errors?
	jp nz,WBOOT	;retry the entire boot if an error
; no error, move to next sector
	pop hl		;recall dma address
	ld de,128		;dma=dma+128
	add hl,de		;new dma address is in h,l
	pop de		;recall sector address
	pop bc		;recall number of sector remaining
	dec b		;sectors=sectors-1
	jp z,gocpm	; transfer to cp/m if all have been read
;more sector remain to load
	inc de
	jp load1		; no need to check next track

; end of load operation, set parameters and go to cp/m
gocpm:
	ld a,0c3h		;c3 is a jmp instruction
	ld (0),a		; fro jmp to wboot
	ld hl,wboote	;wboot entry point
	ld (1),hl		; set address field for jmp at 0
	ld (5),a		; for jmp to bdos
	ld hl,bdos	; bdos entry point
	ld (6),hl		;address field of jump at 5 to bdos
	ld bc,80h		;default dma address is 80h
	call SETDMA
	ei		;enable the interrupt system
	ld a,(cdisk)	; get current disk number
	ld c,a		;send to the ccp
	jp ccp		;go to cp/m for further processing
;simple i/o handler
;console status, return 0ffh if char ready
CONST:
	in a,(RxStat)	;;; read on-chip UART receiver status
	and 10h		; data available?
	ret z		; return with 0 if data not available
	ld a,0ffh		; else data is available, return with 0ffh
	ret

CONIN:
; console char into reg A
	in a,(RxStat)	;;; read on-chip UART receiver status
	and 10h		; data available?
	jp z,CONIN
	in a,(RxData)	; get it in Reg A
;	and 7fh		; console input unfiltered
	ret
CONOUT:
;console char output from reg C
	in a,(TxStat)	;;; transmit empty?
	and 1
	jp z,CONOUT
	ld a,c		; get char to accumulator
	out (TxData),a	; put it out to internal Z280 UART
	ret
	
LIST:
;list char from reg C
	ld a,c		;char to reg a
	ret		;null subroutine
LISTST:
; return list status (0 if not ready, 1 if ready)
	xor a		;0 is always OK to return
	ret
PUNCH:
; punch char from reg C
	ld a,c		;char to reg A
	ret		;null subroutine
READER:
; read char into reg A from reader device
	ld a,1ah		;enter end of file for now
	and 7fh		;remember to strip parity bit
	ret
; i/o drivers for the disk follow
; for now, we will simple store the parameters away in the
; read and write subroutines
HOME:
;move to the track 00 position of current drive
;translate this call into a settrk call with param
;;; track is only 8-bit.  track +1 is not changed
	ld bc,0		;select track 0
	call SETTRK
	ret		;we will move to 00 on first read
SELDSK
; select disk given by register C
;;; Maybe a work around to track limited to 8-bit:
;;;  track+1 denotes disk number for disk bigger than 32meg
	ld hl,0		;error return code
	ld a,c
	ld (diskno),a
	cp 2		;must be between 0 and 2
	ret nc		;no carry if 2,3,...
; disk number is in the proper range
; compute proper disk parameter header address
	ld a,(diskno)
	ld l,a		;l=disk number 0,1
	ld h,0		;high order zero
	add hl,hl		;*2
	add hl,hl		;*4
	add hl,hl		;*8
	add hl,hl		;*16 (size of each header)
	ld de,dpbase
	add hl,de		;hl=.dpbase (diskno*16)
	ret
SETTRK:
; set track given by reg C
;;; track is only 8-bit value.  track+1 is not changed
	ld (track),bc
	ret
SETSEC:
;set sector given by reg C
;	ld a,c
;	ld (sector),a
	ld (sector),bc
	ret
SECTRAN;
;translate the sector given by bc using the 
;translate table given by de
;;; no translate	ex de,hl		;hl=.trans
;;; no translate	add hl,bc		;hl=.trans(sector)
;;; no translate	ld l,(hl)		;l=trans(sector)
;;; no translate	ld h,0		;hl=trans(sector)
	ld h,b		;;; no translation
	ld l,c		;;; copy bc to hl
;	ld (sector),hl	; save value to sector <-- redundant, but accidentally
;			; make the whole thing worked before 16-bit track 
	ret		;with value in hl
SETDMA:
; set dma address given by register B and C
	ld l,c		;low order address
	ld h,b		; high order address
	ld (dmaad),hl	;save the address
	ret
READ:
;perform read operation (usual this is similar ...
;so we will allow space to set up read command...
	push hl		; save regs
	push de
	push bc
	ld c,4		; assuming drive B (RAMdisk) that starts at 0x40000 in RAM
	ld a,(diskno)	; check if RAMdisk
	cp 1
	jp z,RAMDISKr
	ld c,1		; not RAMdisk, so it must be drive A that starts at 0x10000 in RAM
; read RAM disk
; starting address in RAM is 0x40000
; starting address for drive A is 0x10000
;  calculate offset from the base of the page descriptor value
; each track is 1024 sectors of 128 byte/sec = 0x20000
;   shift (track) left by one + shift (MSB sector) right by one
;   shift (LSB sector) right 1bit with carry bit from MSB sector shift

;
;z calculate absolute address from track/sector values
;z
RAMDISKr:
	ld a,(track)	; offset= track*0x20000
	add a,a		; shift left by one
	ld hl,(sector)	; get sector value
	srl h
	rr l		; reg L is A[15..8] of address offset
	add a,h		; reg A is A[23..16] of address offset
;	add a,04h		; the RAMdisk starts from 0x040000
	add a,c		; reg C contains drive offset, 1 for drive A, 4 for drive B
	ld h,a		; HL now has valid A[23..8] into RAM disk
	push hl		;z save for later use
;z use DMA instead of MMU
	call DMAPage
	ld c,DMActrl	; set up DMA master control
	ld hl,0f0e0h	; software ready for dma0&1, no end-of-process, no links
;	outw (c),hl	; write DMA master control reg
	db 0edh,0bfh	; op code for OUTW (C),HL
	ld c,DMA2cnt	; setup count of 128 byte
	ld hl,128
;	outw (c),hl	; write DMA2 count reg
	db 0edh,0bfh	; op code for OUTW (C),HL
	ld c,DMA2srcH	; source is RAMdisk
	pop hl		; get the calcuated RAM disk physical address
	ld a,0fh		; force lowest nibble of DMA2srcH to 0xF
	or l
	ld l,a		; low nibble of L is now 0xF
;	outw (c),hl	; write DMA2 source high reg
	db 0edh,0bfh	; op code for OUTW (C),HL
	xor a		; clear reg A
	ld l,a		; clear the lower half of HL reg
	ld a,(sector)	; calculate address offset within the 4K page
	and 1fh		; only interested in lowest 5 bits, the number of 128-byte sector
	srl a
	rr l		; A+L is now the address offset within the 4K page
	or 0f0h		; force high nibble to 0xF
	ld h,a		; HL now point the address [A11..A0] in RAM disk	
	ld c,DMA2srcL	; A11..A0 of source address
;	outw (c),hl	; write DMA2 source low reg
	db 0edh,0bfh	; op code for OUTW (C),HL
	ld c,DMA2dstH	; destination is what's in dmaad
	ld de,(dmaad)	; get the dmaad in reg de
;;mmu mark the code added when MMU is turned on by ZZ80Mon
;;mmu page 0 is mapped to 0x3C000, DMA moves to physical address, so
;;mmu test for dmaad in 0x0-0xFFF.  If dmaad is in page 0, add 0x3C000 offset to it
	ld a,d		;;mmu
	and 0f0h		;;mmu if highest nibble of dmaad is zero, add offset
	jp nz,notpage0	;;mmu
	ld l,0cfh		;;mmu if page 0, add 0x3C000 offset
	ld h,03h		;;mmu
	jp rdma2hi	;;mmu
notpage0:
	ld h,0		; clear h register
	ld a,0fh		; force lowest nibble of DMA2dstH to 0xF
	or d
	ld l,a		; A23..A16 is zero, A15..A12 is most significant nibble of dmaad
rdma2hi:		
;	outw (c),hl	; write DMA2 destination high reg
	db 0edh,0bfh	; op code for OUTW (C),HL
	ld c,DMA2dstL	;
	ld a,0f0h		; force high nibble of DMA2dstL to 0xFF
	or d
	ld h,a		; move the lower 12 bits of address to destination
	ld l,e
;	outw (c),hl	; write DMA2 destination low reg
	db 0edh,0bfh	; op code for OUTW (C),HL	
	ld hl,8080h	; enable DMA2, burst, byte size, flowthrough, no interrupt
;			;  incrementing memory for source & destination
	ld c,DMA2td	; setup DMA2 transaction descriptor reg
;	outw (c),hl	; write DMA2 transaction description reg
	db 0edh,0bfh	; op code for OUTW (C),HL
;  DMA should start now


	nop
	nop
	call UARTPage	; return page i/o reg to default value
	
	pop bc		;restore reg
	pop de
	pop hl
	ld a,0		; return a OK status
	ret

; write to RAMdisk
; starting address in RAM disk is 0x40000
; starting address for drive A is 0x10000
WRITE:
	push hl		; save reg
	push de
	push bc
	ld c,4		; assuming drive B (RAMdisk) that starts at 0x40000 in RAM
	ld a,(diskno)	; check if RAMdisk
	cp 1
	jp z,RAMDISKw
	ld c,1		; not RAMdisk, so it must be drive A that starts at 0x10000 in RAM

;	call setupRD	; calculate the absolute memory address
;*********************setup RAM Disk ****************************

;z calculate absolute address from track/sector values
;z
RAMDISKw:
	ld a,(track)	; offset= track*0x20000
	add a,a		; shift left by one
	ld hl,(sector)	; get sector value
	srl h
	rr l		; reg L is A[15..8] of address offset
	add a,h		; reg A is A[23..16] of address offset
;	add a,04h		; the RAMdisk starts from 0x040000
	add a,c		; reg C contains drive offset, 1 for drive A, 4 for drive B
	ld h,a		; HL now has valid A[23..8] into RAM disk
	push hl		;z save for later use
;z use DMA instead of MMU
	call DMAPage
	ld c,DMActrl	; set up DMA master control
	ld hl,0f0e0h	; software ready for dma0&1, no end-of-process, no links
;	outw (c),hl	; write DMA master control reg
	db 0edh,0bfh	; op code for OUTW (C),HL
	ld c,DMA3cnt	; setup count of 128 byte
	ld hl,128
;	outw (c),hl	; write DMA3 count reg
	db 0edh,0bfh	; op code for OUTW (C),HL
	ld c,DMA3dstH	; destination is RAMdisk
	pop hl		; get the calcuated RAM disk physical address
	ld a,0fh		; force lowest nibble of DMA3dstH to 0xF
	or l
	ld l,a		; low nibble of L is now 0xF
;	outw (c),hl	; write DMA3 destination high reg
	db 0edh,0bfh	; op code for OUTW (C),HL
	xor a		; clear reg A
	ld l,a		; clear the lower half of HL reg
	ld a,(sector)	; calculate address offset within the 4K page
	and 1fh		; only interested in lowest 5 bits, the number of 128-byte sector
	srl a
	rr l		; A+L is now the address offset within the 4K page
	or 0f0h		; force high nibble to 0xF
	ld h,a		; HL now point the address [A11..A0] in RAM disk	
	ld c,DMA3dstL	; A11..A0 of destination address
;	outw (c),hl	; write DMA3 source low reg
	db 0edh,0bfh	; op code for OUTW (C),HL
	ld c,DMA3srcH	; source is what's in dmaad
	ld de,(dmaad)	; get the dmaad in reg de
;;mmu mark the code added when MMU is turned on by ZZ80Mon
;;mmu page 0 is mapped to 0x3C000, DMA moves to physical address, so
;;mmu test for dmaad in 0x0-0xFFF.  If dmaad is in page 0, add 0x3C000 offset to it
	ld a,d		;;mmu
	and 0f0h		;;mmu if highest nibble of dmaad is zero, add offset
	jp nz,notwpage0	;;mmu
	ld l,0cfh		;;mmu if page 0, add 0x3C000 offset
	ld h,03h		;;mmu
	jp wdma2hi	;;mmu
notwpage0:
	ld h,0		; clear h register
	ld a,0fh		; force lowest nibble of DMA3dstH to 0xF
	or d
	ld l,a		; A23..A16 is zero, A15..A12 is most significant nibble of dmaad
wdma2hi		
;	outw (c),hl	; write DMA3 source high reg
	db 0edh,0bfh	; op code for OUTW (C),HL
	ld c,DMA3srcL	;
	ld a,0f0h		; force high nibble of DMA3srcL to 0xFF
	or d
	ld h,a		; move the lower 12 bits of address to source reg
	ld l,e
;	outw (c),hl	; write DMA3 source low reg
	db 0edh,0bfh	; op code for OUTW (C),HL	
	ld hl,8080h	; enable DMA3, burst, byte size, flowthrough, no interrupt
;			;  incrementing memory for source & destination
	ld c,DMA3td	; setup DMA3 transaction descriptor reg
;	outw (c),hl	; write DMA3 transaction description reg
	db 0edh,0bfh	; op code for OUTW (C),HL
;  DMA should start now

	nop
	nop
	call UARTPage	; return page i/o reg to default value

	
	pop bc		;restore reg
	pop de
	pop hl
	ld a,0		; return a OK status
	ret



; initialize MMU page descriptors
;initMMU:
;	push hl		; save register
;	push bc
;	ld a,0ah		; V/WP/C/M bits are 1/0/1/0
;	ld hl,MMUtbl	;   valid, not write protect, cacheable
;initMMUp
;	ld (hl),a		; store page descriptor value to table
;	inc hl		; MSB of page descriptor
;	ld (hl),0		; transparent mapping
;	inc hl
;	add 10h		; point to next page descriptor
;	cp 0ah		; end of table?
;	jp nz,initMMUp
;	xor a		; clear reg A 
;	out (MMUptr),a	; point to this page (page 0x0), user page descriptors	
;	ld hl,MMUtbl	; block move MMU page descriptors
;	ld c,MMUmove	; points to MMU block move port
;	ld b,16		; move 16 pages
;	db 0edh,93h	; op code for otirw, output word and increment
;	otirw
;	ld a,10h		; repeat the block move for system page descriptors
;	out (MMUptr),a	; point to this page (page 0x0), user page descriptors	
;	ld hl,MMUtbl	; block move MMU page descriptors
;	ld c,MMUmove	; points to MMU block move port
;	ld b,16		; move 16 pages
;	db 0edh,93h	; op code for otirw, output word and increment
;	otirw
;	ld c,MMUctrl	; point to MMU master control register
;	ld hl,0bbffh	; enable user & system  translate
;	db 0edh,0bfh	; op code for OUTW (C),HL
;	outw (c),hl	; turn on MMU
;	pop bc		; restore register
;	pop hl
;	ret

;;; init Z280 page i/o reg to point to UART
UARTPage:
	push bc		; save register
	push hl
	ld c,08h		; reg c points to I/O page register
	ld l,0feh		; set I/O page register to 0xFE
	db 0edh,6eh	; this is the op code for LDCTL (C),HL
	pop hl		; restore reg
	pop bc
	ret

;;; init Z280 page i/o reg to point to compactflash
CFPage:
	push bc		; save register
	push hl
	ld c,08h		; reg c points to I/O page register
	ld l,0		; set I/O page register to 0
	db 0edh,6eh	; this is the op code for LDCTL (C),HL
;	ldctl (c),hl	; write to I/O page register
	pop hl		; restore reg
	pop bc
	ret
;;; initialize Z280 page i/o reg to point to MMU * DMA
MMUPage:
DMAPage:
	push bc		; save reg
	push hl
	ld c,8		; reg C points to I/O page reg
	ld l,0ffh		; MMU page I/O reg is 0xFF
	db 0edh,6eh	; op code for LDCTL (C),HL
	pop hl
	pop bc		; restore reg
	ret

;*********************diagnostic uses ********** remove when done ***********
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;ADROUT -- Print an address to the console
;
;pre: HL pair contains address to print
;post: HL printed to console as hex
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ADROUT: 	LD A,H
        	CALL HEXOUT
        	LD A,L
        	CALL HEXOUT
        	RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;HEXOUT -- Output byte to console as hex
;
;pre: A register contains byte to be output
;post: byte is output to console as hex
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
HEXOUT: 	PUSH BC
        	LD B,A
        	RRCA
        	RRCA
        	RRCA
        	RRCA
        	AND 0Fh
        	CALL HEXASC
	ld c,a
        	CALL CONOUT
        	LD A,B
        	AND 0Fh
        	CALL HEXASC
	ld c,a
        	CALL CONOUT
        	POP BC
        	RET
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;HEXASC -- Convert nybble to ASCII char
;
;pre: A register contains nybble
;post: A register contains ASCII char
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
HEXASC: 	ADD 90h
        	DAA
        	ADC A,40h
        	DAA
        	RET
;*********************diagnostic uses ********** remove when done ***********
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;STROUT -- Print a null-terminated string
;
;pre: HL contains pointer to start of a null-
;     terminated string
;post: string at HL printed to console
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
STROUT: 	LD C,(HL)
        	CP 00
        	RET Z
        	CALL CONOUT
        	INC HL
        	JP STROUT

; wait UART transmit done before return
COUTdone:
	in a,(TxStat)	; trasmit empty?
	and 1
	jp z,COUTdone	; wait until transmit empty
	ret

signon:	
	db	"Copyright"
	db	" 1979 (c) by Digital Research      ",13,10
	db "CP/M 2.2 for ZZ80RC", 13,10
        	db "8/12/18 v1.1",13,10,0 

scratch	ds 1
	org 0f800h	; fix location for BIOS variables
;the remainder of the cbios is reserved uninitializ...
;data area, and does not need to be a part of the...
;system memory image (the space must be available,....
;however, between "begdat" and "enddat").
track:	ds 2		;two bytes for expansion
sector:	ds 2		;two bytes for expansion
dmaad:	ds 2		;direct memory address
diskno	ds 1		;disk number 0-15
;CFLA07	ds 1		;;;CF LA address, LSB
;CFLA815	ds 1
;CFLA1623	ds 1		;;;CF LA address, MSB

;CFsecdat0	ds 128		;;;CF sector data, 512 bytes
;CFsecdat1 ds 128
;CFsecdat2 ds 128
;CFsecdat3	ds 128
; scratch ram area for bdos use
begdat 	equ $		;beginning of data area
dirbf: 	ds 128		;scratch directory area
;all00: 	ds 31		;allocation vector 0-3
;all01: 	ds 31
;all02:	ds 31
;all03:	ds 31
all00:	ds 32
all01:	ds 32
;all02:	ds 256
;all03:	ds 256
;all04:	ds 256
;chk00:	ds 16		;check vector 0-3
;chk01:	ds 16
;chk02:	ds 16
;chk03:	ds 16
chk00:	ds 0
chk01:	ds 0
;chk02:	ds 0
;chk03:	ds 0
;chk04:	ds 0
MMUtbl	ds 32		; 16 pages of system MMU page descriptors
enddat	equ $		;end of data area
datsiz	equ $-begdat	;size of data area
	end

