
* * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*							*
*     Copyright (C) 1985,1986 AMPRO COMPUTERS, INC.	*
*							*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * *

; Assemble with asm.com or equivalent.  There is no Z80 code. 


; Revision history:
;
;  Ver	Date	Who	Description
;  ---	-----	---	------------------------------------------
;  2.0  F2.05   RJB	Fixed foreign format display error.
;		        Changed to allow foreign drive letter swap.
;
;  1.1	E6.17	RJB	Removed HD controller name from display.
;
;  1.0	E2.19	RJB	Original version
;

VER		EQU	20	; version
SWIDTH		EQU	80	; Screen width
PWIDTH		EQU	132	; Maximum printer width

THIS$MONTH	EQU	2	; This version's month
THIS$DAY	EQU	05	; .		 day
THIS$YEAR	EQU	86	; .		 year

MIN$VERSION	EQU	31	; Minimum bios version allowed

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

; Character equates
CTRLC	EQU	3		; CP/M break key
TAB	EQU	9		; Tab character (CTRL-I)
ESC	EQU	27		; escape key (CTRL-[)
CR	EQU	13		; carriage return (CTRL-M)
LF	EQU	10		; line feed (CTRL-J)
EOF	EQU	26		; CP/M end-of-file (CTRL-Z)
EOS	EQU	'$'		; CP/M end-of-string (dollar sign)

; bdos equates
BDOS	EQU	5		; bdos entry
RDCON	EQU	10		; read console buffer
CONBUF	EQU	80h		; location of console buffer

; other equates
NDEV	EQU	16		; number of disk devices supported

	ORG	0100H

	JMP	START

SCREEN$WIDTH:	DB	SWIDTH-1; 1 less than actual # of characters
SLOW$TERM:	DB	0	; Set to delay (ms) for slow terminal
CMD$LINE$CHRS:	DB	0	; Count of cmd line characters left
CMD$LINE$PTR:	DW	0	; Pointer to next cmd line chr

NAME$MSG:
	DB	0DH,'AMPRO '
NAME:	DB	'SWAP'
NLEN:	EQU	$-NAME
	DB	' Utility',CR,LF
	DB	'Copyright (C) 1985 AMPRO Computers, Inc.',CR,LF
	DB	'Version ',VER/10+'0','.',VER MOD 10+'0'
	DB	'  [',THIS$YEAR-80+'@',THIS$MONTH+'0','.'
	DB	THIS$DAY/10+'0',THIS$DAY MOD 10 + '0',']'
	DB	CR,LF,LF,'$',CR
HELP$MSG:
	DB	'Usage: SWAP'
	DB	CR,LF,'$',CR,' ',CR

BIOS$PLUS:
	DB	'This program requires AMPRO bios version '
	DB	MIN$VERSION/10+'0','.',MIN$VERSION MOD 10+'0'
	DB	' or later.',CR,LF,'$'
	DB	CR,' ',CR,EOF
SIGNON$MSG:
	DB	'The AMPRO SWAP utility allows you to swap the '
	DB	'definition of any two CP/M drive letters.  For example, '
	DB	'if you have two floppy drives at A: and B:, and two '
	DB	'hard disk partitions at F: and G:, you could swap pairs '
	DB	'A and F, B and G, C and F, and D and G, '
	DB	'to place the hard disk partitions at A: and B:, '
	DB	'and the floppy drives at C: and D:.',CR,LF,LF
	DB	'NOTE: Swapping drive A: has these two restrictions:',CR,LF,LF
	DB	'  (1)  The A: drive can only be swapped with an existing '
	DB	'drive (one of those listed by SWAP under "current '
	DB	'assignments").  ',CR,LF,LF
	DB	'  (2)  Be sure to use the AMPRO SYSGEN utility to '
	DB	'write a system to the drive which is to become drive '
	DB	'A:, before swapping drive A: with any other drive. ',CR,LF,LF
	DB	'$'

;
; Initialize the stack and command line input pointer.
;

START:
	LXI	H,0		; Get old stack pointer
	DAD	SP		; .
	SHLD	OLDSP		; Save it for later
	LXI	SP,STACK	; Put SP at our stack
	LXI	H,0080H		; Copy command line to a 'safe' area
	LXI	D,INBUF		; .
	MVI	B,128		; .
	CALL	MOVE$BLOCK	; .
	XCHG			; Set up pointers and count of chrs
	MOV	A,M		; .
	STA	CMD$LINE$CHRS	; .
	INX	H		; .
	INX	H		; .
	SHLD	CMD$LINE$PTR	; .

;
; Display the name and signon message
;

TOP$MENU:
	ORA	A		; If cmd line, then no signon
	JNZ	WHICH$BIOS	; .
	CALL	CLEAR$SCREEN	; Otherwise, clear the screen
	LXI	D,NAME$MSG	; Display the name, etc.
	CALL	CENTER$OUTPUT	; .
	LXI	D,SIGNON$MSG	; and the initial message
	CALL	JUSTIFY		; .

;
; Copy the bios to a local area (to make direct bios calls easier)
; and check for version 2.0+.  If not version 2.0+, display an 
; error message and exit.
;

WHICH$BIOS:
	CALL	GET$BIOS$VERS	; Copy the bios to a local area

;
; 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	LB$VERS		; Get bios version #
	CPI	MIN$VERSION	; Check against minimum version
	JNC	MINBIOS		; At least minimum version . . .
	LXI	D,BIOS$PLUS	; Not minimum, display error message
	CALL	JUSTIFY		; .
	JMP	ALL$DONE	; and exit.

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

MINBIOS:

;
; Copy the device descriptors of the active devices to a local area
; and sort them.  If the count of active devices is zero, display
; an error message and exit.
;

DO$ANOTHER:
	CALL	GET$ACTIVE$DEV	; Get the active device descriptors
	CALL	SORT$ACTIVE	; and sort them

	LXI	D,S$WHO$MSG	; Prompt for drive to swap
	LXI	H,S$WHO$OKC	; .
	CALL	PROMPT		; .
	JZ	ALL$DONE	; exit if <ESC> key pressed
	STA	S$WHO$LETTER	; save letter in second prompt
	STA	S$SW1		; .
	CPI	'?'		; check for help
	JZ	SHOW$CURRENT	; .  show current assignments
	SUI	'A'		; Convert A-P to 00H-0FH
	MOV	C,A		; save swap drive
	LXI	D,S$WITH$MSG	; Prompt for drive to swap with
	LXI	H,S$WITH$OKC	; .
	PUSH	B		; .
	CALL	PROMPT		; .
	POP	B		; .
	JZ	ALL$DONE	; exit if <ESC> key pressed
	CPI	'?'		; check for help
	JZ	SHOW$CURRENT	; .  show current assignments
	STA	S$SW2		; 
	SUI	'A'		; convert A-P to 00h-0Fh
	MOV	B,A		; save swap with drive
;
	ORA	A		; Is 'swap with' drive A:?
	JNZ	GS$OTHER	; .
	MOV	A,C		; . if so, make sure other is active
	JMP	GS$TESTFORA	; .  
GS$OTHER:
	MOV	A,C		; 
	ORA	A		; Is 'swap' drive A:?
	JNZ	GS$SWAP$EM	; .
	MOV	A,B		; . if so, make sure other is active
GS$TESTFORA:
	CALL	LB$GET$LDTE	; Get logical device table entry
	MOV	A,M		; Zero means not active
	ORA	A		; .
	JNZ	GS$SWAP$EM	; . (not zero means ok to swap)
	LXI	D,GS$NOT$ACTIVE	; Display error message if drive
	CALL	JUSTIFY		; .  not active
	CALL	RET$TO$CONT	; Wait for a RETURN
	XRA	A		; Clear command line on an error
	STA	CMD$LINE$CHRS	; .
	JMP	SHOW$CURRENT	; and show 'em what's available
GS$SWAP$EM:
	CALL	LB$SWAP$DRV	; otherwise, swap the drives.
	LXI	D,S$SWAPPED	; and tell 'em about it
	CALL	JUSTIFY		; .
	JMP	DO$ANOTHER

S$WHO$MSG:
	DB	CR,LF
	DB	'Which drive do you want to swap '
	DB	'(A-P, ? for list, <ESC> to quit): ','$'
S$WITH$MSG:
	DB	'       Swap '
S$WHO$LETTER:
	DB	'x: with which drive '
	DB	'(A-P, ? for list, <ESC> to quit): ','$'

S$SWAPPED:
	DB	TAB,'<<<  Drive '
S$SW1:	DB	'x: successfully swapped with drive '
S$SW2:	DB	'x:  >>>',CR,LF,'$'

S$WHO$OKC:
S$WITH$OKC:
	DB	'?ABCDEFGHIJKLMNOP',0
GS$NOT$ACTIVE:
	DB	CR,LF,'ERROR -- Cannot swap drive A: with a non-existant '
	DB	'drive letter.',CR,LF,LF,'$'

SHOW$CURRENT:
	CALL	CLEAR$SCREEN	; Clear the screen
	LXI	D,CURRENT$LTR	; "Current CP/M drive letter assignments"
	CALL	CENTER$OUTPUT	; .
	CALL	DISPLAY$FDEV	; Display floppy devices
	CALL	DO$CRLF		; .
	CALL	DISPLAY$EINF    ; Display message about E-drive
	CALL	DO$CRLF		; .
	CALL	DISPLAY$SDEV	; Display SCSI devices
	CALL	DO$CRLF		; .
	JMP	DO$ANOTHER

CURRENT$LTR:	DB	'>>> Current CP/M Drive Letter Assignments <<<'
		DB	CR,LF,LF,'$'

;
; Restore the old stack pointer and exit the program.
;

ALL$DONE:
	JMP	LB$WBOOT	; Return via warm boot


* * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*							*
*		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	; .
	MVI	B,D$FDEV$HLEN	; .
	CALL	MOVE$BLOCK	; .
	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; .
	MVI	B,4		; .
	CALL	MOVE$BLOCK	; .
	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; .
	MVI	B,FNAMES$LEN	; .
	CALL	MOVE$BLOCK	; .
	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$LETTER$1	DB	'*)'
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$EINF:
;
; [F2.05]
; 
; Display message about E-drive labeling.
;
	LXI	D,D$EINFO$MSG	; Print the message
	CALL	CENTER$OUTPUT	; .
	RET
;
D$EINFO$MSG:	DB	'('
E$LETTER$2:	DB	'*) = Current foreign format drive letter.'
		DB	CR,LF,LF,'$'
;


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:
	MVI	B,4		; Since DE & HL still point to last & 
	CALL	MOVE$BLOCK	; .  current, move current to last.

	PUSH	H		; save ptr
	LXI	H,DRIVE$INFO	; Copy template to output buffer
	LXI	D,OUTBUF	; .
	MVI	B,DRIVE$LEN	; .
	CALL	MOVE$BLOCK	; .

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

	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

FD$HEADER:	DB	'> HARD DISK ASSIGNMENTS <',CR,LF
		DB	'CP/M drive(s)         '
		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


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$GET$LDTE	; 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:
	CALL	LB$GETEDSK	; Get pointer to E-disk parameters
	LXI	B,15		; Bump pointer to E-disk drive
	DAD	B		; .
	DCX	H		; Decrement ptr because next routine bumps it
	CALL	G$F$DRIVE	; Convert floppy drive
	MOV	A,M		; Set high bit for E disk
	ORI	80h		; .
	MOV	M,A		; .
	POP	PSW		; Get drive letter
	PUSH	PSW		; Save it back
	ADI	'A'		; Convert 0-15 to A-P
	STA	E$LETTER$1	; Save in '(*)' location
	STA	E$LETTER$2	;   and in description
	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
	MVI	B,8		; Save data
	CALL	MOVE$BLOCK	; .
	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:	DB	'A','B'	; Floppy CP/M letters
		DB	'C','D'	; .
ACTIVE$TLEN:	EQU	$-ACTIVE$SDEV	; Active table length
ACTIVE$EOT:	DB	0,0,0,0	; End of table, spares

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:
;
; [DC.20]
;
; 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
	MVI	B,LB$LEN	; .  (length of bios area)
	CALL	MOVE$BLOCK	; .  (move routine)
	MVI	A,0		; Test CP/M version
	CALL	LB$GETNXT	; Get next jump table
	STA	LB$VERS		; 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
	MVI	B,LB$XLEN	; .  (length of extra table)
	CALL	MOVE$BLOCK	; .  (move routine)
	MVI	A,0FFH		; Set NZ to indicate bios
	ORA	A		; ... version 2.1+
	RET			; ... and return.


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.  The value 0FFH is ignored in the list of "OK" chrs
; and may be used as a place holder.
;
; 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:
	CPI	0FFH		; Place holder character?
	JZ	I$NEXT$PTR	; . Yes, bump pointers
	CMP	C		; Chrs match?
	JZ	I$CHR$OK	; . Yes, return
I$NEXT$PTR:
	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


MOVE$BLOCK:
;
; [DC.20]
;
; Move a block of memory (up to 256 bytes)
;
; Entry:
;	HL = source
; 	DE = destination
; 	B  = count
; Exit:
;	data moved
;
; Modifies: 
;	none
;
	PUSH	PSW		; Save registers
	PUSH	B		; .
	PUSH	D		; .
	PUSH	H		; .
M$NEXT$CHR:
	MOV	A,M		; Get next byte,
	STAX	D		; Save in destination.
	INX	H		; Increment pointer to source.
	INX	D		; Increment pointer to dest.
	DCR	B		; Decrement counter.
	JNZ	M$NEXT$CHR	; Loop back if we're not done.
	POP	H		; Restore registers
	POP	D		; .
	POP	B		; .
	POP	PSW		; .
	RET			; No more to do -- return.


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
;	--------------	------------------------------------
;	  (space)	Ignored
;	, (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	'@'		; Repeat existing cmd line?
	JNZ	P$NOT$REPEAT	; .  (check other chrs if not)
	PUSH	H		; .  Save oklist pointer
	LXI	H,INBUF		; .  Set command line pointer back
	INX	H		; .  .  to the beginning
	SHLD	CMD$LINE$PTR	; .  .
	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	' '		; Ignore spaces
	JZ	RE$PROMPT	; .
	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


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


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 . . .				*
*							*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * *

;
; All data after this point is after the end of the program
;

; 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$BIOS$TBL	; Length of bios table (20 entries)

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

LB$VERS		DS	1	; Bios version number

;
; Stack, I/O buffers, HEAP
;

OLDSP:	DS	2	; old stack pointer
	DS	63	;
STACK:	DS	1	; 31-level stack

INBUF:	DS	128	; Command line input buffer
INBUFL:	EQU	$-INBUF	; Input buffer length

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

HEAP:	EQU	$	; Next available area for data

	END

