
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*								*
*	HInit.asm				Version 1	*
*								*
*	Copyright (C) 1984,1985      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.2	EA.22	RJB	Changed space algorithm to be based on the
;			# of tracks required to avoid allocating an
;			extra block, therby overwriting the next
;			partition.
;
;  1.1	E8.22	RJB	Corrected calculation of lowest available
;			byte when Z3ENV is available.
;
;  1.0	E7.02	RJB	Hard disk init for PHYTAB, DPH, DPB, and 
;			drive initialization, if necessary.


; Program version, and current version date

VERS		EQU	12	; Current version
THIS$MONTH	EQU	10	; Today's month
THIS$DAY	EQU	22	; .       day
THIS$YEAR	EQU	85	; .       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	31	; 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)

LDIR80		EQU	0B0EDH		; LDIR (0edh,0b0h)
CPIR80		EQU	0B1EDH		; CPIR (0edh,0b1h)
INIR80		EQU	0B2EDH		; INIR (0edh,0b2h)
OTIR80		EQU	0B3EDH		; OTIR (0edh,0b3h)

SBCD80		EQU	043EDH		; SBCD (0edh,043h)
LBCD80		EQU	04BEDH		; LBCD (0edh,04bh)
SDED80		EQU	053EDH		; SDED (0edh,053h)
LDED80		EQU	05BEDH		; LDED (0edh,05bh)
SSPD80		EQU	073EDH		; SSPD (0edh,073h)
LSPD80		EQU	07BEDH		; LSPD (0edh,07bh)
SIXD80		EQU	022DDH		; SIXD (0ddh,022h)
LIXD80		EQU	02ADDH		; LIXD (0ddh,02ah)
SIYD80		EQU	022FDH		; SIYD (0fdh,022h)
LIYD80		EQU	02AFDH		; LIYD (0fdh,02ah)


; 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 AND 255

DJNZ		EQU	010H		; DJNZ addr
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)
FF		EQU	'L'-'@'		; Ctrl-L (Form feed, new pg)
CR		EQU	'M'-'@'		; Ctrl-M (Carriage return)
NAK		EQU	'U'-'@'		; Ctrl-U (Negative ack)
CAN		EQU	'X'-'@'		; Ctrl-X (Cancel)
EOF		EQU	'Z'-'@'		; Ctrl-Z (CP/M End-of-file)
ESC		EQU	1BH		; Ctrl-[ (Escape)
EXP		EQU	21H		; 	 (Exclamation point)
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
ZCPR3$TYPE:	DB	1 		; External ZCPR3 ENV
ZCPR3$PTR:	DW	0FE00H		; Pointer to ZCPR3 ENV

NAME$MSG:
	DB	0DH,'Ampro '
NAME:	DB	'Hard Disk System Initialization'; <-- Insert name here
	DB	00H,' Utility',CR,LF	; (Zero marks end-of-name)
	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	'Usage: HINIT scsiaddr ctrlr lun '
	DB	'[cyl,head,rwc,wpc, steprate] '
	DB	'cpmletter partition [,cpmletter partition] ...'
	DB	CR,LF,LF
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 Hard Disk System Initialization utility '
	DB	'prepares your Ampro 3.1+ bios '
	DB	'to access a hard disk unit.  The following '
	DB	'information is required to initialize the bios '
	DB	'for your hard disk unit:',CR,LF,LF
	DB	TAB,'Controller: SCSI address'
	DB	TAB,'* Drive: starting RWC cylinder',CR,LF
	DB	TAB,'Controller: type/model* '
	DB	TAB,'* Drive: starting WPC cylinder',CR,LF
	DB	TAB,'Drive: logical unit number'
	DB	TAB,'* Drive: step rate code',CR,LF
	DB	TAB,'* Drive: number of cylinders'
	DB	TAB,'CP/M drive letter',CR,LF
	DB	TAB,'* Drive: number of heads'
	DB	TAB,'Size (Kb) of each partition',CR,LF,LF
	DB	'(*) Only required on non-"SCSI generic" '
	DB	'Hard disk controllers',CR,LF,LF,LF
	DB	'NOTE: Your drive''s total formatted '
	DB	'capacity in Kbytes is:',CR,LF,TAB
	DB	'(CYLS-1) * HEADS * SECTORS per TRACK * 0.5',CR,LF
	DB	'where SECTORS per TRACK depends on your controller '
	DB	'and is usually 17 or 18.',CR,LF,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,
	DW	SSPD80,STACK	; .  then save old stack ptr.
	ENDIF

;
; Stuff the stack with our pointer and get the bios JMP table.
;

	LXI	SP,STACK	; Set up the stack pointer
	CALL	GET$BIOS$VERS	; Copy the jmp tbl to a local area

;
; Special first-time initialization goes here . . .
;

; Find the lowest Z3 address of the RCP, IOP, FCP, or NDR.  The CL,
; ENV, SH, PATH, MSG, FCB, STK, and WHL are all assumed to be either
; in low memory (below 0080H) or above the loadable packages.  The
; address found becomes the upper limit to the hard disk allocation
; vectors.  If no Z3 environment descriptor is found, the top of
; memory (0FFFFH) is returned as the upper limit to the hard disk
; allocation vectors.

	LHLD	ZCPR3$PTR	; Get pointer to Z3 stuff ...
	INX	H		; Bump to where 'Z3ENV' literal
	INX	H		; .  is supposed to be
	INX	H		; .
	LXI	D,Z3$CHECK$V	; Make sure this is a Z3 env
	MVI	B,5		; .  descriptor
CHECK$NEXT$Z3:
	LDAX	D		; .
	CMP	M		; .
	DB	JRNZ,NO$Z3-$-1	; Not equal, no Z3 env
	INX	D		; .
	INX	H		; .
	DB	DJNZ,CHECK$NEXT$Z3-$-1 and 255

	LXI	B,4		; Bump HL to RCP
	DAD	B		; .

	LXI	D,0FD00H	; Check the next four Z3 elements
	MVI	B,4		; .  (RCP, IOP, FCP, and NDR).  keep
NEXT$PACKAGE:			; .  the smallest non-zero value as
	PUSH	H		; .  the upper limit to the hard
	CALL	GET$HL$PTR	; .  disk allocation vectors.  We
	MOV	A,L		; .  assume that we are using at
	CMP	H		; .  least 0FD00H and up, as that
	DB	JRZ,SKIP0-$-1	; .  is the standard Ampro Z3 env.
	CALL	CMP$HL$DE	; .
	DB	JRNC,SKIP0-$-1	; .
	XCHG			; .
SKIP0:
	POP	H		; .
	INX	H		; .
	INX	H		; .
	INX	H		; .
	DB	DJNZ,NEXT$PACKAGE-$-1 and 255

	XCHG			; Save lowest byte found
	DB	JR,SAVE$Z3-$-1	; .

Z3$CHECK$V:
	DB	'Z3ENV'		; Z3 environment check vector

NO$Z3:
	LXI	H,0FFFFH	; Indicate the last available byte
SAVE$Z3:			; .  is the top of memory, and fall
	SHLD	LAST$BYTE	; .  throught to check bios version.
;
; Get the base of ALV storage
;
	CALL	LB$HD$INFO	; Get bottom of ALV storage
	CALL	GET$HL$PTR	; .
	SHLD	HDC$ALV		; .
;
; Compute the DPH base
;
	LHLD	1		; Get bios base from wboot vector
	MVI	L,80H		; Assume DPH starts at BIOS+80H
	SHLD	DPH$BASE	; Save it.
;
; 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
	DB	JRNC,MINBIOS-$-1; 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:
	LDA	BIOS$VERSION	; Get current bios version

	CPI	30		; Is it version 3?
	DB	JRC,CHECK2-$-1	; No, check for version 2

	; Bios version 3 init code ...

CHECK2:
	CPI	20		; Is it version 2?
	DB	JRC,TOPMENU-$-1	; No, assume version 1

	; Bios version 2 init code ...


;
; This is where you can jump to start the program over.
;

TOPMENU:
	LXI	SP,STACK	; Stuff SP with our stack.

;
; 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
	DB	JRNZ,CLRHD-$-1	; .  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		; .
CLRHD:
	LXI	D,CLEAR$HDC$MSG	; Clear prior hdc assignments?
	LXI	H,CLEAR$HDC$OKC	; .
	CALL	PROMPT		; .
	JZ	ALL$DONE	; .  (end if the ESC key pressed).
	CPI	'Y'		; .
	CZ	CLEAR$HDC	; .

AGAIN:
;
;  Program main loop . . .
;
	IF	CMD$LINE$OK	; If cmd line input is possible 
	LDA	CMD$LINE$CHRS	; Check for any input and skip
	ORA	A		; .  the current status messages
	DB	JRNZ,NOSTAT-$-1	; .  if any chrs in the cmd line.
	ENDIF

	CALL	SHOW$STAT	; Show "current drive" + partitions
NOSTAT:
	LXI	D,MAIN$MENU$MSG	; Get main choices
	LXI	H,MAIN$MENU$OKC	; .
	CALL	PROMPT		; .
	JZ	ALL$DONE	; .
	MOV	A,B		; .
	DCR	A		; .
	LXI	H,MAIN$MENU$EXE	; .
	JMP	GO$TABLE	; .

MAIN$MENU$EXE:
	DW	DEFINE$CDRIVE	; Define "Current Drive"
	DW	DEFINE$PART	; Define parition on "Current Drive"

DEFINE$CDRIVE
	LXI	D,SCSI$ADDR$MSG	; Get SCSI address
	LXI	H,SCSI$ADDR$OKC	; .
	CALL	PROMPT		; .
	JZ	ALL$DONE	; . ESC -- finished
	STA	CD$ID		; . (Save ID in msg)
	MOV	A,B		; .
	DCR	A		; .
	CALL	BIN$TO$SCSI	; .
	STA	SCSI$ADDR	; .

	LXI	D,HD$CTRL$MSG	; Get HD controller name
	LXI	H,HD$CTRL$OKC	; .
	CALL	PROMPT		; .
	JZ	DEFINE$CDRIVE	; . ESC -- top of this section
	STA	CD$TYPE		; . (Save type in msg)
	MOV	A,B		; .
	DCR	A		; .
	STA	HD$CTRL		; .

	LXI	D,DRV$LUN$MSG	; Get drive logical unit #
	LXI	H,DRV$LUN$OKC	; .
	CALL	PROMPT		; .
	JZ	DEFINE$CDRIVE	; . ESC -- back up 1 section
	STA	CD$LUN		; . (Save logical unit # in msg)
	MOV	A,B		; .
	DCR	A		; Convert 0-3 to SCSI lun
	RRC			; .
	RRC			; .
	RRC			; .
	ANI	1110$0000B	; .  mask all but bits 7-6-5
	STA	DRV$LUN		; .

	XRA	A		; Clear step rate value, in case
	STA	STEP$RATE	; .  we've got a non-Xebec HDC

	LDA	HD$CTRL		; Check for Xebec HDC
	CPI	NON$GENERIC	; If generic, then no need to
	DB	JRC,DCDONE-$-1	; .  get drive info ...

	LXI	D,DRV$CYLS$MSG	; Get # of cylinders
	CALL	PROMPT$DECIMAL	; .
	JZ	DEFINE$CDRIVE	; . ESC -- top of this section
	LXI	H,DRV$CYLS	; .
	MOV	M,D		; .
	INX	H		; .
	MOV	M,E		; .

	LXI	D,DRV$HEAD$MSG	; Get # of heads
	CALL	PROMPT$DECIMAL	; .
	JZ	DEFINE$CDRIVE	; . ESC -- top of this section
	STA	DRV$HEAD	; .

	LXI	D,DRV$RWC$MSG	; Get reduced write current cylinder
	CALL	PROMPT$DECIMAL	; .
	JZ	DEFINE$CDRIVE	; . ESC -- top of this section
	LXI	H,DRV$RWC	; .
	MOV	M,D		; .
	INX	H		; .
	MOV	M,E		; .

	LXI	D,DRV$WPC$MSG	; Get write precomp cylinder
	CALL	PROMPT$DECIMAL	; .
	JZ	DEFINE$CDRIVE	; . ESC -- top of this section
	LXI	H,DRV$WPC	; .
	MOV	M,D		; .
	INX	H		; .
	MOV	M,E		; .

	LDA	HD$CTRL		; Check for Xebec HDC
	CPI	NON$GENERIC	; .
	JNZ	DTC
	LXI	D,XEB$STEP$MSG	; Get step rate mode value
	LXI	H,XEB$STEP$OKC	; .
	CALL	PROMPT		; .
	JZ	DEFINE$CDRIVE	; . ESC -- top of this section
	MOV	A,B		; .
	DCR	A		; .
	STA	STEP$RATE	; .
	JMP	DCDONE		; .

DTC:
	LXI	D,DTC$STEP$MSG
	LXI	H,DTC$STEP$OKC
	CALL	PROMPT
	JZ	DEFINE$CDRIVE
	MOV	A,B
	DCR	A
	STA	STEP$RATE
	
DCDONE:
	CALL	INIT$HDC	; Initialize HDC for this drive ...
				; . (ok or error msg returned in DE)

	LXI	H,2		; Setup initial reserved tracks value
	SHLD	RESERVED	; .

	MVI	A,0FFH		; Mark drive
	STA	CDRIVE$OK	; .

	CALL	CENTER$OUTPUT	; Display either error or ok msg

	JMP	AGAIN		; Go back for more


DEFINE$PART:
	LDA	CDRIVE$OK	; Check for drive defined
	ORA	A		; .
	DB	JRNZ,DPOK-$-1	; Drive defined -- continue

	LXI	D,NO$DRIVE$MSG	; No drive defined -- display message
	LXI	H,NO$DRIVE$OKC	; .  and return to main menu.
	CALL	PROMPT		; .
	JMP	AGAIN		; .

DPOK:
	LXI	D,CPM$LTR$MSG	; Get CP/M drive letter
	DB	JR,GET$PART-$-1	; .
GET$PART$2:			; .
	LXI	D,CPM$LTR$MSG2	; .
GET$PART:			; .
	LXI	H,CPM$LTR$OKC	; .
	CALL	PROMPT		; .
	JZ	AGAIN		; . ESC -- next physical drive
	MOV	A,B		; .
	ADI	4		; . (bump 1-11 to 5-15)
	STA	CPM$LTR		; .
	ADI	'A'		; Put current CP/M drive letter
	STA	CPM$LTR$ECHO	; .  in prompt
	STA	DRIVE$INST	; .  and install message
GET$PART$AGAIN:
	LXI	D,PARTITION$MSG	; Get partition size (Kb)
	CALL	PROMPT$DEC$NOLF	; .
	DB	JRZ,GET$PART$2-$-1 and 255	; Go back for more
	MOV	E,A		; .
	ORA	D		; .
	DB	JRZ,GET$PART$AGAIN-$-1 and 255	; Zero, try again
	DW	SDED80,PARTITION; Save partition value

	CALL	ADD$PARTITION	; Add another drive partition
	JMP	AGAIN		; .

ALL$DONE:			; Exit the program ...
	IF	RTN$VIA$WB	; If we're returning via warm boot,
	JMP	LB$WBOOT	; .  then JUMP!
	ENDIF
	IF	NOT RTN$VIA$WB	; Otherwise,
	DW	LSPD80,STACK	; .  Get old stack ptr back.
	RET			; .  and return to cp/m
	ENDIF


* * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*							*
*	Routines specific to this program . . .		*
*							*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * *


CLEAR$HDC:
	CALL	LB$HD$INFO	; Get pointer to HD info
	PUSH	H		; Bump to absolute start
	INX	H		; .
	INX	H		; .
	MOV	E,M		; Get absolute start vector
	INX	H		; .
	MOV	D,M		; .
	POP	H		; Plug in current HD info
	MOV	M,E		; .
	INX	H		; .
	MOV	M,D		; .

	XRA	A		; Clear all "driver 3" entries
NXTDVR:
	PUSH	PSW		;
	CALL	LB$GET$LOGICAL	; 
	MOV	A,M		; 
	CPI	03H		; Driver 3?
	DB	JRNZ,BUMPD-$-1	; No,  skip it
	MVI	M,0		; Yes, clear it
BUMPD
	POP	PSW		; Bump to next unit
	INR	A		; .
	CPI	16		; done?
	DB	JRNC,NXTDVR-$-1 and 255	; No,  do another

	CALL	LB$HD$INFO	; Reset pointer to ALV
	CALL	GET$HL$PTR	; .
	SHLD	HDC$ALV		; .

	LXI	D,HDC$CLR$DONE	; Tell 'em we cleared prior assignments
	CALL	CENTER$OUTPUT	; .

	RET


SHOW$STAT:
	RET


DO$SCSI:
	INX	H
	LDA	DRV$LUN
	ORA	M
	MOV	M,A
	DCX	H
	LDA	SCSI$ADDR
	CALL	LB$SCSIDRV
	ORA	A
	RET


INIT$HDC:
	LDA	HD$CTRL		; Init HDC for this drive
	LXI	H,HDC$INIT$EXE	; .
	JMP	GO$TABLE	; .

HDC$INIT$EXE:
	DW	NO$INIT$NEEDED	; Generic SCSI HDC (burst mode)
	DW	INIT$SHUGART	; Generic SCSI HDC (byte mode)
	DW	INIT$XEBEC	; Xebec 1410(A)
	DW	INIT$DTC	; Data Technology 500 Series

NO$INIT$NEEDED:
	LXI	D,CD$MSG	; Just set message
	RET

INIT$SHUGART:
	CALL	LB$HD$INFO	; Get HD info pointer
	LXI	D,4		; Bump to byte/block ptr
	DAD	D		; .
	CALL	GET$HL$PTR	; Get pointer in HL
	MVI	M,1		; Set byte mode
	LXI	D,CD$MSG	; Set A-OK message
	RET

INIT$XEBEC:
	LXI	D,X$ID		; Where to put the fmt data
	LXI	H,DRV$CYLS	; .
	LXI	B,7		; .
	DB	0EDH,0B0H	; .  (LDIR)

	LXI	H,X$IC		; Init Drive Characteristics
	LXI	D,X$ID		; .
	CALL	DO$SCSI		; .
	LXI	D,CD$MSG	; Init ok -- load A-OK msg
	RZ			;
	LXI	D,INIT$ERR$MSG	; Init failed -- load error msg
	RET

X$IC:	DB	0CH
	DB	0,0,0,0,0
X$ID:	DB	0,0,0,0,0,0,0,11

INIT$DTC:
	LHLD	DRV$CYLS
	SHLD	D$CYL
	LDA	DRV$RWC+1
	STA	D$RWC
	LDA	DRV$HEAD
	DCR	A
	STA	D$HD
	LDA	STEP$RATE
	MOV	E,A
	MVI	D,0
	LXI	H,D$TRAN$TBL
	DAD	D
	MOV	A,M
	STA	D$STPR
	XRA	A
	STA	STEP$RATE
	LXI	H,D$MSC
	LXI	D,D$MSD
	CALL	DO$SCSI
	LXI	D,CD$MSG	; Init ok -- load A-OK msg
	RZ			;
	LXI	D,INIT$ERR$MSG	; Init failed -- load error msg
	RET

D$TRAN$TBL:
	DB	60
	DB	4
	DB	3
	DB	2
	DB	1

D$MSC:	DB	0C2H,0,0
	DB	0,0,0
D$MSD:	DB	11
D$STPR:	DB	0
	DB	0
D$HD:	DB	0
D$CYL:	DB	0,0
D$RWC:	DB	0,0,0


ADD$PARTITION:
	LHLD	PARTITION	; Get partition size (in K)
	CALL	HL$DIV$2	; Divide K by 8 to get number
	CALL	HL$DIV$2	; .  of tracks in this partition
	CALL	HL$DIV$2	; .
	SHLD	NTRACKS		; .
	DAD	H		; Multiply tracks by 2 to get #
	SHLD	NBLOCKS		; .  of blocks in this partition

	LHLD	NBLOCKS		; Compute the number of ALV bytes
	LXI	B,7		; .  required for this partition
	DAD	B		; .
	CALL	HL$DIV$2	; . (  the actual formula is:  )
	CALL	HL$DIV$2	; . ( ALVB = [NBLOCK + 7] / 8  )
	CALL	HL$DIV$2	; .
	SHLD	ALVBYTES	; Allocation vector size (in bytes)

	DW	LBCD80,HDC$ALV	; Get ALV base in BC
	DAD	B		; Add to HL
	DW	LDED80,LAST$BYTE; Compare to last available byte
	CALL	CMP$HL$DE	; .
	DB	JRC,ENOUGH-$-1	; Enough room?

	LXI	D,NOT$ENOUGH$RM	; No ... tell 'em.
	CALL	JUSTIFY		; .
	CALL	RET$TO$CONT	; .
	RET

ENOUGH:
;
; Update PHYTAB ...
;
	LDA	CPM$LTR		; Get data for this logical drive
	CALL	LB$GETLOGICAL	; .
	MVI	M,03H		; Set Hard Disk driver
	INX	H		; .
	MOV	A,M		; Get DPH offset
	ANI	0F0H		; .
	STA	DPHOFS		; .  (save in order to update DPH)
	MOV	B,A		; Or in step rate code
	LDA	STEP$RATE	; .
	ANI	00FH		; .
	ORA	B		; .
	MOV	M,A		; Save as DPH/step rate
	INX	H		; .
	LDA	DRV$LUN		; .
	ORI	0AH		; Set to 512 byte sectors, 4K AU
	MOV	M,A		; .
	INX	H		; .
	LDA	SCSI$ADDR	; Update SCSI address
	MOV	M,A		; .
;
; Update DPH
;
	LHLD	DPH$BASE	; Get DPH base
	LDA	DPHOFS		; Add offset for this unit
	MOV	C,A		; .
	MVI	B,0		; .
	DAD	B		; .
	LXI	B,0AH		; Add offset to DPB
	DAD	B		; .
	SHLD	HDC$UNIT$DPB	; .
	INX	H		; Bump to CKV
	INX	H		; .
	DW	LDED80,HDC$ALV	; Get allocation vector to DE
	MOV	M,E		; Save in CKV
	INX	H		; .
	MOV	M,D		; .
	INX	H		; Save in ALV
	MOV	M,E		; .
	INX	H		; .
	MOV	M,D		; .
	INX	H		; .

	LHLD	ALVBYTES	; Add ALV to ALVBYTES
	DAD	D		; .
	SHLD	HDC$ALV		; Save updated ALV base
	XCHG			; .
	CALL	LB$HD$INFO	; Get pointer in BIOS to ALV base
	MOV	M,E		; Save updated ALV base in BIOS
	INX	H		; .
	MOV	M,D		; .
;
; Update HDC DPB ...
;
	LHLD	HDC$UNIT$DPB	; Get ptr to unit DPB ptr back
	CALL	GET$HL$PTR	; Get actual unit DPB ptr in HL
	PUSH	H		; .
	LXI	B,5		; Bump to disk size
	DAD	B		; .
	DW	LDED80,NBLOCKS	; Save # of blocks (CP/M wants
	DCX	D		; .  [blocks - 1] stored here)
	MOV	M,E		; .
	INX	H		; .
	MOV	M,D		; .
	POP	H		; .
	LXI	B,13		; Bump to reserved tracks
	DAD	B		; .
	DW	LDED80,RESERVED	; Save reserved tracks
	MOV	M,E		; .
	INX	H		; .
	MOV	M,D		; .
	LHLD	NTRACKS		; Update reserved tracks
	DAD	D		; .
	SHLD	RESERVED	; .
;
; Display "successful" message
;
	XRA	A		; Clear carry
	LHLD	HDC$ALV		; Compute space remaining
	XCHG			; .
	LHLD	LAST$BYTE	; .
	DB	0EDH,052H	; SBC HL,DE
	DCX	H		; .
	XCHG			; Convert to decimal
	LXI	H,SPACE$LEFT$DEC; .
	CALL	DE$TO$HL$DEC	; .
	LXI	D,SPACE$LEFT	; .
	CALL	CENTER$OUTPUT	; .
	RET


* * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*							*
*	      	Misc data and messages . . .		*
*							*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * *


CLEAR$HDC$MSG:	DB	'Do you want to clear the existing hard '
		DB	'disk assignments (Y/N)? ','$'
CLEAR$HDC$OKC:	DB	'YN',0

HDC$CLR$DONE:	DB	'*** Previous hard disk assignments have '
		DB	'been cleared ***',CR,LF,'$'

MAIN$MENU$MSG:	DB	CR,LF,LF
		DB	'Options available:',CR,LF
		DB	TAB,' (D)  Define the Current Drive',CR,LF
		DB	TAB,' (A)  Add a partition to the '
		DB	'Current Drive',CR,LF,LF
		DB	TAB,'(ESC) Exit the program',CR,LF,LF
		DB	'What next (D/A/ESC)? ','$'
MAIN$MENU$OKC:	DB	'DA',0

SCSI$ADDR$MSG:	DB	CR,LF,LF,LF,LF,LF
		DB	'Defining the Current Drive ...'
		DB	CR,LF
		DB	'======================================'
		DB	CR,LF
		DB	LF,LF
		DB	'The Current Drive is the physical hard '
		DB	'disk drive you are currently working '
		DB	'with.  The definition of the drive '
		DB	'consists of the SCSI ID and type of '
		DB	'the hard disk controller connected to '
		DB	'the drive, '
		DB	'the logical unit number (LUN) of the '
		DB	'drive, and the drive characteristics if '
		DB	'you are using a Xebec 1410(A) controller.'
		DB	CR,LF,LF,'Hard disk controller SCSI ID:'
		DB	CR,LF,'-----------------------------'
		DB	CR,LF,LF
		DB	'Each hard disk controller must be set '
		DB	'to one of the eight SCSI bus ID''s.  These '
		DB	'ID''s range from zero (0) to seven (7).  '
		DB	'If you have only one hard disk controller '
		DB	'in your system, it''s ID is usually zero '
		DB	'(0).  If you have more than one hard disk '
		DB	'controller, make sure they are set to '
		DB	'different ID''s.',CR,LF,LF
		DB	'What is the SCSI ID of the current drive''s '
		DB	'controller (0-7, ESC to quit)? ','$'
SCSI$ADDR$OKC:	DB	'01234567',0

HD$CTRL$MSG:	DB	CR,LF,'Hard disk controller type:'
		DB	CR,LF,'--------------------------'
		DB	CR,LF,LF
		DB	'Of the following controllers ...',CR,LF,LF
		DB	TAB,'1 - Generic burst-mode SCSI controller '
		DB	'(Adaptec ACB4000, Xebec Owl)',CR,LF
		DB	TAB,'2 - Generic byte-mode SCSI controller '
		DB	'(Shugart 1610-4)',CR,LF
		DB	TAB,'3 - Xebec 1410 or 1410A',CR,LF
		DB	TAB,'4 - Data Technology 500 Series',CR,LF
		DB	LF
		DB	'Which one is the Current Drive connected '
		DB	'to (1, 2, 3, or 4)? ','$'
HD$CTRL$OKC:	DB	'12'
NON$GENERIC:	EQU	$-HD$CTRL$OKC
		DB	'34',0

DRV$LUN$MSG:	DB	CR,LF,'Current Drive logical unit number:'
		DB	CR,LF,'----------------------------------'
		DB	CR,LF,LF
		DB	'Each disk connected to a hard disk '
		DB	'controller has a unique number, called '
		DB	'the Logical Unit Number, or LUN.  '
		DB	'The first LUN on a hard disk '
		DB	'controller is zero (0), and the numbers '
		DB	'increase at that point to a maximum of '
		DB	'seven (7).  Most hard disk '
		DB	'controllers can support two drives, which '
		DB	'means the LUN''s for the controller are '
		DB	'either zero (0) or one (1).  If only '
		DB	'one drive is connected, the logical '
		DB	'unit number (LUN) is normally zero (0).  '
		DB	CR,LF,LF
		DB	'What is the Current Drive''s logical '
		DB	'unit number (0-7)? ','$'
DRV$LUN$OKC:	DB	'01234567',0

DRV$CYLS$MSG:	DB	CR,LF
		DB	'For the controller you indicated, '
		DB	'the following additional information on '
		DB	'the Current Drive is required:'
		DB	CR,LF,LF
		DB	'      Number of cylinders? ','$'
DRV$HEAD$MSG:	DB	'          Number of heads? ','$'
DRV$RWC$MSG:	DB	'Starting cylinder for RWC? ','$'
DRV$WPC$MSG:	DB	'Starting cylinder for WPC? ','$'

XEB$STEP$MSG:	DB	CR,LF
		DB	'Xebec 1410(A) step mode (choose from the '
		DB	'following table):',CR,LF
		DB	TAB,'0 -   3ms step',CR,LF
		DB	TAB,'4 - 200us buffered step',CR,LF
		DB	TAB,'5 -  70us buffered step',CR,LF
		DB	TAB,'6 -  30us buffered step',CR,LF
		DB	TAB,'7 -  15us buffered step',CR,LF
		DB	'Which step mode (0, 4, 5, 6, or 7)? ','$'
XEB$STEP$OKC:	DB	'0',0FFH,0FFH,0FFH,'4567',0

DTC$STEP$MSG:	DB	CR,LF
		DB	'Data Technology 500 Series step mode '
		DB	'(choose from the '
		DB	'following table):',CR,LF
		DB	TAB,'0 -   3ms step',CR,LF
		DB	TAB,'1 - 200us buffered step',CR,LF
		DB	TAB,'2 - 150us buffered step',CR,LF
		DB	TAB,'3 - 100us buffered step',CR,LF
		DB	TAB,'4 -  50us buffered step',CR,LF
		DB	'Which step mode (0, 1, 2, 3, or 4)? ','$'
DTC$STEP$OKC:	DB	'01234',0

NO$DRIVE$MSG:	DB	'Please define the Current Drive before '
		DB	'adding a partition to it.',CR,LF
		DB	'Press the RETURN key to continue. ','$'
NO$DRIVE$OKC:	DB	CR,LF,0

CPM$LTR$MSG:	DB	CR,LF,LF
		DB	'Add a CP/M partition '
		DB	'on the Current Drive ...',CR,LF
		DB	'========================'
		DB	'========================'
		DB	CR,LF
CPM$LTR$MSG2:	DB	CR,LF
		DB	'CP/M letter to use for this partition '
		DB	'(F-P, ESC for new Current Drive)? ','$'
CPM$LTR$OKC:	DB	'FGHIJKLMNOP',0

PARTITION$MSG:	DB	CR,LF
		DB	'Size of the '
CPM$LTR$ECHO:	DB	'x: partition (in K bytes)? ','$'

NOT$ENOUGH$RM:	DB	CR,LF,'There is not enough space left to '
		DB	'install a hard disk partition of the '
		DB	'size you indicated.'
		DB	CR,LF,'$'

INIT$ERR$MSG:	DB	'*** Cannot initialize HDC for the Current '
		DB	'Drive -- check you disk parameters. ***'
		DB	CR,LF,'$'

CD$MSG:		DB	CR,LF,'*** Current Drive set to: SCSI ID ['
CD$ID:		DB	'x], HDC type: ['
CD$TYPE:	DB	'x], logical unit ['
CD$LUN:		DB	'x]. ***',CR,'$'


SPACE$LEFT:	DB	CR,LF,'<<< Drive '
DRIVE$INST:	DB	'x: installed -- '
SPACE$LEFT$DEC:	DB	'xxxxx bytes of bios buffer area '
		DB	'remaining. >>>',CR,'$'

SCSI$ADDR:	DB	0
HD$CTRL:	DB	0	
DRV$LUN:	DB	0

DRV$CYLS:	DW	0
DRV$HEAD:	DB	0
DRV$RWC:	DW	0
DRV$WPC:	DW	0

STEP$RATE:	DB	0
CPM$LTR:	DB	0
PARTITION:	DW	0

LAST$BYTE:	DW	0
RESERVED:	DW	0
HDC$INIT:	DB	0

NBLOCKS:	DW	0
NTRACKS:	DW	0
ALVBYTES:	DW	0

DPHOFS:		DB	0
HDC$UNIT$DPB:	DW	0

HDC$ALV:	DW	0	; HDC CSV & HDC ALV
DPH$BASE:	DW	0

CDRIVE$OK	DB	0


* * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*							*
*		Library routines . . .			*
*							*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * *


A$TO$HL$HEX:
;
; [DC.27]
;
; Converts the number in A to the hex digits in HL
;
; Entry:
; 	A  = number to convert
;
; Exit:
;	HL = the hex equivalent of the number (L=high, H=low)
;		(use shld to store the converted number)
;
; Modifies: none
;
	PUSH	PSW		; Save original number
	RRC			; Get high nybble
	RRC			; .
	RRC			; .
	RRC			; .
	ANI	0FH		; .
	CALL	A$TO$HEX	; Convert to hex
	MOV	L,A		; Save in L register
	POP	PSW		; Get original number back
	PUSH	PSW		; Save again for later
	ANI	0FH		; Get low nybble
	CALL	A$TO$HEX	; Convert to hex
	MOV	H,A		; Save in H register
	POP	PSW		; Get original number back
	RET			; and return

A$TO$HEX:			; Convert A to a hex digit
	CPI	0AH		; If 0-9, we don't need to 
	JM	A$TO$HEX$2	; .  add any offset
	ADI	07H		; Offset for A-F
A$TO$HEX$2:			; .
	ADI	30H		; ASCII bias
	RET			; and return


BIN$TO$SCSI:
;
; [E6.10]
;
; Converts binary 0-7 to SCSI address 
;
; Entry:
;	A  = number to convert (0-7)
;
; Exit:
;	A  = converted SCSI address    [00H = error]
;
; Modifies: B
;
	ANI	07H		; Mask out all but addrs 0-7
	INR	A		; Bump A to shift at least one bit
	MOV	B,A		; .  and move to the B register
	XRA	A		; Clear A register
	STC			; Set carry for shift
BIN$NEXT$BIT:
	RAL			; Shift left one bit
	DCR	B		; Decrement count
	DB	JRNZ,BIN$NEXT$BIT-$-1 and 255
	RET			; Z = all done


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


CMP$HL$DE:
;
; [E6.10]
;
; Compare register pair HL with register pair DE.
;
; Entry:
;	DE,HL = data to compare
;
; Exit:
;	Z: DE = HL, NZ: DE # HL
;	C: HL < DE, NC: HL >= DE
;
; Modifies:
;	PSW
;
	MOV	A,H		; Compare high bytes
	CMP	D		; .
	RNZ			; If not zero, flags are set
	MOV	A,L		; Compare low bytes
	CMP	E		; .
	RET			; Return with flags set


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


DE$TO$HL$DEC:
;
; [E6.25]
;
; Convert the 16-bit number in the DE register pair to a 5-digit
; decimal number.  Store this number starting in the memory pointed
; to by the HL register pair.  Optionally convert leading zeroes to
; blanks.
;
; Call DE$TO$HL$DEC   to convert leading zeroes to blanks
; Call DE$TO$HL$DEC$0 to leave leading zeroes alone
;
; Entry:
;	DE = number to convert
;	HL = ptr to target memory location
;
; Exit:
;	DE,HL unchanged
;
; Modifies:
;	PSW
;
	MVI	A,1		; Set "clear leading 0's" mode
	DB	JR,E$DETOHL-$-1	; Jump to entry point
DE$TO$HL$DEC$0:
	XRA	A		; Set "leave leading 0's alone" mode
E$DETOHL:
	STA	DE$TO$HL$MODE	; Save mode flag
	PUSH	B		; Save registers
	PUSH	D		; .
	PUSH	H		; .
	LXI	B,-10000	; Convert ten-thousands digit
	CALL	TODEC		; .
	LXI	B,-1000		; Convert thousands digit
	CALL	TODEC		; .
	LXI	B,-100		; Convert hundreds digit
	CALL	TODEC		; .
	LXI	B,-10		; Convert tens digit
	CALL	TODEC		; .
	MOV	A,E		; Convert ones digit
	ADI	'0'		; .  (Leave zero intact)
	MOV	M,A		; .
	XRA	A		; Clear PSW
	POP	H		; Restore registers
	POP	D		; .
	POP	B		; .
	RET			; and return ...

TODEC:
	MVI	A,'0'		; Start with an ASCII zero
	PUSH	H		; Save target string pointer
	XCHG			; Move number to convert to HL
TODEC1:
	MOV	E,L		; Save a copy of current HL in DE
	MOV	D,H		; .  (in case we're done)
	INR	A		; Bump digit
	DAD	B		; Add "negative" BC to HL
	DB	JRC,TODEC1-$-1 and 255	; Continue while Carry set
	DCR	A		; Get rid of extra bump
	POP	H		; Restore target string pointer
	MOV	M,A		; Save digit
	CPI	'0'		; Is the digit an ascii zero?
	DB	JRNZ,NOZERO-$-1	; No,  Turn off leading 0's flag
	LDA	DE$TO$HL$MODE	; Yes, Check leading 0's flag
	ORA	A		; Convert leading 0's to blanks?
	DB	JRZ,NOBLANK-$-1	; No,  Leave digit alone
	MVI	M,' '		; Yes, Change digit to a blank
	DB	JR,NOBLANK-$-1	; All done for now
NOZERO:
	XRA	A		; Turn off leading 0's flag
	STA	DE$TO$HL$MODE	; .
NOBLANK:
	INX	H		; Bump digit pointer
	RET			; and return ...

DE$TO$HL$MODE:	DB	0	; "Convert 0's to blanks" flag


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


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


HL$DIV$2:
	PUSH	PSW
	XRA	A
	MOV	A,H
	RAR
	MOV	H,A
	MOV	A,L
	RAR
	MOV	L,A
	POP	PSW
	RET


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:
;
; [E6.11]
;
; 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 E6.11, 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
	DB	JR,E$JUSTIFY-$-1; 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
	CPI	CR		; CR?
	DB	JRNZ,J$NO$R-$-1	; .
	CALL	J$FLUSH$LINE	; .  Flush output line,
	MVI	A,CR		; .  Output CR,
	CALL	CON$CHR		; .  .
	INX	H		; .  bump ptr & check next
	DB	JR,J$NEXT$LINE-$-1 and 255
J$NO$R:
	CPI	LF		; LF?
	DB	JRNZ,J$NO$L-$-1	; .
	CALL	CON$CHR		; .  Output LF,
	INX	H		; .  bump ptr & check next
	DB	JR,J$CHECK$CHR-$-1 and 255
J$NO$L:
	CPI	FF		; FF?
	DB	JRNZ,J$NO$F-$-1	; .
	CALL	CLEAR$SCREEN	; .  Clear screen,
	INX	H		; .  bump ptr & check next
	DB	JR,J$CHECK$CHR-$-1 and 255
J$NO$F:
	CPI	'$'		; End of string?
	DB	JRNZ,J$NO$S-$-1	; .
	CALL	J$FLUSH$LINE	; .  Flush output line, and
	RET			; .  return to caller.
J$NO$S:
	STAX	D		; Not a special chr, save in buffer

	CPI	' '		; Blank?
	DB	JRNZ,J$NO$B-$-1	; .  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$NO$B:
	INR	B		; Increment counter
	INX	H		; .  and input 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
	ORA	A		; .  right-justify flag is non-zero
	CM	J$ADD$BLANKS	; .
	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	' '		; .
	DB	JRZ,J$SKIP$BLANKS-$-1 and 255
	JMP	J$NEXT$LINE	; and check next segment

J$FLUSH$LINE:			; Flush line when CR or EOS encountered
	MVI	A,'$'		; Plug current position with EOS ($)
	STAX	D		; .
	CALL	J$SEND$BUFFER	; Send this line of data
	RET			; and return

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:
;
; [E6.14]
;
; 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:
;	PSW, BC
;
	MVI	A,01H		; Set CRLF after chr
	DB	JR,E$PROMPT-$-1	; Jump to entry point
PROMPT$NOLF:
	MVI	A,00H		; Set no CRLF after chr
E$PROMPT:
	STA	PROMPT$MODE	; Save prompt mode flag
	SHLD	USER$OKLIST	; .  and user 'ok' chr list
RE$PROMPT:
	LDA	CMD$LINE$CHRS	; Are there any characters left from

	ORA	A		; .  the command line?
	DB	JRZ,P$DISP-$-1	; No,  dsp text & get chr from bios
	DCR	A		; Yes, reduce # of chrs by one
	STA	CMD$LINE$CHRS	; .
	LHLD	CMD$LINE$PTR	; Get command line character
	MOV	A,M		; .
	INX	H		; .
	SHLD	CMD$LINE$PTR	; .

	CPI	' '		; Ignore spaces
	DB	JRZ,RE$PROMPT-$-1 and 255

	CPI	'@'		; @ = repeat cmd line
	DB	JRNZ,P$NO$R-$-1	; .
	LXI	H,INBUF+2	; Set command line pointer back
	SHLD	CMD$LINE$PTR	; .  to the beginning
	MVI	A,07FH		; Set count of chrs to 127
	STA	CMD$LINE$CHRS	; .  (the most it could be)
	DB	JR,RE$PROMPT-$-1 and 255
P$NO$R:
	CPI	','		; Change ',' to CR
	DB	JRNZ,P$NO$C-$-1	; .
	MVI	A,CR		; .
P$NO$C:
	CPI	'.'		; Change '.' to ESC
	DB	JRNZ,P$NO$D-$-1	; .
	MVI	A,ESC		; .
P$NO$D:
	CPI	'_'		; Underscore = prompt anyway
	DB	JRZ,P$DISP-$-1	; .

	CALL	TO$UPPER	; Convert the chr to upper case
	LHLD	USER$OKLIST	; Get the user ok chr list
	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:
	CALL	JUSTIFY		; and call justify routine
P$TRY$AGAIN:
	CALL	CONIN$NE$XC	; Console input, no echo, except ^C
	CALL	TO$UPPER	; Convert the chr to upper case
	LHLD	USER$OKLIST	; Get the user ok chr list
	CALL	IS$IT$OK	; Check the chr against the ok list
	JM	P$TRY$AGAIN	; Not there, try again
	CNZ	CON$CHR		; Display chr we found
	PUSH	PSW		; Check mode flag in case we need
	LDA	PROMPT$MODE	; .  to send a CR+LF after the
	DB	BIT,BTST+B0+ZA	; .  user's input
	CNZ	DO$CRLF		; .
	POP	PSW		; .
	RET			; and return

PROMPT$MODE:	DB	0	; Prompt mode flag
USER$OKLIST:	DW	0	; User 'ok' chr list

; end of PROMPT routine


PROMPT$DECIMAL:
;
; [E6.11]
;
; Prompt the user for a decimal input of up to 5 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:
;	DE = Value entered by the user, 0-0FFFFH
;	A  = Low byte of value entered by the user, 0-0FFH
;
;	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
	PUSH	B		; .  and the BC register, too.
	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	5		; .
	JC	ADD$DIGIT	; 5 digits or less, add to string
	LXI	D,BLOT		; More than 5 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$DE	; Convert string to DE register
	MVI	A,0FFH		; Insure NZ flag
	ORA	A		; .
	MOV	A,E		; Move low byte to A
	PUSH	PSW		; Check mode flag to see if a
	LDA	PROMPT$D$MODE	; .  CR+LF should be sent after
	ORA	A		; .  the user's input
	CNZ	DO$CRLF		; .
	POP	PSW		; .
ESCRTN:
	POP	B		; Get old BC register back
	POP	H		; .  and original HL, as well.
	RET			; and return with result in HL & A

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 5 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


STR$TO$A:
;
; [E5.26]
;
; Converts the string pointed to by HL to a number in the A reg.
; 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 255, inaccurate
; results will occur.
;
; Entry:
;	HL = ptr to string
;
; Exit:
;	A  = value of string in HL
;	HL = next character to process
;
; Modifies:
;	A, HL
;
	PUSH	B		; We need the BC register
	XRA	A		; Clear totals
STA$NEXT$CHR:
	MOV	C,A		; Save results so far . . .
	MOV	A,M		; Get chr
	CPI	'0'		; Less than '0'?
	DB	JRC,NA$DIG-$-1	; Yes, finished
	CPI	'9'+1		; Greater than '9'
	DB	JRNC,NA$DIG-$-1	; Yes, finished
	SUI	'0'		; No,  convert to 0-9
	MOV	B,A		; Save this digit
	XRA	A		; Multiply previous by 10
	ADD	C		; x1
	ADD	A		; x2
	ADD	A		; x4
	ADD	C		; x5
	ADD	A		; x10
	ADD	B		; Add in new digit
	INX	H		; Bump to next chr
	JMP	STA$NEXT$CHR	; Go back for another digit
NA$DIG:
	MOV	A,C		; Get totals
	ORA	A		; Set Z/NZ flag
	POP	B		; Get old BC reg back
	RET			; Return with results in A.


STR$TO$DE:
;
; [E5.26]
;
; Converts the string pointed to by HL to a number in the DE reg.
; 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 65535 (64K),
; inaccurate results will occur.
;
; Entry:
;	HL = ptr to string
;
; Exit:
;	DE = value of string in HL
;	HL = next character to process
;
; Modifies:
;	DE, HL
;
;
	PUSH	PSW		; We need the A register
	PUSH	B		; .  and the BC register
	LXI	D,0		; Clear totals
STN$NEXT$CHR:
	PUSH	D		; Save results so far . . .
	POP	B		; .
	MOV	A,M		; Get 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'		; No,  convert to 0-9
	MOV	E,A		; Save this digit
	MVI	D,0		; .
	PUSH	H		; Save ptr to input string
	LXI	H,0		; Multiply previous by 10
	DAD	B		; x1
	DAD	H		; x2
	DAD	H		; x4
	DAD	B		; x5
	DAD	H		; x10
	DAD	D		; Add in new digit
	XCHG			; Save results 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:
	POP	B		; Get old BC reg back
	POP	PSW		; .  and old A reg as well.
	RET			; Return with results in 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 (use DS or EQU)		*
*							*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * *

; 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

LB$XTBL:			; 'Extra' table definitions ...
LB$SWAP$DRV	DS	3	; Swap two logical drives
LB$HD$INFO	DS	3	; Get HD pointers
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

		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	$-OUTBUF; Output buffer length

HEAPPTR:	DS	2	; Pointer to next area
HEAPLEN:	DS	2	; Length of next area
HEAP:		EQU	$	; Start of heap

		END	START

