
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*								*
*	Copyright (C) 1984,85,86  AMPRO COMPUTERS, INC.		*
*								*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

; Assemble with asm.com or equivalent.
; All Z80 opcodes are defined with DB or DW statements.


; Revision history:
;
;  Ver	Date	Who	Description
;  ---	-----	---	--------------------------------------------
;  1.0	E7.15	RJB	Original version
;  1.1  F1.30   RBL	Now includes Shugart controller option
;  1.2  F4.04	RBL	Now includes Seagate 225N

; Program version, and current version date

VERS		EQU	12	; Current version
THIS$MONTH	EQU	4	; Today's month
THIS$DAY	EQU	04	; .       day
THIS$YEAR	EQU	86	; .       year

INT$REV		EQU	99	; Internal revision number


; TRUE and FALSE are defined here

NO		EQU	0
FALSE		EQU	0
YES		EQU	NOT FALSE
TRUE		EQU	NOT FALSE


; Operating characteristics

MIN$VERSION	EQU	20	; Minimum bios version allowed
CMD$LINE$OK	EQU	TRUE	; Allow command line input?
RTN$VIA$WB	EQU	TRUE	; Return to CP/M via warm boot?

INTERNAL	EQU	FALSE	; Internal (unreleased) revision?


; Include diagnostic messages?

TEST		EQU	NO	; TEST diagnostics?


; Current screen width and output buffer width

SWIDTH		EQU	80	; Screen width
PWIDTH		EQU	132	; Output buffer width


; Z-80 opcode equates (reversed so we can use a DW to enter them)

CPIR80		EQU	0B1EDH		; CPIR
INIR80		EQU	0B2EDH		; INIR
LDIR80		EQU	0B0EDH		; LDIR
OTIR80		EQU	0B3EDH		; OTIR

INI80		EQU	0A2EDH		; INI 
OUTI80		EQU	0A3EDH		; OUTI

SBCD80		EQU	043EDH		; LD	(dddd),BC
LBCD80		EQU	04BEDH		; LD	BC,(dddd)

SDED80		EQU	053EDH		; LD	(dddd),DE
LDED80		EQU	05BEDH		; LD	DE,(dddd)

SSPD80		EQU	073EDH		; LD	(dddd),SP
LSPD80		EQU	07BEDH		; LD	SP,(dddd)

LXIX		EQU	021DDH		; LXI	IX,dddd
SIXD80		EQU	022DDH		; LD	(dddd),IX
LIXD80		EQU	02ADDH		; LD	IX,(dddd)
POPIX		EQU	0E1DDH		; POP	IX
PUSHIX		EQU	0E5DDH		; PUSH	IX

LXIY		EQU	021DDH		; LXI	IY,dddd
SIYD80		EQU	022FDH		; LD	(dddd),IY
LIYD80		EQU	02AFDH		; LD	IY,(dddd)
POPIY		EQU	0E1FDH		; POP	IY
PUSHIY		EQU	0E5FDH		; PUSH	IY

; Bit SET/RESET/TEST Z-80 opcode equates (use DB to enter)
; Example: SET 7,D would be DB BIT,BSET+B7+ZD

BIT		EQU	0CBH		; Bit prefix

BTST		EQU	040H		; Bit test
BRES		EQU	080H		; Bit reset
BSET		EQU	0C0H		; Bit set

B0		EQU	000H		; Bit 0
B1		EQU	008H		; Bit 1
B2		EQU	010H		; Bit 2
B3		EQU	018H		; Bit 3
B4		EQU	020H		; Bit 4
B5		EQU	028H		; Bit 5
B6		EQU	030H		; Bit 6
B7		EQU	038H		; Bit 7

ZB		EQU	000H		; B Reg
ZC		EQU	001H		; C Reg
ZD		EQU	002H		; D Reg
ZE		EQU	003H		; E Reg
ZH		EQU	004H		; H Reg
ZL		EQU	005H		; L Reg
ZM		EQU	006H		; M Reg
ZA		EQU	007H		; A Reg

; Jump relative opcode equates (use DB to enter)
; Example: JR AGAIN would be DB JR,AGAIN-$-1

JR		EQU	018H		; JR addr
JRNZ		EQU	020H		; JR NZ,addr
JRZ		EQU	028H		; JR Z,addr
JRNC		EQU	030H		; JR NC,addr
JRC		EQU	038H		; JR C,addr

; IX and IY prefixes (use DB to enter)

IX		EQU	0DDH		; IX prefix
IY		EQU	0FDH		; IY prefix


; Character equates

CTRLC		EQU	'C'-'@'		; Ctrl-C (ETX)
BS		EQU	'H'-'@'		; Ctrl-H (Backspace)
TAB		EQU	'I'-'@'		; Ctrl-I (Tab)
LF		EQU	'J'-'@'		; Ctrl-J (Line feed)
CR		EQU	'M'-'@'		; Ctrl-M (Carriage return)
NAK		EQU	'U'-'@'		; Ctrl-U
CAN		EQU	'X'-'@'		; Ctrl-X (Cancel)
EOF		EQU	'Z'-'@'		; Ctrl-Z (CP/M End-of-file)
ESC		EQU	1BH		; Ctrl-[ (Escape)
EOS		EQU	'$'		;	 (CP/M End-of-string)
DEL		EQU	7FH		;	 (Delete)


; bdos equates

BDOS	EQU	5		; bdos entry


;	*	*	*	*	*	*	*	*
;
;	The code starts here ...
;
;	*	*	*	*	*	*	*	*

	ORG	0100H
	JMP	START

SCREEN$WIDTH:	DB	SWIDTH-1	; 1 less than actual #
SLOW$TERM:	DB	10		; Delay (ms) for slow term
CMD$LINE$CHRS:	DB	0		; # of cmd line chrs left
CMD$LINE$PTR:	DW	0		; Ptr to next cmd line chr

NAME$MSG:
	DB	0DH,'Ampro '
NAME:	DB	'PARK'		; <-- Insert name here
NAMELN:	EQU	$-NAME
	DB	' Utility',CR,LF
	DB	'Copyright (C) 1985 AMPRO Computers, Inc.',CR,LF
	DB	'Version ',VERS/10+'0','.',VERS MOD 10+'0'
	IF	INTERNAL	; Display internal revision number?
	DB	'x',INT$REV/10+'0',INT$REV MOD 10+'0'
	DB	'  [',THIS$YEAR-80+'@'
	DB	THIS$MONTH+'0'+((THIS$MONTH/10)*7)
	DB	'.',THIS$DAY/10+'0',THIS$DAY MOD 10+'0',']'
	ENDIF
	DB	CR,LF,LF,'$',CR
HELP$MSG:
	DB	'To park a hard disk drive:',CR,LF,LF
	DB	TAB,'PARK scsiaddr logicalunit controller [blocknumber],.'
	DB	CR,LF,LF
	DB	'Where ''controller'' is one of the following:',CR,LF,LF
	DB	TAB
	DB	'G - Generic SCSI Controller  ',CR,LF
	DB	'    (Adaptec, Seagate 225N, Shugart 1610-4, etc.)  ',CR,LF
	DB	'O - Xebec Owl   '
	DB	'# - all other controllers ',CR,LF,LF
	DB	'$',CR,' ',CR,EOF

SIGNON$MSG:
	DB	'The PARK utility is used to move the heads of a '
	DB	'hard disk drive to the landing or shipping zone.  '
	DB	CR,LF,LF
	DB	'NOTE:  This program will NOT return to CP/M when '
	DB	'you terminate the parking session with an escape or '
	DB	'control-c.'
	DB	CR,LF,LF
	DB	'$'

;
; Initialize the command line input pointer.
;

START: 
	IF	CMD$LINE$OK	; If we want to allow cmd line input,
	LXI	H,0080H		; .  Save the command line.
	LXI	D,INBUF		; .  .
	LXI	B,128		; .  .
	DW	LDIR80		; .  .
	LXI	H,INBUF		; .  Set up ptrs and count of chrs
	MOV	A,M		; .  .
	STA	CMD$LINE$CHRS	; .  Save count of characters,
	INX	H		; .  Bump line ptr,
	SHLD	CMD$LINE$PTR	; .  .  and save ptr to cmd line
	ENDIF
	IF	NOT CMD$LINE$OK	; If no command line input allowed,
	MVI	A,0		; .  Clear the count of characters,
	STA	CMD$LINE$CHRS	; .  .
	LXI	H,0		; .  but set the ptr up anyway.
	SHLD	CMD$LINE$PTR	; .  .
	ENDIF

	IF	NOT RTN$VIA$WB	; If we're returning without warm boot,
	LXI	H,0000H		; .  then get the old stack ptr,
	DAD	SP		; .  and
	SHLD	STACK		; .  save it for later,
	ENDIF

;
; This is where you can jump to start the program over.
;

TOP$MENU:
	LXI	SP,STACK	; Stuff SP with our stack.
	CALL	GET$BIOS$VERS	; Copy the jmp tbl to a local area

;
; Display the name and signon message
;

	IF	CMD$LINE$OK	; If cmd line input is possible 
	LDA	CMD$LINE$CHRS	; Check for any input and skip
	ORA	A		; .  the initial messages if any
	JNZ	CHECK$B$VERS	; .  chrs in the cmd line.
	ENDIF

	CALL	CLEAR$SCREEN	; Clear the screen
	LXI	D,NAME$MSG	; Display the name, version, etc.
	CALL	CENTER$OUTPUT	; .
	LXI	D,SIGNON$MSG	; and the initial message
	CALL	JUSTIFY		; .

;
; Check the version of the bios against the minimum version allowed.
; If the bios is not at least the minimum, display an error message
; and exit to the operating system.
;

CHECK$B$VERS:
	LDA	BIOS$VERSION	; Get bios version #
	CPI	MIN$VERSION	; Check against minimum version
	JNC	WHICH$BIOS	; At least minimum version . . .
	LXI	D,BIOS$PLUS	; Not minimum, display error message
	CALL	JUSTIFY		; .
	JMP	LB$WBOOT	; and exit.

BIOS$PLUS:
	DB	'This program requires AMPRO bios version '
VERSION$REQ:
	DB	MIN$VERSION/10,'.',MIN$VERSION MOD 10,' or later.'
	DB	CR,LF,'$'

;
; Perform any initialization particular to each version of the Ampro
; bios, if necessary.
;

WHICH$BIOS:
	LDA	BIOS$VERSION	; Get current bios version

	CPI	30		; Is it version 3?
	DB	JRC,CHECK2-$-1	; No, check for version 2

	; Put version 3 init code here ...

CHECK2:
	CPI	20		; Is it version 2?
	DB	JRC,AGAIN-$-1	; No, assume version 1

	; Put version 2 init code here ...

AGAIN:
;
;  Program main loop . . .
;
	LXI	H,0		; Set block to zero
	SHLD	SCSI$BLOCK	; .
	SHLD	SCSI$BLOCK+2	; .

	LXI	D,S$ADDR$MSG	; Get scsi address
	LXI	H,S$ADDR$OKC	; .
	CALL	PROMPT		; .
	JZ	ALL$DONE	; .
	STA	RESULT$ID	; Plug id in messages
	STA	NO$UNITS$ID	; .
	MOV	A,B		; .
	DCR	A		; .
	ANI	07H		; Convert binary to SCSI ID
	INR	A		; .
	MOV	B,A		; .
	XRA	A		; .
	STC			; .
NEXT$BIT:
	RAL			; .
	DCR	B		; .
	JNZ	NEXT$BIT	; .
	STA	SCSI$ADDR	; .

	LXI	D,L$UNIT$MSG	; Get logical unit #
	LXI	H,L$UNIT$OKC	; .
	CALL	PROMPT		; .
	JZ	AGAIN		; .
	STA	RESULT$LUN	; .
	STA	NO$UNITS$UNIT	; .
	MOV	A,B		; .
	DCR	A		; Convert unit # to 0-3
	RRC			; Rotate to bits 7,6,5
	RRC			; .
	RRC			; .
	STA	DRV$LUN		; .

	LXI	D,HDC$MSG	; Get controller
	LXI	H,HDC$OKC	; .
	CALL	PROMPT		; .
	JZ	AGAIN		; .
	MOV	A,B		; .
	DCR	A		; Check for block number (#)
	STA	CONTROLLER	; .  (save controller anyway)
	JZ	GET$BLOCK	; .

	LXI	D,RET$TO$PARK	; Accept <RETURN> on other
	LXI	H,RET$TO$PARK$C	; . controllers
	CALL	PROMPT		; .
	JMP	SKIP$BLOCK	; .

GET$BLOCK:
	LXI	D,BLOCK$MSG	; Get block to park on
	CALL	PROMPT$DECIMAL	; .
	JZ	AGAIN		; .
	LXI	H,SCSI$BLOCK	; Save block in buffer area
	MOV	M,B		; .
	INX	H		; .
	MOV	M,C		; .
	INX	H		; .
	MOV	M,D		; .
	INX	H		; .
	MOV	M,E		; .
SKIP$BLOCK:

	CALL	PARK		; Perform park
	JMP	AGAIN		; Go back for more ...

ALL$DONE:			; Don't return to CP/M
	LXI	D,ALL$PARKED	; Give 'all parked message'
	CALL	CENTER$OUTPUT	; .
LOONY$TUNES:
	JMP	LOONY$TUNES	; Th-th-th-that's all, folks!


* * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*							*
*	      General purpose routines . . .		*
*							*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * *


DO$SCSI:
	INX	H
	LDA	DRV$LUN
	ORA	M
	MOV	M,A
	DCX	H
	LDA	SCSI$ADDR
	CALL	LB$SCSIDRV
	ORA	A
	RET


PARK:
	MVI	A,0FFH		; Set "No response" flag
	STA	DRV$RESPONSE	; .
	LXI	H,HDC$COMMAND	; Clear HDC command bytes
	LXI	D,HDC$COMMAND+1	; .
	LXI	B,9		; .
	MVI	M,0		; .
	DW	LDIR80		; .

	LDA	CONTROLLER	; Init HDC for this drive
	LXI	H,PARK$EXE	; .
	JMP	GO$TABLE	; .

PARK$EXE:
	DW	PARK$AT$BLOCK	; Park at block #
	DW	PARK$OWL	; Xebec Owl
	DW	PARK$GENERIC	; PARK Generic SCSI drive
	DW	PARK$GENERIC	; PARK Generic SCSI drive (Old Adaptec letter)
	DW	PARK$GENERIC	; PARK Generic SCSI drive (Old Shugart letter)


PARK$AT$BLOCK:
	LXI	H,SCSI$BLOCK+1	; Move block number into cmd
	LXI	D,HDC$COMMAND+1	; .
	LXI	B,3		; .
	DW	LDIR80		; .
	MVI	A,0BH		; Set seek command
	JMP	PERFORM$CMD	; Do it

PARK$GENERIC:
	MVI	A,1BH		; Set park command
	JMP	PERFORM$CMD	; Do it

PARK$OWL:
	MVI	A,011H		; Set seek command
	CALL	PERFORM$CMD	; Do it
	LXI	H,BLINK$LED	; Set blink LED command
	JMP	DO$SCSI		; .
BLINK$LED:
	DB	0EDH,0,0,0,0,0	; Blink LED SCSI cmd


PERFORM$CMD:
	LXI	H,HDC$COMMAND	; Point to park command
	MOV	M,A		; .  (plug cmd)
	LXI	D,HEAP		; Set DE to dummy buffer
	CALL	DO$SCSI		; Execute park command
	DB	JRNZ,NEXT$UNIT-$-1
	LXI	D,PARK$RESULT	; Display result message
	CALL	CENTER$OUTPUT	; .
	XRA	A		; Indicate that a unit responded
	STA	DRV$RESPONSE	; .

NEXT$UNIT:
	LDA	DRV$RESPONSE	; See if any units responded
	ORA	A		; .
	LXI	D,NO$UNITS	; .
	CNZ	CENTER$OUTPUT	; .
	RET

HDC$COMMAND:
	DB	0,0,0,0,0,0,0,0,0,0


* * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*							*
*	      	Various messages . . .			*
*							*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * *


S$ADDR$MSG:
	DB	CR,LF
	DB	'What is the SCSI address of your controller '
	DB	'(0-7)? ','$'
S$ADDR$OKC:
	DB	'01234567',0

L$UNIT$MSG:
	DB	CR,LF
	DB	'What is the logical unit number of the drive '
	DB	'you wish to park (0-3)? ','$'
L$UNIT$OKC:
	DB	'0123',0	; Logical unit numbers

HDC$MSG:
	DB	CR,LF
	DB	'Of the following list of controllers:',CR,LF
	DB	TAB,'G - Generic SCSI controller',CR,LF
	DB	TAB,'    (Adaptec, Seagate 225N, Shugart 1610-4, etc.)',CR,LF
	DB	TAB,'O - Xebec Owl',CR,LF
	DB	TAB,'# - Any other hard disk controller',CR,LF
	DB	'Which controller are you using (A,O,S, or #)? ','$'
HDC$OKC:
	DB	'#OGAS',0

BLOCK$MSG:
	DB	CR,LF
	DB	'Park the drive at which block address? ','$'

RET$TO$PARK:
	DB	CR,LF
	DB	'Press the <RETURN> key to park this drive: ','$'
RET$TO$PARK$C:
	DB	CR,'0'

SCSI$BLOCK:
	DW	0,0

UNIT$ID:
	DB	0

CONTROLLER:
	DB	0

DRV$RESPONSE:
	DB	0


PARK$RESULT:
	DB	'>>> SCSI ID '
RESULT$ID:
	DB	'x, Logical unit '
RESULT$LUN:
	DB	'x parked. <<<',CR,LF,'$'

NO$UNITS:
	DB	'>>> SCSI ID '
NO$UNITS$ID:
	DB	'x, Logical unit '
NO$UNITS$UNIT:
	DB	'x did not respond. <<<',CR,LF,'$'

ALL$PARKED:
	DB	CR,LF,LF,'>>> You may now turn off '
	DB	'your system. <<<',CR,LF,'$'


* * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*							*
*		Library routines . . .			*
*							*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * *


CENTER$OUTPUT:
;
; [DC.20]
;
; Automatically centers the output line(s) based on the line width
; stored in SCREEN$WIDTH.  Each line is delimited with CR+LF.  This
; routine will return to the caller when it encounters the string
; terminator, '$'.  Any additional LF characters after a CR+LF pair
; will be passed through.
;
; Entry:
;	DE = Pointer to output string(s), terminated with CR+LF.
;
; Exit:
;	The output string(s) are sent to the screen
;
; Modifies: DE
;
	PUSH	PSW		; Save registers
	PUSH	B		; .
	PUSH	H		; .
CENTER$NEXT$LN:
	CALL	GET$STRLEN	; Get length to next CR or '$' in B
	MOV	A,B		; Check for zero length
	ORA	A		; .
	JZ	NEXT$DELIM	; If so, output the CR, LF, etc.
	LDA	SCREEN$WIDTH	; Compute offset needed to center line
	STC			; .
	SBB	B		; .  (if there are too many chrs, just
	JC	NO$BLANKS	; .   print the line as is . . . )
	ANI	0FEh		; Clear least significant bit
	RRC			; .  and rotate to divide by two
	MVI	C,' '		; Output enough blanks to center line
	CNZ	CON$CHR$AC	; .  (only if count is non-zero)
NO$BLANKS:
	MOV	A,M		; Save CR for later
	MVI	M,'$'		; Plug position with '$' for cp/m
	CALL	CON$MSG		; .  print string function
	MOV	M,A		; Restore saved CR
NEXT$DELIM:
	MOV	A,M		; Get character
	CPI	CR		; Print it if CR
	JZ	OUTPUT$DELIM	; .
	CPI	LF		; Print it if LF
	JZ	OUTPUT$DELIM	; .
	CPI	EOS		; Stop processing if EOS ('$')
	JZ	CENTER$DONE	; .
	XCHG			; Put new pointer in DE
	JMP	CENTER$NEXT$LN	; Go & do the next line
OUTPUT$DELIM:
	CALL	CON$CHR		; Output delimiter
	CPI	LF		; Wait 10ms if we have a line feed
	CZ	WAIT		; .
	INX	H		; Point to next chr
	JMP	NEXT$DELIM	; and check that one, also
CENTER$DONE:
	POP	H		; Restore registers
	POP	B		; .
	POP	PSW		; .
	RET			; and return


CLEAR$SCREEN:
;
; [E1.28]
;
; This routine clears the screen by calling DO$CRLF 26 times.
;
; Entry:
;	None
;
; Exit:
;	The screen is cleared
;
; Modifies:
;	None
;
	PUSH	PSW		; Save just in case
	MVI	A,26		; 26 CRLF's
C$NEXT$LINE:
	CALL	DO$CRLF		; Next line
	DCR	A		; Done?
	JNZ	C$NEXT$LINE	; Nope.
	POP	PSW		; Restore original AF
	RET			; and return


CON$CHR:
;
; [DC.20]
;
; This routine sends the character in the "A" register to the console
; through the BDOS conout call.
;
; Entry:
;	A  = character to send
;
; Exit:
;	character is sent to the console
;
; Modifies:
;	None
;
	PUSH	PSW
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,2
	MOV	E,A
	CALL	BDOS
	POP	H
	POP	D
	POP	B
	POP	PSW
	RET


CON$CHR$AC:
;
; [DC.20]
;
; This routine sends the character in the C register to the console
; the number of times in the A register.
;
; Entry:
;	A  = Number of times to send character
;	C  = Character to send
;
; Exit:
;	Same
;
; Modifies:
;	None
;
	PUSH	PSW		; Save all registers
	PUSH	B		; .
	PUSH	D		; .
	PUSH	H		; .
	MOV	B,A		; Move data to accomodate CP/M
	MOV	A,C		; .
NEXT$CHR$OUT:
	CALL	CON$CHR		; Send 1 chr
	DCR	B		; Decrement counter
	JNZ	NEXT$CHR$OUT	; Done?
	POP	H		; Restore all registers
	POP	D		; .
	POP	B		; .
	POP	PSW		; .
	RET			; and return


CONIN$NE$XC:
;
; [E1.28]
;
; Console input, no echo, exit on ctrl-c
;
; Entry:
;	none
;
; Exit:
;	A  = character from console, except for ctrl-c, which causes
;	     an immediate jump to ALL$DONE
;
; Modifies:
;	A
;
	CALL	LB$CONIN
	CPI	CTRLC
	JZ	ALL$DONE
	RET


CON$MSG:
;
; [DC.20]
;
; Console message 
;
; Entry:
;	DE = pointer to message string, terminated with '$'
;
; Exit:
;	message printed on console
;
; Modifies: A, BC
;
	PUSH	PSW		; Save registers
	PUSH	B		; .
	PUSH	D		; .
	PUSH	H		; .
	MVI	C,9		; BDOS print string command
	CALL	BDOS		; .
	POP	H		; Restore registers
	POP	D		; .
	POP	B		; .
	POP	PSW		; .
	RET			; and return


DISPLAY$FDEV:
;
; [E2.15]
;
;
	LXI	D,D$FDEV$HDR	; Print header
	CALL	CENTER$OUTPUT	; .

	LXI	H,ACTIVE$FDEV	; Get pointer to active floppies
	LXI	B,0004H		; Set starting and maximum #

D$NEXT$FDEV:
	MOV	A,M		; Get unit #
	ORA	A		; If zero, skip to next
	JZ	D$BUMP$PTR	; .

	PUSH	H		; Copy output line to output buffer
	PUSH	B		; .
	LXI	H,D$FDEV$LIN	; .
	LXI	D,OUTBUF	; .
	LXI	B,D$FDEV$HLEN	; .
	DW	LDIR80		; .
	POP	B		; .
	POP	H		; .

	PUSH	PSW		; update 'current' drive letter
	ANI	07FH		; .  (mask out 'E-disk' bit)
	STA	OUTBUF+D$CURRENT; .
	POP	PSW		; .
	ANI	080H		; get 'E-disk' bit back
	JNZ	D$EDISK$OK	; 'E-disk' present, leave E: alone
	PUSH	H		; .
	PUSH	B		; .
	LXI	H,D$EBLANK	; .
	LXI	D,OUTBUF+D$EDISK; .
	LXI	B,4		; .
	DW	LDIR80		; .
	POP	B		; .
	POP	H		; .

D$EDISK$OK:
	PUSH	B		;
	PUSH	H		;
	MOV	A,B		; update floppy device number
	LXI	H,FNAMES	; .
	LXI	B,FNAMES$LEN	; .
	CALL	INDEX$TABLE	; .
	LXI	D,OUTBUF+D$FNAME; .
	LXI	B,FNAMES$LEN	; .
	DW	LDIR80		; .
	POP	H		; .
	POP	B		; .
	LXI	D,OUTBUF	; and output the line
	CALL	CENTER$OUTPUT	; .

D$BUMP$PTR:
	INX	H		; Bump pointer to next device,
	INR	B		; increment letter count,
	MOV	A,B		; Compare to maximum,
	CMP	C		; .
	JNZ	D$NEXT$FDEV	; Do another if we're not done.
	RET

D$FDEV$HDR:	DB	'- FLOPPY DISK ASSIGNMENTS -',CR,LF
		DB	'CP/M drive   '
		DB	'Floppy disk',CR,LF
		DB	'------------------------',CR,LF,'$'

D$FDEV$LIN:
		DB	' '
D$CURRENT:	EQU	$-D$FDEV$LIN
		DB	'x  '
D$EDISK:	EQU	$-D$FDEV$LIN
		DB	'(E)'
D$EBLANK:	DB	'         '
D$FNAME:	EQU	$-D$FDEV$LIN
		DB	'        '
		DB	CR,LF,'$'
D$FDEV$HLEN	EQU	$-D$FDEV$LIN	; Line length

FNAMES:		DB	'First '
		DB	'Second'
		DB	'Third '
		DB	'Fourth'
FNAMES$LEN:	EQU	6

; end of DISPLAY$FDEV


DISPLAY$SDEV:
;
; [E2.15]
;
; Display the hard disk data in the active device table.
;
; Entry
;	Active devices in the ACTIVE$SDEV area
;
; Exit
;	Devices are displayed on the console
;
; Modifies
;	All
;
	LDA	TOTAL$ACTIVE	; Check how many devices
	ORA	A		; .
	RZ			; Return if no devices active

	LXI	D,FD$HEADER	; Display table header
	CALL	CENTER$OUTPUT	; .

	LXI	D,LAST$SCSI	; Init local data
	LXI	B,04FFH		; . (fill last info with 0FFh's)
	CALL	FILL$BLOCK	; .
	XRA	A		; Current logical

D$GET$UNIT:
	STA	LOGICAL$UNIT	; save current unit #
	CALL	INDEX$ACTIVE	; HL = address of active table entry

	LXI	D,LAST$SCSI	; See if same device
	MVI	B,4		; Compare for 4 bytes
	CALL	STR$COMP	; .
	JZ	D$ADD$LETTER	; Same device -- add CP/M letter

	LDAX	D		; Not same device, check to see
	ORA	A		; .  if last device was 'FF'.  If so,
	JM	D$DONT$DISP	; .  don't display the line.

	CALL	D$SEND$LINE	; Send line to console

D$DONT$DISP:
	LXI	B,4		; Since DE & HL still point to last & 
	DW	LDIR80		; .  current, move current to last.

	PUSH	H		; save ptr
	LXI	H,DRIVE$INFO	; Copy template to output buffer
	LXI	D,OUTBUF	; .
	LXI	B,DRIVE$LEN	; .
	DW	LDIR80		; .

	LXI	H,OUTBUF+DRIVE$LETTERS	; Setup CP/M letter ptr
	SHLD	CPM$PTR		; .
	POP	H		; Restore ptr to current
	PUSH	H		; . and save it back

	MOV	A,M		; Plug output buffer w/device info
	CALL	SCSI$TO$BIN	; SCSI address
	ADI	'0'		; .  (convert to ascii 0-7)
	STA	OUTBUF+DRIVE$ADDRESS	; .
	INX	H		; .

	MOV	A,M		; Controller type
	PUSH	H		; .
	LXI	H,CT$NAME	; .
	LXI	B,CT$NLEN	; .
	CALL	INDEX$TABLE	; .

	LXI	D,OUTBUF+DRIVE$CTRL	; .
	DW	LDIR80		; .
	POP	H		; .
	INX	H		; .
	INX	H

	MOV	A,M		; Logical unit #
	RLC			; .  (move bits 7-5 to 2-0) 
	RLC			; .
	RLC			; .
	ANI	07H		; .  (mask out other bits)
	ADI	'0'		; .  (convert to ascii 0-7)
	STA	OUTBUF+DRIVE$UNIT	; .

	POP	H		; Restore ptr so we agree
D$ADD$LETTER:
	INX	H		; Get CP/M letter for this device
	INX	H		; .
	INX	H		; .
	INX	H		; .
	INX	H		; .
	MOV	B,M		; .
	LHLD	CPM$PTR		; Get ptr to CP/M letter area
	MOV	M,B		; Save CP/M drive letter
	INX	H		; Bump pointer
	INX	H		; .
	SHLD	CPM$PTR		; Save CP/M letter area pointer back

D$NEXT$UNIT:
	LDA	TOTAL$ACTIVE	; Get count of active units
	MOV	B,A		; Save for a moment
	LDA	LOGICAL$UNIT	; Get unit we're working on
	INR	A		; Bump to next
	CMP	B		; Are we done?
	JNZ	D$GET$UNIT	; No -- go do another

	CALL	D$SEND$LINE	; Send this line out, too.
	CALL	DO$CRLF		; Send out an extra CR+LF.

	RET			; all done

D$SEND$LINE:
	PUSH	PSW		; Save A & flags and
	PUSH	D		; .  D just in case
	LXI	D,OUTBUF	; Send line to console
	CALL	CENTER$OUTPUT	; .
D$SEND$DONE:
	POP	D		;
	POP	PSW
	RET

LOGICAL$UNIT:	DB	0	; Current logical unit
LAST$SCSI:	DB	0	; Last hard disk scsi address
LAST$CTRL:	DB	0	; Last hard disk controller
LAST$DTYPE:	DB	0	; Last hard disk drive type
LAST$DRIVE:	DB	0	; Last hard disk drive
CPM$PTR:	DW	0	; Pointer to next CP/M letter pos

CT$NAME:	DB	'Xebec          '
		DB	'Adaptec        '
;		DB	'Data Technology'	; Supported later
;		DB	'Western Digital'	; Supported later
		DB	'UNKNOWN        '
CT$UN:		DB	'UNKNOWN        '
CT$NLEN:	EQU	$-CT$UN

FD$HEADER:	DB	'- HARD DISK ASSIGNMENTS -',CR,LF
		DB	'CP/M drive(s)      Controller        '
		DB	'Addr   Unit #',CR,LF
		DB	'-------------------------------------'
		DB	'-------------',CR,LF,'$'

DRIVE$INFO:	EQU	$
DRIVE$LETTERS:	EQU	$-DRIVE$INFO
		DB	'                   '
DRIVE$CTRL:	EQU	$-DRIVE$INFO
		DB	'                   '
DRIVE$ADDRESS:	EQU	$-DRIVE$INFO
		DB	'0       '
DRIVE$UNIT:	EQU	$-DRIVE$INFO
		DB	'0'
		DB	CR,LF,'$'
DRIVE$LEN	EQU	$-DRIVE$INFO

; end of DISP$SDEV


DO$CRLF:
;
; [DC.27]
;
; This routine sends a carriage return and a line feed to the terminal,
; and then waits 'SLOW$TERM' ms for a slow terminal to catch up.
;
; Entry:
;	none
;
; Exit:
;	CR + LF is sent to the screen.
;
; Modifies:
;	none
;
	PUSH	PSW		; Save AF
	MVI	A,0Dh		; Send carriage return
	CALL	CON$CHR		; .
	MVI	A,0Ah		; and line feed
	CALL	CON$CHR		; .
	LDA	SLOW$TERM	; Check slow flag
	ORA	A		; .
	CNZ	WAIT		; wait for the s-l-o-w terminals
	POP	PSW		; recover original AF
	RET			; and return


FILL$BLOCK:
;
; [E1.29]
;
; Fills the buffer pointed to by DE with the character in C for
; a length of B bytes (max 256).
;
; Entry:
;	B  = Length to fill
;	C  = Character to fill with
;	DE = Start of buffer
;
; Exit:
;	Buffer filled
;
; Modifies:
;	Only buffer area affected
;
	PUSH	B		; Save registers
	PUSH	D		; .
	XCHG			; Set up M register use
F$NEXT:
	MOV	M,C		; Stuff a char
	INX	H		; Bump pointer
	DCR	B		; Bounce counter
	JNZ	F$NEXT		; Do it again if not done
	XCHG			; We are done.  restore old HL
	POP	D		; Restore other registers
	POP	B		; .
	RET			; and return


GET$ACTIVE$DEV:
;
; [E2.15]
;
; Get the active devices. Store the floppy and E-disk descriptions
; in the ACTIVE$FDEV table, and store the SCSI descriptions in the
; ACTIVE$SDEV table.
;
; Entry:
;	none
;
; Exit:
;	The active device descriptions will be in tables.
;
;	ACTIVE$FDEV (4 entries, 1 byte each) format:
;
;	+0: Current CP/M letter and "E" disk status.
;	    (Bit 7 set indicates this device is the "E" disk)
;
;	ACTIVE$SDEV (11 entries, 8 bytes each) format:
;
;	+0: SCSI Address
;	+1: Controller type (0-3)
;	+2: Drive type (0-3)
;	+3: Drive unit (0-7) -- shifted for use with SCSI commands
;	+4: Logical partition (0-7)
;	+5: Current CP/M letter
;	+6: spare
;	+7: spare
;
; Calls:
;	LB$GET$LDTE	Get logical drive table entry address
;
; Modifies: All
;
	LXI	D,ACTIVE$SDEV	; Clear the table
	LXI	B,ACTIVE$TLEN SHL 8	;
	CALL	FILL$BLOCK
	XCHG			;
	SHLD	NEXT$POS	; Save pointer for later
	XRA	A		; Clear count of active SCSI devices
	STA	TOTAL$ACTIVE	; .
G$GET$INFO:
	PUSH	PSW		; Save current logical unit #
	CALL	LB$GETLOGICAL	; Get logical drive table entry addr
	MOV	A,M		; If zero, non-active device
	ORA	A		; .
	JZ	G$NEXT$DEV	; .
	CPI	8		; Only driver codes 0-7 supported
	JP	G$NEXT$DEV	; .

	LXI	D,OUTBUF	; Clear scratch area
	LXI	B,0800H		; Fill with 00h for length of 8 bytes
	CALL	FILL$BLOCK	; .

	XCHG			; Save pointer to drive entry
	LXI	H,G$EXE$TBL	;
	JMP	GO$TABLE	; Jump to proper routine

G$EXE$TBL:
	DW	G$NEXT$DEV	; Driver code = 00
	DW	G$FLOPPY	; Driver code = 01
	DW	G$EDISK		; Driver code = 02
	DW	G$HARD		; Driver code = 03
	DW	G$NEXT$DEV	; Driver code = 04
	DW	G$NEXT$DEV	; Driver code = 05
	DW	G$NEXT$DEV	; Driver code = 06
	DW	G$NEXT$DEV	; Driver code = 07

G$F$DRIVE:			; Convert floppy drive to ptr
	INX	H		; Get byte
	MOV	A,M		; Floppy drive byte
	ANI	03h		; Isolate drive bits
	LXI	H,ACTIVE$FDEV	; Compute table addr
	MOV	C,A		; .
	MVI	B,0		; .
	DAD	B		; .
	RET			; and return

G$FLOPPY:
	XCHG			; Get pointer to drive entry back
	CALL	G$F$DRIVE	; Convert floppy drive
	POP	PSW		; Get logical unit
	PUSH	PSW		; .
	ADI	'A'		; Convert 0-15 to A-P
	ORA	M		; Or in E-disk, if present
	MOV	M,A		; Save new disk letter
	JMP	G$NEXT$DEV	; Get next device info

G$EDISK:
	XCHG			; Get pointer to drive entry back
	CALL	G$F$DRIVE	; Convert floppy drive
	MOV	A,M		; Set high bit for E disk
	ORI	80h		; .
	MOV	M,A		; .
	JMP	G$NEXT$DEV	; Get next device info

G$HARD:
	XCHG			; Get pointer to drive entry back
	INX	H		; (+1) Controller, Drive, Partition
	MOV	A,M		; Get controller (0-3)
	RRC			; .
	RRC			; .
	ANI	03H		; .
	STA	CTRL$TYPE	; .
	MOV	A,M		; Get drive type (0-3)
	ANI	03H		; .
	STA	DRV$TYPE	; .
	MOV	A,M		; Get partition
	ANI	0F0H		; .
	STA	PARTITION	; .

	INX	H		; (+2) Unit #
	MOV	A,M		; .
	ANI	0E0H		; .
	STA	DRV$LUN	; .

	INX	H		; (+3) SCSI address
	MOV	A,M		; .
	STA	SCSI$ADDR	; .

	POP	PSW		;
	PUSH	PSW		; Get CP/M letter
	ADI	'A'		; .
	STA	CPM$LETTER	; .

	LHLD	NEXT$POS	; Get next buffer position
	LXI	D,SCSI$ADDR	; . and current scratch area
	XCHG			; Now DE=buffer, HL=scratch
	LXI	B,8		; Save data
	DW	LDIR80		; .
	LXI	H,8		; Update buffer pointer
	DAD	D		; .
	SHLD	NEXT$POS	; .
	LXI	H,TOTAL$ACTIVE	; Bump count of SCSI devices
	INR	M		; .

G$NEXT$DEV:
	POP	PSW		; Get logical back
	INR	A		; Bump to next logical
	CPI	16		; Do maximum of 16 logicals
	JNZ	G$GET$INFO	; Not done -- go get more
	RET

		DS	16-($ MOD 16)
ACTIVE$SDEV:	DS	12*8	; SCSI device info
ACTIVE$FDEV:	DS	4	; Floppy CP/M letters
ACTIVE$TLEN:	EQU	$-ACTIVE$SDEV	; Active table length

TOTAL$ACTIVE:	DB	0	; # of active SCSI devices
NEXT$POS:	DW	0	; Next SCSI buffer position

SCSI$ADDR:	DB	0		; SCSI Address (0-7)
CTRL$TYPE:	DB	0	; Controller type (0-3)
DRV$TYPE:	DB	0	; Drive type (0-3)
DRV$LUN:	DB	0	; Drive logical unit # (0-7)
PARTITION:	DB	0	; Drive partition (0-7)
CPM$LETTER:	DB	0	; CP/M letter (0-15)
UNIT:		DB	0	; Unit number (1-8)
SPARE:		DB	0	; Spare

; end of GET$ACTIVE$DEV routine


GET$BIOS$VERS:
;
; [E5.24]
;
; Get bios version -- Copies the current BIOS jump tables (starting
; at warm boot) to a local area for ease of utility access.  If the
; BIOS is version 2.0 or greater, the secondary jump table is copied
; also.
;
; Entry:
;	none
;
; Exit:
;	Z  = bios 1.0 - 1.4 (floppy only bios)
;	NZ = bios 2.0 or greater (floppy & fixed disk bios)
;
; Modifies: All registers
;
	LHLD	1		; Get start of bios jump table
	LXI	D,LB$BIOS$TBL	; Move bios to local storage
	LXI	B,LB$LEN	; .  (length of bios area)
	DW	LDIR80		; .  (LDIR)
	MVI	A,10		; Test CP/M version
	CALL	LB$GETNXT	; Get next jump table
	STA	BIOS$VERSION	; Save bios version
	INX	H		; See if HL is 0FFFFh
	MOV	A,H		; .
	ORA	L		; .
	RZ			; If so, then old version
	DCX	H		; Fix HL as it has the table addr
	LXI	D,LB$XTBL	; Move extra table to local storage
	LXI	B,LB$XLEN	; .  (length of extra table)
	DW	LDIR80		; .  (LDIR)
	MVI	A,0FFH		; Set NZ to indicate bios
	ORA	A		; ... version 2.1+
	RET			; ... and return.

BIOS$VERSION:
	DB	0		; Current bios version


GET$HL$PTR:
;
; [DC.20]
;
; Gets the pointer pointed to by HL and puts it in HL
;
; Entry:
;	HL = pointer to put in HL
;
; Exit:
;	HL = pointer
;
; Modifies: none
;
	PUSH	PSW		; Save A register
	MOV	A,M		; Get low byte of pointer
	INX	H		; .
	MOV	H,M		; Get high byte of pointer
	MOV	L,A		; Pointer is now together
	POP	PSW		; Restore A register
	RET			; and return


GET$STRLEN
; Searches the string pointed to by HL and returns the string length
; to the next carriage return.  The length is returned in B.
	PUSH	D		; Save start of string
	MVI	B,0		; Clear counter
TRY$NEXT$CHR:
	LDAX	D		; Get character
	CPI	CR		; Is it CR?
	JZ	EOS$FOUND	; .
	CPI	'$'		; Is it '$'?
	JZ	EOS$FOUND	; .
	INR	B		; No -- increment count and
	INX	D		; .  point to the next character
	JMP	TRY$NEXT$CHR	; .
EOS$FOUND:
	POP	H		; CR or '$' found, recall orig ptr
	XCHG			; DE=orginial, HL=current
	RET			; and return


GO$TABLE:
;
; [E2.05]
;
; Jump to a routine based on a table of pointers
;
; Entry:
;	A  = index into table
;	HL = table base address
;
; Exit:
;	Routine at (A*2)+HL is executed
;
; Modifies:
;	B, HL
;
	LXI	B,2		; Compute offset to table of routines
	CALL	INDEX$TABLE	; .
	CALL	GET$HL$PTR	; .
	PCHL			; Jump to proper routine


INDEX$ACTIVE:
;
; [E2.10]
;
; Compute index into active table
;
; Entry:
;	A  = Table entry number (00h-0fh)
;
; Exit:
;	HL = Address of table entry
;
; Modifies:
;	PSW, DE, HL
;
	LXI	B,8		; Length of active device entry
	LXI	H,ACTIVE$SDEV	; Table base address
	JMP	INDEX$TABLE	; Return through INDEX$TABLE


INDEX$TABLE:
;
; [E1.30]
;
; Computes offset to table given base address, entry length, and entry
; requested.
;
; Entry:
;	A  = entry #
;	BC = table entry length
;	HL = base address
;
; Exit:
;	HL = address to entry
;
; Modifies:
;	A, BC, HL
;
	ORA	A		; Set up flags for first check
I$TBL$ADD:
	RZ			; If A=0, we're done
	DAD	B		; Otherwise add length to base,
	DCR	A		; .  decrement counter,
	JMP	I$TBL$ADD	; .  and check again.


IS$IT$OK:
;
; [E1.28]
;
; Check the character in A against the list of "OK" chrs pointed
; to by HL
;
; Entry:
;	A  = character to check
;	HL = pointer to list of "OK" characters
;
; Exit:
;	A  = original character if ok, 0ffh if not in list
;	B  = position of character in list
;
; Modifies:
;	BC
;
	PUSH	H		; Save original "OK" pointer
	MOV	C,A		; Save chr to check against
	MVI	B,0		; Clear counter
	CPI	ESC		; If chr is <ESC>
	JZ	I$CHR$OK	; .  then automatically ok
	INR	B		; .  otherwise start counting at 1
I$CHK$NEXT:
	MOV	A,M		; Get chr to check against
	ORA	A		; End of table?
	JNZ	I$NOT$EOT	; No, check chr
	DCR	A		; Decrement to get 0ffh
	MOV	B,A		; Stuff for later move
	JMP	I$CHR$OK	; And exit
I$NOT$EOT:
	CMP	C		; Chrs match?
	JZ	I$CHR$OK	; . Yes, return
	INX	H		; . No, bump pointer
	INR	B		; . . and bump counter
	JMP	I$CHK$NEXT	; . . and check next chr
I$CHR$OK:
	MOV	A,B		; Set status based on
	ORA	A		; .  position counter
	MOV	A,C		; Get user chr back
I$DONE:
	POP	H		; and original "OK" pointer
	RET			; and return


JUSTIFY:
;
; [E1.30]
;
; This routine will send a data stream to the console, with each line
; justified based on the SCREEN$WIDTH value.  The stream must terminate
; with the CP/M end of string character ($) and may contain imbedded
; CR,LF pairs to separate paragraphs.
;
; NOTE:  To insure proper operation, the LF character should only follow
; a CR character or another LF character, as the CR character is used to
; flush the current line without justification.
;
; Two entry points are provided:
;	JUSTIFY		Justify output, flush right
;	JUSTIFY$RAGGED	Justify output, ragged right
;
; As of E1.30, the flush right routine was not installed, so either
; entry point will provide the same results.
;
; Entry:
;	DE = pointer to line(s) to output
;
; Exit:
;	The data is sent to the screen.
;
; Modifies: All registers
;
	MVI	A,80H		; Set flush right mode
	JMP	E$JUSTIFY	; Jump to routine entry
JUSTIFY$RAGGED:
	MVI	A,00H		; Set ragged right mode
E$JUSTIFY:
	STA	J$MODE		; Save mode byte
	MVI	A,'$'		; Mark start of buffer
	STA	OUTBUF-1	; .
	XCHG			; DE is usually print source
J$NEXT$LINE:
	XRA	A		; Clear character counter
	STA	BLANK$LEN	; .
	MOV	B,A		; .
	LXI	D,OUTBUF	; Set up buffer pointer
J$CHECK$CHR:
	MOV	A,M		; Get character
	STAX	D		; Save in output buffer
	CPI	CR		; CR?
	CZ	J$FLUSH$LINE	; .  Yes, flush output line,
	CZ	J$LITERAL	; .  .  output CR & bump ptr
	JZ	J$NEXT$LINE	; .  .  and do another.
	CPI	LF		; LF?
	CZ	J$LITERAL	; .  Yes, output LF & bump ptr
	JZ	J$CHECK$CHR	; .  .  and check next chr.
	CPI	'$'		; End of string?
	CZ	J$FLUSH$LINE	; .  Yes, flush output line,
	RZ			; .  .  and return to caller
	CPI	' '		; Blank?
	JNZ	J$NOT$A$BLANK	; .  No, don't save position
	SHLD	BLANK$POS	; Save position for later
	XCHG			; and save corresponding position
	SHLD	OUTBUF$BLANK	; .  of the blank we just saved
	XCHG			; .  in the output buffer
	MOV	A,B		; .
	STA	BLANK$LEN	; Save current length also
J$NOT$A$BLANK:
	INR	B		; Increment counter
	INX	H		; .  and pointer
	INX	D		; .  and output pointer
	LDA	SCREEN$WIDTH	; Compare counter against screen width
	SUB	B		; .
	JP	J$CHECK$CHR	; And continue checking if not past end
;
; Screen width exceeded, send this line to the screen.
;
	LHLD	OUTBUF$BLANK	; Get pos of last blank in output buf
	MVI	M,'$'		; and plug with eos ('$')
	LDA	J$MODE		; Justify right edge only if the
	CM	J$ADD$BLANKS	; .  right-justify flag is non-zero
	CALL	J$SEND$BUFFER	; Output the line to the screen
	CALL	DO$CRLF		; and a CR / LF
	LHLD	BLANK$POS	; Get pointer to where we left off
J$SKIP$BLANKS:
	INX	H		; Bump pointer past blank(s)
	MOV	A,M		; .
	CPI	' '		; .
	JZ	J$SKIP$BLANKS	; .
	JMP	J$NEXT$LINE	; and check next segment

J$FLUSH$LINE:			; Flush line when CR or EOS encountered
	PUSH	PSW		; Save chr & zero flag
	MVI	A,'$'		; Plug current position with EOS ($)
	STAX	D		; .
	CALL	J$SEND$BUFFER	; Send this line of data
	POP	PSW		; Restore chr & zero flag
	RET			; and return

J$LITERAL:			; Send the chr in A
	PUSH	PSW		; Save zero flag
	CALL	CON$CHR		; Console chr out through CP/M
	INX	H		; Bump chr pointer
	POP	PSW		; Restore zero flag
	RET

J$ADD$BLANKS:
	RET			; At a later time, this routine will justify
				; the right margin by inserting extra blanks
				; in the output line.

J$SEND$BUFFER:
	MOV	A,B		; If line to output is of zero length,
	ORA	A		; .  then don't output the line.
	RZ			; .
	LXI	D,OUTBUF	; Get address of output buffer
	CALL	CON$MSG		; and call our print message routine
	RET			; return

J$MODE		DB	0	; Current right justify mode
BLANK$POS	DW	0	; Last blank on this line
OUTBUF$BLANK	DW	0	; Last blank in the output buffer
BLANK$LEN	DB	0	; Length of line to the blank

; NOTE: OUTBUF is defined to be after the stack and before the heap.

; end of justify$output data area


PROMPT:
;
; [E2.19]
;
; Prompt the user or the command line for input.
;
; Two entry points are provided:
;	PROMPT		standard entry, CRLF after chr from user
;	PROMPT$NOLF	special entry, No CRLF after chr from user
;
; When the command line is used for input, the following characters
; are translated to new values or new functions:
;
;	Character	New character or new function
;	--------------	------------------------------------
;	, (comma)	<RETURN> key
;	. (period)	<ESC> key
;	@ (at-sign)	repeat existing command line
;	_ (underscore)	Prompt and get character from user
;
; Entry:
;	DE = pointer to prompt string 
;	HL = pointer to list of valid chars (terminated with 00H)
;
; Exit:
;	A  = char from the user
;	B  = position of this character (0, 1, 2, ... n)
;
;	Z  = char was the escape key
;	NZ = char was not the escape key
;
; Modifies:
;	A, BC
;
	MVI	A,01H		; Set CRLF after chr
	JMP	E$PROMPT	; Jump to entry point
PROMPT$NOLF:
	MVI	A,00H		; Set no CRLF after chr
E$PROMPT:
	STA	PROMPT$MODE	; Save prompt mode flag
RE$PROMPT:
	LDA	CMD$LINE$CHRS	; Are there any characters left from
	ORA	A		; .  the command line?
	JZ	P$DISP		; No -- display & get chr from bios
	PUSH	H		; Yes, save oklist pointer
	LHLD	CMD$LINE$PTR	; and get chr from command line
	DCR	A		; .  Reduce count of chrs by one
	STA	CMD$LINE$CHRS	; .  .
	MOV	A,M		; .  Get command line character
	INX	H		; .  Increment pointer
	SHLD	CMD$LINE$PTR	; .  .
	POP	H		; .  Restore oklist pointer
	CPI	' '		; Ignore blanks
	JZ	RE$PROMPT	; .
	CPI	'@'		; Repeat existing cmd line?
	JNZ	P$NOT$REPEAT	; .  (check other chrs if not)
	PUSH	H		; .  Save oklist pointer
	LXI	H,INBUF+2	; .  Set command line pointer back
	SHLD	CMD$LINE$PTR	; .  .  to the beginning
	POP	H		; .  Restore oklist pointer
	MVI	A,07FH		; .  Set the count of chars to 127
	STA	CMD$LINE$CHRS	; .  .  (the most it could be)
	JMP	RE$PROMPT	; .  and get the next character
P$NOT$REPEAT:
	CPI	','		; Change ',' to CR
	JNZ	P$NOT$COMMA	; .
	MVI	A,CR		; .
P$NOT$COMMA:
	CPI	'.'		; Change '.' to ESC
	JNZ	P$NOT$DOT	; .
	MVI	A,ESC		; .
P$NOT$DOT:
	CPI	'_'		; Underline means prompt & get chr
	JZ	P$DISP		; . from bios anyway (user input)
	CALL	TO$UPPER	; Convert the chr to upper case
	CALL	IS$IT$OK	; Check the chr against the ok list
	RP			; If ok, return
	XRA	A		; Otherwise, cancel the cmd line
	STA	CMD$LINE$CHRS	; .  buffer and fall through to p$disp
P$DISP:
	PUSH	H		; Save pointer to ok-chrs
	CALL	JUSTIFY		; and call justify routine
	POP	H		; .
P$TRY$AGAIN:
	CALL	CONIN$NE$XC	; Console input, no echo, except ^C
	CALL	TO$UPPER	; Convert the chr to upper case
	CALL	IS$IT$OK	; If the character is not "OK"
	JM	P$TRY$AGAIN	; . get another
	CNZ	CON$CHR		; . otherwise display it
	PUSH	PSW		; Check mode flag in case we need
	LDA	PROMPT$MODE	; .  to send a CR+LF after the
	ANI	01H		; .  user's input
	CNZ	DO$CRLF		; .
	POP	PSW		; .
	RET			; and return

PROMPT$MODE:	DB	0	; Prompt mode flag


PROMPT$DECIMAL:
;
; [E6.11]
;
; Prompt the user for a decimal input of up to 10 digits.  The Z flag
; indicates the termination character: Z = ESC key, results may not
; be valid; NZ = RETURN key, results valid.
;
; Entry:
;	DE = pointer to prompt string
;
; Exit:
;	BC,DE = Value entered by the user, 0-0FFFFFFFFH
;
;	Z  = ESC key pressed, ignore results
;	NZ = RETURN key pressed, results valid
;
; Modifies:
;	A, PSW
;
	MVI	A,01H		; Set CRLF after entry
	DB	JR,E$PR$DEC-$-1	; Jump to entry point
PROMPT$DEC$NOLF:
	MVI	A,00H		; Set no CRLF after entry
E$PR$DEC:
	STA	PROMPT$D$MODE	; Save prompt mode flag
	PUSH	H		; Save original HL reigster
	XRA	A		; Clear the current digit pointer
	MOV	C,A		; Setup count in C register
	LXI	H,SCRATCH	; Initialize the scratch pointer
NEXT$DIGIT:
	PUSH	B		; Save digit counter
	PUSH	H		; .  and string pointer
	LXI	H,DEC$INPUT$OKC	; Get current 'ok' chrs
	CALL	PROMPT$NOLF	; Prompt for digit
	POP	H		; Get string pointer back
	POP	B		; .  along with digit counter
	DB	JRZ,ESCRTN-$-1	; Return if ESC key hit
	CPI	CR		; Check for return
	JZ	CONVERT$STRING	; Convert string to decimal, if so
	CPI	BS		; Check for backspace
	JZ	BACKUP$DIGIT	; Back up 1 digit, if we can

	MOV	B,A		; Save character for a moment
	MOV	A,C		; Check digit count
	CPI	10		; .
	JC	ADD$DIGIT	; 10 digits or less, add to string
	LXI	D,BLOT		; More than 10 digits, blot this one
	CALL	CON$MSG		; .
	JMP	DE$FOR$NEXT	; .
ADD$DIGIT:
	INR	C		; Otherwise bump digit count
	MOV	M,B		; Save digit
	INX	H		; Bump digit pointer
	JMP	DE$FOR$NEXT	; Setup DE for next prompt

BACKUP$DIGIT:
	MVI	A,' '		; Bump forward to clear digit
	CALL	CON$CHR		; .
	XRA	A		; Are we at the beginning?
	CMP	C		; .
	JZ	DE$FOR$NEXT	; Yes, don't backup
	MVI	A,BS		; Backup to correct position
	CALL	CON$CHR		; .
	DCX	H		; Backup pointer 1 chr
	DCR	C		; Push count 1 back, also
DE$FOR$NEXT:
	LXI	D,DEC$INPUT$MSG	; Setup for next prompt
	JMP	NEXT$DIGIT	; .

CONVERT$STRING:
	MVI	M,0		; Mark end of string
	LXI	H,SCRATCH	; Get beginning of string
	CALL	STR$TO$BCDE	; Convert string to BC & DE regs
	XRA	A		; Insure NZ flag
	DCR	A		; .
	PUSH	PSW		; Check mode flag to see if a
	LDA	PROMPT$D$MODE	; .  CR+LF should be sent after
	ANI	01H		; .  the user's input
	CNZ	DO$CRLF		; .
	POP	PSW		; .
ESCRTN:
	POP	H		; Get original HL back
	RET			; and return with result in BC & DE

DEC$INPUT$MSG:	DB	'$'		; Decimal input message
DEC$INPUT$OKC:	DB	'0123456789'	; Decimal input 'ok' chrs
		DB	BS,CR,0		; .
BLOT:		DB	BS,' ',BS,'$'	; Blot out digit
SCRATCH:	DB	'          ',0	; Max 10 digits
PROMPT$D$MODE:	DB	0		; CRLF flag


RET$TO$CONT:
;
; [E2.19]
;
; Prompts and waits for the RETURN key to be pressed.
;
; Entry:
;	none
;
; Exit:
;	Display message and wait for a RETURN key.
;
; Modifies:
;	all
;
	LXI	D,RTC$MSG	; Press RETURN to continue ...
	CALL	CENTER$OUTPUT	; .
	LXI	D,NO$MSG	; .
	LXI	H,RTC$OKC	; .
	CALL	PROMPT		; .	[ CR ]
	RET

RTC$MSG:	DB	'Press the RETURN key to continue ...'
NO$MSG:		DB	'$'
RTC$OKC:	DB	CR,0

; end of RET$TO$CONT routine


SCSI$TO$BIN:
;
; [DC.27]
;
; Converts SCSI address to binary 0-7
;
; Entry:
;	A  = SCSI address to convert
;
; Exit:
;	A  = converted address (0-7)    [0FFH = error]
;
; Modifies: B
;
	MVI	B,0FFH		; Set up B register for possible error
	ORA	A		; If A=0, error
	JZ	SCSI$CONVERTED	; .
SCSI$NEXT$BIT:
	INR	B		; Increment count
	RRC			; Shift address right 1 bit
	JNC	SCSI$NEXT$BIT	; Bit shifted to cary means we're done
SCSI$CONVERTED:
	MOV	A,B		; Move converted addr to A
	RET


SORT$ACTIVE:
;
; [E2.21]
;
; Sort active device table entries
;
; Entry:
;	none
;
; Exit:
;	Active device table is sorted.
;
; Modifies:
;	All
;
	LDA	TOTAL$ACTIVE	; Get # of items to sort
	CPI	02		; Less than two items?
	RM			; .  yes -- no sort necessary
S$NEXT$BLOCK:			; .
	STA	SORT$MAX	; Save # to sort
	LXI	B,0001H		; Setup initial compare pointers
S$NEXT$ELEM:
	PUSH	B		; Save compare pointers
	MOV	A,C		; Convert pointers to address
	CALL	INDEX$ACTIVE	; .  in DE and HL
	PUSH	H		; .
	MOV	A,B		; .
	CALL	INDEX$ACTIVE	; .
	POP	D		; .
	MVI	B,8		; Compare length = 8 chrs
	CALL	STR$COMP	; Compare string in [DE] to [HL]
	MVI	B,8		; Swap length = 8 chrs
	CM	STR$SWAP	; Swap if first is less than second
	POP	B		; Get pointers back
	INR	B		; Point to next item
	INR	C		; 
	LDA	SORT$MAX	; Compare with max
	CMP	C		; .
	JNZ	S$NEXT$ELEM	; Not done with this pass ...
	DCR	A		; Make sort limit one smaller
	CPI	03H		; Done if only 2 elements
	JP	S$NEXT$BLOCK	; Not done if > 2 elements
	RET

SORTMAX:	DW	0	; Sort max range (0-255 elements)


STR$COMP:
;
; [E1.18]
;
; Compare two strings
;
; Entry:
;	HL = source
; 	DE = destination
; 	B  = count
; Exit:
;	Z  = two strings equal
;	M  = source <  dest
;	P  = source >= dest
;
; Modifies: all
;
	PUSH	D
	PUSH	H
NEXT$COMPARE:
	MOV	C,M
	INX	H
	LDAX	D
	INX	D
	CMP	C
	JNZ	NOT$EQUAL
	DCR	B
	JNZ	NEXT$COMPARE
NOT$EQUAL:
	POP	H
	POP	D
	RET


STR$SWAP:
; Swap two strings
;
; [E1.18]
;
; Entry:
;	HL = source
; 	DE = destination
; 	B  = count
; Exit:
;	data moved
;
; Modifies: all
;
	MOV	C,M
	LDAX	D
	MOV	M,A
	MOV	A,C
	STAX	D
	INX	H
	INX	D
	DCR	B
	JNZ	STR$SWAP
	RET


STR$TO$BCDE:
;
; [E7.09]
;
; Converts the string pointed to by HL to a 32-bit number in the 
; DE and A registers.  The conversion will continue until the first
; non-numeric chr found.
;
; NOTE: a test for register overflow is not made.  If HL points
; to a string whose numerical value is greater than 4,294,967,295
; (0FFFFFFFFH), inaccurate results will occur.
;
; Entry:
;	HL = ptr to string
;
; Exit:
;	BC,DE = value of string (BC = high word, DE = low word)
;	HL    = next character to process
;
; Modifies:
;	A, DE, HL
;
;

	DW	LXIX,0		; Clear totals
	LXI	D,0		; .
STN$NEXT$CHR:
	MOV	B,A		; Save current high byte
	MOV	A,M		; Get next chr
	CPI	'0'		; Less than '0'?
	DB	JRC,NO$DIG-$-1	; Yes, finished
	CPI	'9'+1		; Greater than '9'
	DB	JRNC,NO$DIG-$-1	; Yes, finished
	SUI	'0'		; Convert to 0-9
	PUSH	H		; Save pointer to input string
	PUSH	PSW		; Save converted digit

	PUSH	D		; Setup regs for X10
	POP	B		; .
	DW	PUSHIX		; .  BC = p3, p2  Previous 32-bit
	POP	D		; .  DE = p1, p0    number
	LXI	H,0		; .  HL = t3, t2  Current 32-bit
	DW	LXIX,0		; .  IX = t1, t0    total
	MVI	A,10		; Set up count
STN$NEXT$X10:			; Add next digit
	DB	0DDH,019H	; . (ADD IX,DE)
	DB	0EDH,04AH	; . (ADC HL,BC)
	DCR	A		; .
	JNZ	STN$NEXT$X10	; .
	POP	PSW		; Get new digit back
	MOV	E,A		; Setup to add new digit
	MVI	D,0		; .
	LXI	B,0		; .
	DB	0DDH,019H	; Add new digit
	DB	0EDH,04AH	; . 
	XCHG			; Move total in HL back to DE
	POP	H		; Get input string ptr back
	INX	H		; Bump to next chr
	JMP	STN$NEXT$CHR	; Go back for another digit
NO$DIG:
	PUSH	D		; Move high word to BC
	POP	B		; .
	DW	PUSHIX		; Move low word to DE
	POP	D		; .
	RET			; Return with results in BC and DE.


TO$UPPER:
;
; [E1.08]
;
; Convert the character in A to upper case.
;
; Entry:
;	A  = character to convert
;
; Exit:
;	A  = upper case character (if alpha)
;
; Modifies:
;	A
;
	CPI	'z'+1		; Convert to upper case
	JP	UPPER$ALREADY	; .
	CPI	'a'		; .
	JM	UPPER$ALREADY	; .
	ANI	5FH		; .
UPPER$ALREADY:
	RET			; and return


WAIT:
; Wait A ms
;
; Entry:
;	milliseconds in A
;
; Exit:
;	time waited
;
; Modifies: A
;
	PUSH	PSW
	MVI	A,221
WAIT$2:	DCR	A
	JNZ	WAIT$2
	POP	PSW
	DCR	A
	JNZ	WAIT
	RET


* * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*							*
*		Data area . . .				*
*							*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * *

; Replicated BIOS to make direct calls easier . . .

LB$BIOS$TBL:
LB$WBOOT	DS	3	; Warm boot
LB$CONST	DS	3	; Console status
LB$CONIN	DS	3	; Console input
LB$CONOUT	DS	3	; Console output
LB$LISTOUT	DS	3	; List output
LB$PUNCH	DS	3	; Punch output
LB$READER	DS	3	; Reader input
LB$HOMDSK	DS	3	; Home disk (move to track 00)
LB$SELDSK	DS	3	; Select disk drive
LB$SETTRK	DS	3	; Select track number
LB$SETSEC	DS	3	; Select sector number
LB$SETDMA	DS	3	; Set DMA address
LB$DSKREAD	DS	3	; Disk read
LB$DSKWRITE	DS	3	; Disk write
LB$LISTST	DS	3	; List status
LB$SECTRN	DS	3	; Sector translate routine
; AMPRO-specific BIOS calls
LB$GETNXT	DS	3	; Get bios ver & next tbl address
LB$GETEDSK	DS	3	; Get pointer to E-disk storage
LB$IOINIT	DS	3	; Set new I/O parameters
LB$SCSIDRV	DS	3	; SCSI direct driver
LB$LEN	EQU	$-LB$WBOOT	; Length of bios table

LB$XTBL:			; 'Extra' table definitions ...
LB$SWAP$DRV	DS	3	; Swap two logical drives
LB$WINDRV	DS	3	; Set/get win drive parameters
LB$PHYTAB	DS	3	; Set/get phytab access
LB$GETLOGICAL	DS	3	; Get logical device table entry
LB$RESERVED	DS	3	; Reserved entry
LB$XLEN	EQU	$-LB$XTBL	; Length of extra table

LB$VERS:	DS	1	; Bios version number

		DS	64	; 32-level stack
STACK:		DS	2	; Old stack pointer

INBUF:		DS	128	; Command line input buffer
IBUFL:		EQU	128	; Input buffer length

OBPLUG:		DS	1	; Start of outbuf ('$')
OUTBUF:		DS	PWIDTH	; Output buffer
OBUFL:		EQU	PWIDTH	; Output buffer length

HEAPPTR:	DS	2	; Pointer to next area
HEAPLEN:	DS	2	; Length of next area
HEAP:		EQU	$	; Start of heap

		END	START
