; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; *								*
; *  AMPRO Computers, Inc.			BIOS Version 3  *
; *								*
; *	        Copyright (C) 1983,1984,1985,1986               *
; *                    AMPRO Computers, Inc.                    *
; *			All rights reserved.			*
; *								*
; *  This BIOS is designed for use with AMPRO hardware only.	*
; *  All other use is prohibited without prior written consent	*
; *  from AMPRO Computers, Inc.					*
; *								*
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
;
; Floppy-related features:
;
;   o	Automatic sensing of 4 AMPRO formats
;
;   o	Automatic 48tpi (double-stepping) support in 96tpi drives.
;	WARNING:  writes are allowed, but not recommended.
;
;   o	"E-drive" supports wide variety of non-AMPRO formats
;
;   o	Console beeps on attempt to read/write a drive without a
;	floppy in it, but only on first access since CTRL-C.
;
;   o	A step rate per physical drive is now supported, and 2ms and
;	3ms step rates are supported with the 1772 FDC.
;
;   o	8" data rates implemented on a per logical drive basis.
;	NOTE: Most 1770/1772 FDC chips cannot support this rate.
;
; Hard-disk related features:
;
;   o	Hard disk support is SCSI generic and installed by HINIT
;	as a run-time option, or by boot EPROM after use of HGEN.
;
;   o	Bus (SCSI) arbitration is an assembly-time option.
;
;   o	Each hard disk partition has its own DPB, which allows
;	flexible partitioning of any disk drive, up to 88Mb.
;
;   o	A direct SCSI call is available as an extended BIOS JMP.
;
;   o	A SCSI burst/byte option allows some controllers to 
;	operate at optimum rates.  Set by HINIT.
;
; Other features:
;
;   o	Generic ZCPR3 support.  Easily extendable via the AMPRO 
;	EXTENDED ZCPR3 PACKAGES. 
;
;   o	Console input buffer, via software polling.
;
;   o   Real time clock, via software polling.
;
;	*	*	*	*	*	*	*	*
;
;  Revision history:
;
;  Ver	Date	Who	Description
;  ---	-----	---	------------------------------------------
;  3.8  F4.10   FSW	Now include Initiator SCSI ID in select.
;			Added SCSI I/O retries.
;  			Added buffered console and RTC options.
;			Added local stack for above two options.
;			Fixed problem in SELEND routine. 
;			Various speed ups and clean ups. 
;			BEEP is now an option.
;			improved WBOOT option for ZRDOS+.
;			Now clear PUBLIC bytes for ZRDOS+.
;
;  3.5 - 3.7		Internal unreleased versions
;
;  3.4	EA.30	RJB	Moved SCSI mode byte out of buffer area.
;		RBL	Added bios hooks for ZCPR3 IOP.  Added
;			floppy control parameters to cold boot
;			code.  Set "startup" as standard cold boot
;			command (AUTOCMD).  Modified SCSI routines
;			to provide better internal status when in
;			a multi-master environment.  Changed SCSI
;			bus reset to assembly option (new SCSI 
;			boot EPROM does it -- if BIOS does it
;			again, problems can occur.)
;
;  3.3	E8.21	RJB	Changed RESTORE logic to wait longer for
;			the FDC to setup.  Corrected sector number
;			problem with Kaypro 4/10 format.  Added
;			routine to set memory from the end of BIOS
;			thru 0FFFFH to zero in order to support
;			ZCPR3 resident system segment options (RCP
;			FCP, IOP, etc).
;
;  3.2	E7.30	RJB	Removed 2 nested "IF" statements in the 
;			hard disk section which generated extra 
;			(unused) code in the floppy-only version.
;			NO FUNCTIONAL CHANGES.
;
;  3.1	E7.02	RJB	Production release of version 3 bios.
;
;  3.0	E5.21	RJB	Beta release of version 3 bios.
;
;	*	*	*	*	*	*	*	*
; Bios version and date

VERS		EQU	38	; Current version
THIS$MONTH	EQU	04	; Today's month
THIS$DAY	EQU	11	;	  day
THIS$YEAR	EQU	86	;	  year

INT$REV		EQU	02	; Internal revision number

; TRUE and FALSE

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

; Customization equates:

	; The following equate determines the amount of system memory 
	; available for hard disk buffers and other options:
MSIZE		EQU	58
	; 60K = Floppy only (must set ZCPR3 and/or HARD$DISK to NO)
	; 59K = Floppy + 10Mb Hard
	; 58K = Floppy + 42Mb Hard
	; 57K = Floppy + 74Mb Hard
	; 56K = Floppy + 88Mb Hard

ZCPR3           EQU	YES		; enable ZCPR3?
	;The following equate is YES, the BDOS will not be reloaded
	;by warm boot.  Great with ZRDOS version 1.4 and higher.
ONLY$WBOOT$CCP  EQU	YES		; do not warm boot BDOS
	;The following equate should be normally set to NO.
MAX$DRIVE	EQU	NO		; enable max drive checking?
HARD$DISK	EQU	YES		; enable hard disk routines?
ARBITRATION	EQU	NO		; use arbitrated SCSI select?
	;The following equate should be set to NO if you have the
	;hard disk boot eprom.
SCSI$RESET	EQU	NO		; reset SCSI bus if ID=7?
	;Set the BEEP option to NO for systems without consoles.
BEEP		EQU	YES		; beep on non-existent floppies
	;When BUFFKBD is YES, a polled keyboard buffer allows type
	;ahead, within the limitations of CP/M and CP/M applications.
BUFFKBD		EQU	YES		; keyboard buffer
BUFFSIZE	EQU	80		; size of keyboard buffer
	;When CLOCK is YES, a polled real time clock is available.
	;The LITTLE BOARD must CTC ZC/TC2 connected to CLK/TRG3:
	;  - On assemblies which have the SCSI option, whether
	;    populated or not, short JMP2 pins 1 and 2,
	;    and check that JMP2 pins 2 and 3 are unshorted.  NOTE:
	;    some boards have a trace between JMP2 pins 2 and 3 on 
	;    the bottom of the board, which must be cut.
	;  - On the original LITTLE BOARD, without the SCSI
	;    option, the trace to the CTC, U6, pin 20 must be cut.
	;    then wire U6 pin 20 to U6 pin 9.
	;
CLOCK		EQU	YES		; real time clock
LOCAL$STACK	EQU	BUFFKBD OR CLOCK ; if those options are in,
					; local stack is recommended.

INTERNAL	EQU	NO   		; internal (unreleased) bios?

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

CPI80		EQU	0A1EDH		; CPI  (0edh,0a1h)
CPIR80		EQU	0B1EDH		; CPIR (0edh,0b1h)
INIR80		EQU	0B2EDH		; INIR (0edh,0b2h)
LDIR80		EQU	0B0EDH		; LDIR (0edh,0b0h)
OTIR80		EQU	0B3EDH		; OTIR (0edh,0b3h)

INI80		EQU	0A2EDH		; INI  (0edh,0a2h)
OUTI80		EQU	0A3EDH		; OUTI (0edh,0a3h)

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)
SRLA		EQU	02FCBH		; SRL A

; 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
DJNZ		EQU	010H		; DCR, JR NZ addr

; IX and IY prefixes (use DB to enter)

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

; CP/M internals

BIAS		EQU	(MSIZE-20)*1024	; CP/M bias
CCP		EQU	3400H+BIAS	; CCP  starting address
BDOS		EQU	CCP+806H	; BDOS starting address
BIOS		EQU	CCP+1600H	; BIOS starting address
		IF	ONLY$WBOOT$CCP
NSECTS		EQU	(BDOS-CCP)/128	; warm boot sector count
		ENDIF			; just reload CCP
		IF	NOT ONLY$WBOOT$CCP
NSECTS		EQU	(BIOS-CCP)/128	; Warm boot sector count
		ENDIF			; reload CCP and BDOS

; CP/M externals

CDISK		EQU	0004H		; Current disk: 0=A,...,15=P
IOBYTE		EQU	0003H		; Intel I/O byte

; CP/M to host disk constants

WRALL		EQU	0		; Write to allocated
WRDIR		EQU	1		; Write to directory
WRUAL		EQU	2		; Write to unallocated

; Ampro hardware port equates

CONT		EQU	00H		; Ampro system control port

PIO1		EQU	001H		; Parallel printer
STBSET		EQU	002H		; Set print strobe
STBCLR		EQU	003H		; Clear print strobe

CTCA0		EQU	040H		; Clock/timer channel 0
CTCA1		EQU	050H		; Clock/timer channel 1
CTCA2		EQU	060H		; Clock/timer channel 2
CTCA3		EQU	070H		; Clock/timer channel 3

SIODPA		EQU	080H		; Serial port A - data
SIOCPA		EQU	084H		; Serial port A - control
SIODPB		EQU	088H		; Serial port B - data
SIOCPB		EQU	08CH		; Serial port B - control

CMND		EQU	0C0H		; Disk controller command
WTRK		EQU	CMND+1		; Disk controller write track
WSEC		EQU	CMND+2		; Disk controller write sector
WDAT		EQU	CMND+3		; Disk controller write data

STAT		EQU	0C4H		; Disk controller status
RTRK		EQU	STAT+1		; Disk controller read track
RSEC		EQU	STAT+2		; Disk controller read sector
RDAT		EQU	STAT+3		; Disk controller read data

; DART masks

RDA		EQU	01H		; DART recieve data available
TBE		EQU	04H		; DART transmit buffer empty
DCD		EQU	08H		; DART data carrier detect
CTS		EQU	20H		; DART clear to send

; FDC masks, commands, and constants

FRESTOR 	EQU	008H		; FDC restore
FSEEKNV		EQU	018H		; FDC seek, no verify
FSEEK		EQU	01CH		; FDC seek, with verify
FREADS		EQU	088H		; FDC read sector
FWRITES 	EQU	0A8H		; FDC write sector
FRDADDR 	EQU	0C8H		; FDC read address

FNS		EQU	008H		; 0=spin-up,   1=no spin-up
FVF		EQU	004H		; 0=no verify, 1=verify
FNP		EQU	002H		; 0=precomp,   1=no precomp

FRETRY		EQU	3		; Number of floppy retries
HLDELAY		EQU	35		; Head load delay (ms)
DSBIAS		EQU	16		; Double sided sector bias

; Disk type byte definitions:

	;	   Bit: 76543210
	; Density	x	   0=single     1=double
	; Sides		 x	   0=single     1=double
	; Sector #'s	  x	   0=same       1=continuous
	; Track count	   x	   0=down       1=down front, up back
	; Alloc unit	    xx	   00=1K   01=2K   10=4K   11=8K
	; Sector size	      xx   00=128  01=256  10=512  11=1024
	;	   Bit: 76543210

SSDD48		EQU	10000110B	; DD,SS,same,down,2K,512
DSDD48		EQU	11000110B	; DD,DS,same,down,2K,512
SSDD96		EQU	10000111B	; DD,SS,same,down,2K,1024
DSDD96		EQU	11000111B	; DD,DS,same,down,2K,1024

;  NCR controller equates

NCRBASE	EQU	20H		; Base address of NCR 5380
NCRCSD	EQU	NCRBASE+0	; (R)  Current SCSI data register
NCRODR	EQU	NCRBASE+0	; (W)  Output data register
NCRICR	EQU	NCRBASE+1	; (RW) Initiator command register
NCRMR	EQU	NCRBASE+2	; (RW) Mode register
NCRTCR	EQU	NCRBASE+3	; (RW) Target command register
NCRCSBS	EQU	NCRBASE+4	; (R)  Current SCSI bus status
NCRSER	EQU	NCRBASE+4	; (W)  Select enable register
NCRBSR	EQU	NCRBASE+5	; (R)  Bus & status register
NCRSDS	EQU	NCRBASE+5	; (W)  Start DMA send
NCRIDR	EQU	NCRBASE+6	; (R)  Input data register
NCRSDTR	EQU	NCRBASE+6	; (W)  Start DMA target receive
NCRRPI	EQU	NCRBASE+7	; (R)  Reset parity/interrupt
NCRSDIR	EQU	NCRBASE+7	; (W)  Start DMA initiator receive
NCRDACK	EQU	NCRBASE+8	; (RW) DACK pseudo-DMA register

BSYBIT		EQU	08H
ERROR		EQU	02H

; Current SCSI bus status (NCRCSBS)

NCRRST		EQU	10000000B	; Reset
NCRBSY		EQU	01000000B	; Busy
NCRREQ		EQU	00100000B	; Request
NCRMSG		EQU	00010000B	; Message
NCRCD		EQU	00001000B	; Control/Data
NCRIO		EQU	00000100B	; Input/Output
NCRSEL		EQU	00000010B	; Select
NCRDBP		EQU	00000001B	; Data bus parity

; Character equates

CTRLC		EQU	'C'-'@'		; Ctrl-C (Break, abort)
BELL		EQU	'G'-'@'		; Ctrl-G (Bell)
BSP		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)
ESC		EQU	1BH		; Ctrl-[ (Escape)
DEL		EQU	7FH		; 	 (Delete)

; Other equates

DELSEND EQU	28
DDLSPT	EQU	40

; ZCPR3 equates

		IF	ZCPR3
Z3REV		EQU	30		; ZCPR 3.0

Z3ENV		EQU	0FE00H		; Z3 environment descriptor
Z3ENVS		EQU	2		; Size in 128-byte blocks

RCP		EQU	00000H		; Resident command package
RCPS		EQU	0		; Size in 128-byte blocks

IOP		EQU	00000H		; Redirectable I/O package
IOPS		EQU	0		; Size in 128-byte blocks

FCP		EQU	00000H		; Flow command package
FCPS		EQU	0		; Size in 128-byte blocks

Z3NDIR		EQU	00000H		; Named directory area
Z3NDIRS		EQU	0		; Size in 18-byte blocks

Z3CL		EQU	0FF00H		; Z3 command line buffer
Z3CLS		EQU	200		; Size in bytes

SHSTK		EQU	0FD00H		; Shell stack
SHSTKS		EQU	4		; Number of shell elements
SHSIZE		EQU	32		; Size of a shell entry

EXTSTK		EQU	0FFD0H		; Z3 external stack

EXPATH		EQU	40H		; Location of exernal path
EXPATHS		EQU	5		; 5 2-byte path elements

Z3WHL		EQU	4BH		; Wheel byte location

Z3MSG		EQU	0FD80H		; Z3 message buffers

EXTFCB		EQU	0FDD0H		; Z3 external FCB
		ENDIF

;	*	*	*	*	*	*	*	*
;
;		C A V E A T    E M P T O R
;
;  WARNING: The address offsets from BIOS thru BIOS+017FH must remain
;  the same.  Any changes in these offsets will cause incompatibility
;  with the AMPRO system utilities.
;
;		  YOU HAVE BEEN WARNED ...
;
;	*	*	*	*	*	*	*	*

	ORG	BIOS		; Bios starting location

	JMP	BOOT		; Cold start
WBOOTE: JMP	WBOOT		; Warm start
	JMP	CONST		; Console status
	JMP	CONIN		; Console character in
	JMP	CONOUT		; Console character out
	JMP	LIST		; List character out
	JMP	PUNCH		; Punch character out
	JMP	READER		; Reader character in
	JMP	HOME		; Seek to home position
	JMP	SELDSK		; Select disk
	JMP	SETTRK		; Set track number
	JMP	SETSEC		; Set sector number
	JMP	SETDMA		; Set DMA address
	JMP	READ		; Read disk
	JMP	WRITE		; Write disk
	JMP	LISTST		; Return list status
	JMP	SECTRAN 	; Sector translate

; Ampro specific bios calls

	JMP	GETTBL		; Point to more jumps
	JMP	GETEDSK 	; Get ptr to E-disk table
	JMP	IOINIT		; Set new I/O parameters
	IF	HARD$DISK
	JMP	SCSI		; SCSI direct driver
	ENDIF
	IF	NOT HARD$DISK
	MVI	A,0FFH		; Dummy SCSI direct driver
	RET
	ENDIF

;	*	*	*	*	*	*	*	*
;
;  Current system disk
;
;	*	*	*	*	*	*	*	*
	ORG	BIOS+03FH
SYSDSK  DB 0			; Assume A: for now
 
; NOTE:   BIOS+040H thru BIOS+07FH modified by CONFIG
;	*	*	*	*	*	*	*	*
;
;  Initialization parameters for the CTC and DART/SIO
;
;  These values are set by the CONFIG program, and specify the
;  CTC and DART/SIO initialization parameters.  Each CTC channel
;  requires two bytes of initialization, while each DART/SIO 
;  channel may have up to 10 bytes of initialization information.
;
;	*	*	*	*	*	*	*	*
	ORG	BIOS+040H
CTCVAL:	
	IF	CLOCK
	DB	47H,13,47H,13 			; ctc0,ctc1
	DB	25H,125				; ctc2
	DB	55H,125				; ctc3
	ENDIF
	IF	NOT CLOCK
	DB	47H,13,47H,13,3,3,3,3		; CTC0,CTC1,CTC2,CTC3
	ENDIF
SIOAVAL:
	DB	4,46H,5,0EAH,3,0C1H,0,0,0,0	; DART/SIO channel A
SIOBVAL:
	DB	4,46H,5,0EAH,3,0C1H,0,0,0,0	; DART/SIO channel B

;	*	*	*	*	*	*	*	*
;
;  Maximum drive letter available:
;
;  This value is installed by the CONFIG program, and is used by
;  the SELECT routine (optionally) to avoid selecting any disk 
;  units above the maximum letter.  This variable was previously
;  the NDSKS (# of floppy disks available) parameter in the 1.x
;  and 2.x bios.
;
;  Note: Although this parameter is set with the CONFIG utility,
;  it is ignored unless the MAX$DRIVE parameter is set to YES.
;
;	*	*	*	*	*	*	*	*
	ORG	BIOS+05CH
MAX$DRV$LTR:
	DB	'P'-'A'		; Allow drives A-P

;	*	*	*	*	*	*	*	*
;
;  Step rates for the four floppy drives.
;
;  These values are set by the CONFIG program.
;
;	*	*	*	*	*	*	*	*
	ORG	BIOS+05DH
STPRAT:
	DB	0,0,0,0		; Initial rates = 6ms

;	*	*	*	*	*	*	*	*
;
;  Initial IOBYTE value
;
;  This value is set by the CONFIG program, and is used as the
;  initial value for the IOBYTE (location 0003H) on cold boot.
;
;  CP/M defines the following IOBYTE devices:
;
;  Bit: 76543210
;	      xx  Console (0=TTY:, 1=CRT:, 2=BAT:, 3=UC1:)
;	    xx	  Reader  (0=TTY:, 1=PTR:, 2=UR1:, 3=UR2:)
;	  xx	  Punch   (0=TTY:, 1=PTP:, 2=UP1:, 3=UP2:)
;	xx	  List    (0=TTY:, 1=CRT:, 2=LPT:, 3=UL1:)
;
;  In the AMPRO 3.0 bios, the devices are mapped to the serial
;  and parallel ports as follows:
;
;  CRT: Serial Port A		All other devices are undefined.
;  TTY:	Serial Port B		    UC1:  PTR:  UR1:  UR2:  
;  LPT: Parallel port		    UL1:  PTP:  UP1:  UP2:  
;
;	*	*	*	*	*	*	*	*
	ORG	BIOS+061H
IOBYT:	DB	81H	; 10 00 00 01	Initial values:
			; || || || ||
			; || || ||  \= CON: Serial port A (CRT:)
			; || ||  \==== RDR: Serial port B (TTY:)
			; ||  \======= PUN: Serial port B (TTY:)
			;  \========== LST: Parallel port (LPT:)

;	*	*	*	*	*	*	*	*
;
;  AutoStart command
;
;  This value is set by the CONFIG program, and is used as the
;  initial command to ZCPR3.  This command is ignored in the CP/M
;  CCP.
;
;  The format of this region is:
;
;  Command length -- 1 byte
;  Command text   -- 8 bytes (max)
;  Trailing zero  -- 1 byte
;
;	*	*	*	*	*	*	*	*
	ORG	BIOS+062H
AUTOCMD:
	DB	8,'STARTUP ',0	; Cmd length + cmd + 0 terminator

;	*	*	*	*	*	*	*	*
;
;  Handshake required flags for DART/SIO channels A and B
;
;  These values are set by the CONFIG program, and indicate if
;  hardware handshaking is required for each of the two serial
;  channels.  Bit zero (0) of each flag should be set to a one
;  to indicate hardware handshaking.
;
;	*	*	*	*	*	*	*	*
	ORG	BIOS+06CH
HSA:	DB	0		; Handshake flag for channel A
HSB:	DB	1		; Handshake flag for channel B
	DB	0		; (Reserved)
	DB	0		; (Reserved)

;	*	*	*	*	*	*	*	*
;
;  Drive select bytes
;
;  These values are modified by the floppy drive select code, the
;  warm boot code, and several "E" utilities.  The format of each
;  select byte is as follows:
;
;		  Bit:	76543210
;       Speed select	x		0=normal,  1=double
;       Step select	 x		0=single,  1=double
;       Disk density	  x		0=double,  1=single
;       Side select	   x		0=Side 0,  1=Side 1
;       Drive unit 4	    x		.
;       Drive unit 3	     x		.  Select drive
;       Drive unit 2	      x		.  unit 1,2,3,4
;       Drive unit 1	       x	.
;		  Bit:  76543210
;
;	*	*	*	*	*	*	*	*
	ORG	BIOS+070H
DRIVE$TYPES:
	DB	01H		; Drive A:  DD, Unit 1
	DB	02H		; Drive B:  DD, Unit 2
	DB	04H		; Drive C:  DD, Unit 3
	DB	08H		; Drive D:  DD, Unit 4
	DB	02H		; Drive E:  DD, Unit 2
	DB	0,0,0		; (Reserved)

;	*	*	*	*	*	*	*	*
;
;  Reserved data area 
;
;	*	*	*	*	*	*	*	*
	ORG	BIOS+078H
	DW	0,0,0,0		; (Reserved)

;	*	*	*	*	*	*	*	*
;
;  Disk drive parameter headers
;
;  Physical drives A,B,C,D	Floppy drives
;
;  Physical drive  E		Special E-disk
;
;  Physical drives F,G,H,I
;		   J,K,L,M
;		   N,O,P	Hard disk drives
;
;	*	*	*	*	*	*	*	*
	ORG	BIOS+080H
DPBASE:
	DW	XLTSS,0,0,0,DIRBUF,SPARM,CSVA,ALVA
	DW 	XLTSS,0,0,0,DIRBUF,SPARM,CSVB,ALVB
	DW 	XLTSS,0,0,0,DIRBUF,SPARM,CSVC,ALVC
	DW 	XLTSS,0,0,0,DIRBUF,SPARM,CSVD,ALVD

	DW 	XLTE1,0,0,0,DIRBUF,EPARM,CSVE,ALVE

	IF	HARD$DISK
	DW 	0,0,0,0,DIRBUF,FPARM,CSVF,ALVF
	DW 	0,0,0,0,DIRBUF,GPARM,CSVG,ALVG
	DW 	0,0,0,0,DIRBUF,HPARM,CSVH,ALVH
	DW 	0,0,0,0,DIRBUF,IPARM,CSVI,ALVI
	DW 	0,0,0,0,DIRBUF,JPARM,CSVJ,ALVJ
	DW 	0,0,0,0,DIRBUF,KPARM,CSVK,ALVK
	DW 	0,0,0,0,DIRBUF,LPARM,CSVL,ALVL
	DW 	0,0,0,0,DIRBUF,MPARM,CSVM,ALVM
	DW 	0,0,0,0,DIRBUF,NPARM,CSVN,ALVN
	DW 	0,0,0,0,DIRBUF,OPARM,CSVO,ALVO
	DW 	0,0,0,0,DIRBUF,PPARM,CSVP,ALVP
	ENDIF

	IF	NOT HARD$DISK
	DW 	0,0,0,0,DIRBUF,SPARM,CSVF,ALVF
	DW 	0,0,0,0,DIRBUF,SPARM,CSVG,ALVG
	DW 	0,0,0,0,DIRBUF,SPARM,CSVH,ALVH
	DW 	0,0,0,0,DIRBUF,SPARM,CSVI,ALVI
	DW 	0,0,0,0,DIRBUF,SPARM,CSVJ,ALVJ
	DW 	0,0,0,0,DIRBUF,SPARM,CSVK,ALVK
	DW 	0,0,0,0,DIRBUF,SPARM,CSVL,ALVL
	DW 	0,0,0,0,DIRBUF,SPARM,CSVM,ALVM
	DW 	0,0,0,0,DIRBUF,SPARM,CSVN,ALVN
	DW 	0,0,0,0,DIRBUF,SPARM,CSVO,ALVO
	DW 	0,0,0,0,DIRBUF,SPARM,CSVP,ALVP
	ENDIF

SPARM:				; Ampro single sided 48tpi
	DW	40		; Sectors/track
	DB	4		; Block shift
	DB	15		; Block mask
	DB	1		; Extent mask
	DW	94		; Disk size -1
	DW	63		; Directory max
	DB	128		; Allocation 0
	DB	0		; Allocation 1
	DW	16		; Check size
	DW	2		; Offset       

DPARM:				; Ampro double sided 48tpi
				; -or-  single sided 96tpi
	DW	40		; Sectors/track
	DB	4		; Block shift  
	DB	15		; Block mask  
	DB	1		; Extent mask  
	DW	194		; Disk size -1 
	DW	127		; Directory max
	DB	192		; Allocation 0 
	DB	0		; Allocation 1 
	DW	32		; Check size   
	DW	2		; Offset       

QPARM:				; Ampro double sided 96tpi
	DW	40		; Sectors/track
	DB	4		; Block shift  
	DB	15		; Block mask  
	DB	0		; Extent mask  
	DW	394		; Disk size -1 
	DW	255		; Directory max
	DB	240		; Allocation 0 
	DB	0		; Allocation 1 
	DW	64		; Check size   
	DW	2		; Offset       

XLTSS:				; Single sided format skew table
	DB	1,2,3,4,5,6,7,8,9,10
XLTDS:				; Double sided format skew table
	DB	17,18,19,20,21,22,23,24,25,26

;	*	*	*	*	*	*	*	*
;
;  Special user-defined drive		"E-disk"
;
;  NOTE: The order of the following table must not be changed,
;	 since many existing utilities depend on this order.
;
;  A call to the BIOS function GETEDISK (BIOS+030H) returns the
;  address of EPARM.  The type table entry is defined as the re-
;  turned value -1, and the translate table value as the returned
;  value +15.  The translate table has space for 20 entries in
;  bios version 1 or 2, 26 entries in bios version 3+.
;
;	*	*	*	*	*	*	*	*
ETYPE	DB	82H		; ETYPE must be at EPARM - 1
EPARM:				;Set for:	KAYPRO 
  	DW 	40		;SEC/TRK   	KAYPRO IS 40
  	DB 	3		;BLOCK SHIFT
  	DB 	7		;BLOCK MASK
  	DB 	0		;EXTENT MASK
  	DW 	194		;DISK SIZE -1 	KAYPRO IS 194
  	DW 	63		;DIRECTORY MAX -1
  	DB 	240		;ALLOC 0 	KAYPRO 240
  	DB 	0		;ALLOC 1
  	DW 	16		;CHECK SIZE
  	DW 	1		;OFFSET 	KAYPRO IS 1

EDSD:
  	DB 	1		;DEFAULT DRIVE B:

XLTE1:		;TRANSLATE TABLE FOR E DISK
  	DB 	0,1,2,3,4,5,6,7,8,9
  	DW	0,0,0,0,0,0,0,0		; 26 entries max in XLT1

;  End of E-disk user defined table
;	*	*	*	*	*	*	*	*
;
;  GETTBL	GET NEXT JUMP TABLE ADDRESS
;
;  While this routine returns the address of NXTTBL, it can also
;  be used to identify the BIOS version level.
;
;  For pre-2.0 systems, the A register is returned unchanged,
;  and the HL register pair returned 0FFFFH.
;
;  For 2.0 and higher, HL returneds the address of NXTTBL (never
;  equal to 0FFFFH), and A contains the assembly version of this
;  bios code.
;
;	*	*	*	*	*	*	*	*
GETTBL:
	LXI	H,NXTTBL	; Get address of jump table
	MVI	A,VERS		; .  and version number
NXTJMP:				; Label for dummy routines
	RET

NXTTBL:
	JMP	SWAP		; Swap two logical drives
	JMP	HD$INFO		; Get HD table information
	JMP	PHTBAC		; Get/set PhyTab access
	JMP	PAGET		; Get PhyTab entry address
	IF	CLOCK
	JMP	TOD		; return address of clock tick
	ENDIF
	JMP	NXTJMP		; RESERVED ENTRY

;	*	*	*	*	*	*	*	*
;
;  GETEDSK	Get E-disk pointer
;
;	*	*	*	*	*	*	*	*
GETEDSK:
  	LXI 	H,EPARM
  	RET

;	*	*	*	*	*	*	*	*
;
;  IOINIT
;
;  Initialize the CTC and DART comm devices according to the
;  information provided at BIOS + 40 hex.
;
;  Entry from BOOT and also via the JMP IOINIT from the BIOS jump
;  table.
;
;	*	*	*	*	*	*	*	*
IOINIT:
	LXI	H,CTCVAL
	LXI	B,0240H		; Initialize CTC0
	DW	OTIR80
	LXI	B,0250H		; Initialize CTC1
	DW	OTIR80
	LXI	B,0260H		; Initialize CTC2
	DW	OTIR80
	LXI	B,0270H		; Initialize CTC3
	DW	OTIR80
	LXI	B,0A84H		; Initialize DART channel A
	DW	OTIR80
	LXI	B,0A8CH		; Initialize DART channel B
	DW	OTIR80
	LDA	IOBYT		; Initialize IOBYTE
	STA	IOBYTE
	RET

;	*	*	*	*	*	*	*	*
;
;  SWAP		Swap logical drives
;
;  Entry here causes the two LOGICAL drives identified in B and C
;  to be swapped in position.  This alters the current LOGICAL
;  name of the target drive for all subsequent disk operations.
;
;  For example, assume the CP/M logical drive A: is the left
;  floppy disk drive, and the CP/M logical drive F: is the first
;  hard disk partition.  If on entry to the SWAP routine the B
;  register contained 0 (logical drive A:) and the C register
;  contained 5 (logical drive F:), after SWAP, all accesses to
;  CP/M drive A: will be to the first hard disk partition, while
;  all accesses to CP/M drive F: would be to the left floppy 
;  drive.
;
;  		* * * * *   WARNING   * * * * * 
;
;  To maintain compatibility with all utilities, and also for
;  some special case coding within this BIOS, the user definable
;  E-DISK must remain as Drive E logical.
;
;	*	*	*	*	*	*	*	*
SWAP:
	MOV	A,B		; Get first drive logical #
	CALL	PAGET		; Compute current address in table
	PUSH	H		; Save this address
	MOV	A,C		; Get second drive logical #
	CALL	PAGET		; Compute current address in table
	POP	D		; Get pointer to first back in D
	MVI	B,4		; Set up to move 4 bytes
PATMV:
	MOV	C,M		; Get a byte from the first str
	LDAX	D		; Get a byte from the second str
	MOV	M,A		; Swap the bytes
	MOV	A,C		; .
	STAX	D		; .
	INX	D		; Bump pointers
	INX	H		; .
	DB	DJNZ,255-($-PATMV)	
	RET			; No -- all done

;	*	*	*	*	*	*	*	*
;
;  HD$INFO	Return HD information.
;
;  Returns a pointer to various HD pointers.
;
;	*	*	*	*	*	*	*	*
HD$INFO:
	LXI	H,HD$ALV$AVAIL	; Get pointer to available area
	RET			; Provide addr if ptr zero

HD$ALV$AVAIL:
	DW	HD$CURRENT	; Current BIOS buffer area value
	DW	HD$VECTORS	; Hard disk allocation vector base
	IF	HARD$DISK
	DW	HD$BYTE$BLOCK	; Location of SCSI byte/block flag
	ENDIF
	IF	NOT HARD$DISK
	DW	0FFFFH		; "Safe" dummy byte/block location
	ENDIF
	DW	0		; Reserved
	DW	0		; Reserved
	DW	0		; Reserved

;	*	*	*	*	*	*	*	*
;
;  PHTBAC	PHYSICAL TABLE ACCESS
;
;  On entry, if the HL register pair is 0000H, the address of the
;  Logical vs Physical table is returned in HL, with the length 
;  of that table in the A register.
;
;  If on entry, the HL register pair is non-zero, the current
;  table is replaced with the user data starting at the location
;  pointed to by HL for the length of the table.
;
;  Note:  When updating the table, ALL bytes are re-written. No
;  length parameter is accepted from the user, and no alternate
;  starting address is recognized.
;
;  Refer to the PHYTAB for byte definitions...
;
;	*	*	*	*	*	*	*	*
PHTBAC:
	XCHG			; Save ptr in case we need it
	MOV	A,D		; Test former HL
	ORA	E		; 
	MVI	A,PHYEND-PHYTAB	; Get length of table
	LXI	H,PHYTAB	; .  and address, too.
	DB	JRNZ,CPY$TBL-$-1; Copy new table if ptr non-zero
	RET			; Provide addr if ptr zero

;	*	*	*	*	*	*	*	*
;
;  PAGET	GET DRIVE LOGICAL INFORMATION
;
;  Entry here from within the BIOS, as well as from the NXTTBL
;  BIOS Jump entry.
;
;  Enter with the Drive Logical address in the A reg.
;
;  Exits with the Logical Table entry address in the HL reg pair.
;
;  NOTE: Destroys DE and A
;
;	*	*	*	*	*	*	*	*
PAGET:
	LXI	H,PHYTAB	; Get base of physical table
	ADD	A		; Compute offset to the descriptor
	ADD	A		; .
	MOV	E,A		; .
	MVI	D,0		; .
	DAD	D		; .
	RET			; Return with ptr in HL

CPY$TBL:			; Copy from DE to HL, length A
	XCHG			; .  (exchange DE and HL)
	MOV	C,A		; .  (put length info in BC)
	MVI	B,0		; .  (now it's a-ok for LDIR)
	DW	LDIR80		; .  (use LDIR to move table)
	RET			; and return to caller

;	*	*	*	*	*	*	*	*
;
;  PHYSICAL DRIVER TABLE
;
;  This table contains information to identify the Logical vs
;  Physical drives requested, and also the disk driver required 
;  for the particular drive.
;
;  The table is built as 16 groups of 4-bytes each, arranged in
;  the logical order of drives A thru P for a total of 64 bytes.
;
;  The four bytes defining each Logical drive are as follows:
;
;  +0	Disk Driver ID number, 0=reserved for error, Max=7
;
;  +1	Physical offset from DPBASE in DPH table, and the drive
;	unit number.
;
;	Bits   7654:	Offset to DPBASE 0 - F
;
;	Bits   3210:	Physical device address to be passed
;			to the respective driver.
;
;			For floppies, this is the drive unit #.
;			For hard disks, these are reserved.
;
;  +2	Type identifier for this drive 
;
;	Floppy usage:
;
;		   Bit: 76543210
;	Density		x	   0=single     1=double
;	Sides		 x	   0=single     1=double
;	Sector #'s	  x	   0=same       1=continuous
;	Track count	   x	   0=down       1=down front, up back
;	Alloc unit	    xx	   00=1K   01=2K   10=4K   11=8K
;	Sector size	      xx   00=128  01=256  10=512  11=1024
;		   Bit: 76543210
;
;	Hard disk usage:
;
;		   Bit: 76543210
;	LUN		xxx	   Logical unit number (0-7)
;	Reserved	   x	   
;	Alloc unit	    xx	   00=1K   01=2K   10=4K   11=8K
;	Sector size	      xx   00=128  01=256  10=512  11=1024
;		   Bit: 76543210
;
;	Note:	Allocation unit and sector size usage are the same
;		for floppy and hard disk.
;
;
;  +3	00 for floppy or SCSI bus address for hard disk.
;
;	For normal SCSI operations, only one bit may be set.
;
;		   Bit: 76543210
;	SCSI address 0	       x	This is the actual
;	SCSI address 1	      x		bit pattern supplied
;	SCSI address 2	     x		during the SCSI
;	SCSI address 3	    x		select routine.  No
;	SCSI address 4	   x		internal address
;	SCSI address 5	  x		translation or bit
;	SCSI address 6	 x		scaling is done.
;	SCSI address 7	x		
;
;  +4	Duplicated for drives B thru P
; thru
; +63
;
;  Disk Driver Linkage  +64 thru +79
;
;  Each disk driver is described by 2 bytes which point to the
;  driver to be used, with driver 0 reserved for the SELECT ERROR
;  trap for unused or deselected drive letters.
;
;	*	*	*	*	*	*	*	*
PHYTAB:
	DB	1,000H, SSDD48, 0H	; Floppy drive A
	DB	1,011H, SSDD48, 0H	; Floppy drive B
	DB	1,022H, SSDD48, 0H	; Floppy drive C
	DB	1,033H, SSDD48, 0H	; Floppy drive D

	DB	2,044H, 0H, 0FFH	; Special floppy drive E

	DB	0,050H,0,0		; Hard disk drive F
	DB	0,060H,0,0		; Hard disk drive G
	DB	0,070H,0,0		; Hard disk drive H
	DB	0,080H,0,0 		; Hard disk drive I

	DB	0,090H,0,0		; Hard disk drive J
	DB	0,0A0H,0,0		; Hard disk drive K
	DB	0,0B0H,0,0		; Hard disk drive L
	DB	0,0C0H,0,0		; Hard disk drive M

	DB	0,0D0H,0,0		; Hard disk drive N
	DB	0,0E0H,0,0		; Hard disk drive O
	DB	0,0F0H,0,0		; Hard disk drive P

DRVRADR:				; Driver table
	DW	SELERR			; Driver 0 - select error
	DW	FLOPPY			; Driver 1 - floppy
	DW	DISKE			; Driver 2 - E-disk
	IF	HARD$DISK
	DW	DRVR3			; Driver 3 - SCSI hard disk
	ENDIF
	IF	NOT HARD$DISK
	DW	SELERR			; Driver 3 - not defined
	ENDIF
	DW	SELERR			; Driver 4 - not defined
	DW	SELERR			; Driver 5 - not defined
	DW	SELERR			; Driver 6 - not defined
	DW	SELERR			; Driver 7 - not defined

PHYEND	EQU	$
;	*	*	*	*	*	*	*	*
;
;  HOME		Home the selected disk
;
;  Entry via the bios jump table HOME
;
;	*	*	*	*	*	*	*	*
HOME:
	LXI	B,0		; Set the current track to zero
	CALL	SETTRK		; .
	LDA	HSTWRT		; Check for pending write
	ORA	A		; .
	DB	JRNZ,HOMED-$-1	; Pending write, leave active alone
	STA	HSTACT		; No writes, clear host active
HOMED:
	RET

;	*	*	*	*	*	*	*	*
;
;  SETTRK	Set track (BIOS JMP table entry)
;
;	*	*	*	*	*	*	*	*
SETTRK:				; Set track given by BC
	DW	SBCD80,SEKTRK
	DW	SBCD80,CPMTRK
	RET

;	*	*	*	*	*	*	*	*
;
;  SETSEC	Set sector (BIOS JMP table entry)
;
;	*	*	*	*	*	*	*	*
SETSEC:				; Set sector given by reg C
	MOV	A,C
	STA	SEKSEC
	STA	CPMSEC
	RET

;	*	*	*	*	*	*	*	*
;
;  SETDMA	Set DMA (BIOS JMP table entry)
;
;	*	*	*	*	*	*	*	*
SETDMA:				; Set DMA address given by BC
	DW	SBCD80,DMAADR
	XRA	A		; clear flags for ZRDOS
	RET

;	*	*	*	*	*	*	*	*
;
;  SECTRAN	Sector translation (BIOS JMP table entry)
;
;  NOTE: No sector translation is done for the AMPRO disk formats.
;
;	*	*	*	*	*	*	*	*
SECTRAN:			; Translate sector number in BC
	MOV	H,B
	MOV	L,C
	RET

;	*	*	*	*	*	*	*	*
;
;  SELECT	Select disk routine
;
;  Entry via the BIOS Jump Table SELDSK
;
;  Entry:
;	C  CP/M Logical drive
;	E  Bit zero is non-zero if this is the first select of the
;	   drive since warm or cold boot.
;
;	*	*	*	*	*	*	*	*
SELDSK:
	MOV	A,C		; Get logical disk requested
	STA	LOGDSK		; Save it
	STA	SEKDSK		; .

	IF	MAX$DRIVE	; Is the max drive checking enabled?
	LDA	MAX$DRV$LTR	; .  Yes -- Is the letter requested
	CMP	C		; .  .  greater than the max letter?
	JC	SELERR		; .  Yes -- return w/select error
	ENDIF			; .  .

	IF	LOCAL$STACK
	DW	73EDH,OLDSTK	; ld(oldstk),sp - save old stack
	LXI	SP,LOCSTK	; set local stack
	ENDIF
	MVI	A,0FFH		; No to either -- set SELECT cmd

; NOTE:  we fall through to PDRVR
;	*	*	*	*	*	*	*	*
;
;  PDRVR	Physical driver linkage
;
;  Enter with the Logical Disk Drive saved at (LOGDSK) and the 
;  Physical Command in the A reg as:
;
;	FF	Select Disk Routine
;	00	Write Disk
;	01 	Read  Disk
;
;  The proper driver is computed and jumped to as needed.
;
;	*	*	*	*	*	*	*	*
PDRVR:
	PUSH	D		; Protect first select status
	STA	PHYCMD		; Save cmd for later
	LDA	LOGDSK		; Get logical drive requested
	CALL	PAGET		; Get table address for this drive
	SHLD	PHYTAG		; Save index to logical descriptor
	PUSH	H		; Save start address
	LXI	D,TAGDRV	; Move the four tag bytes
	LXI	B,4		; .
	DW	LDIR80		; (LDIR)
	POP	H		; Restore starting address
	POP	D		; Restore first select status
	MOV	A,M		; Get driver number
	ORA	A		; .  If zero, then select error
	DB	JRZ,SELERR-$-1	; .
	MOV	B,A		; save drive index
	INX	H		; Bump to next position
	MOV	A,M		; Get unit # and DPH offset
	ANI	0FH		; Mask all but unit #
	STA	PHYDRV		; Save unit #
	MOV	A,M		; Get unit # and DPH offset again
	ANI	0F0H		; Mask all but DPH offset
	STA	DPHDRV		; Save DPH offset
PDRVR1:	
	MOV	A,B		; get driver number back
	ADD	A		; .
	MOV	C,A		; Compute ptr within driver table
	MVI	B,0		; .
	LXI	H,DRVRADR	; .
	DAD	B		; .
	MOV	A,M		; Get ptr to driver in HL
	INX	H		; .
	MOV	H,M		; .
	MOV	L,A		; .
	LDA	PHYCMD		; Get cmd back in the A reg
	PCHL			; Jump to the proper driver

;	*	*	*	*	*	*	*	*
;
;  SELERR	Indicate select error to the BDOS
;
;	*	*	*	*	*	*	*	*
SELERR:
	LXI	H,0		; Zero indicates select failed
	RET

;	*	*	*	*	*	*	*	*
;
;  FLOPPY	Floppy disk dispatcher -- driver code (01)
;
;  Enter with operation code in the A register as:
;
;	FF	Select Disk
;	00	Write Disk
;	01	Read Disk
;
;	*	*	*	*	*	*	*	*
FLOPPY:
	INR	A		; Code = select?
	JZ	SELFLP		; Yes, perform a floppy select
	JMP	FLOPPYIO	; No,  perform host read or write

;	*	*	*	*	*	*	*	*
;
;  DISKE	E-disk dispatcher -- driver code (02)
;
;  Enter with the operation code in the A register as:
;
;	FF	Select Disk
;	00	Write Disk
;	01	Read Disk
;
;	*	*	*	*	*	*	*	*
DISKE:
	PUSH	PSW		; Save command
	PUSH	D		; .  and first-select status
	LDA	EDSD		; Get E-disk unit
	MOV	E,A		; Get type byte for the drive
	MVI	D,0		; .  we want to be the 'E'
	LXI	H,DRIVE$TYPES	; .  disk
	DAD	D		; .
	MOV	A,M		; .
	ANI	00FH		; Strip all but select bit
	MOV	B,A		; Save select bit
	LXI	H,DRIVE$TYPES+4	; Point to the E-disk drive byte
	MOV	A,M		; Get the E-disk drive byte
	ANI	0F0H		; Strip select bits
	ORA	B		; OR in proper select bits
	MOV	M,A		; Save new E-disk drive byte
	PUSH	H		; Save pointer to E-disk drive byte
	LDA	EPARM-1		; Get type byte
	LHLD	PHYTAG		; Save as PHYTAB type byte
	INX	H		; .
	INX	H		; .
	MOV	M,A		; .
	STA	TAGTYP		; and as current type byte
	POP	H		; Get E-disk drive byte ptr back
	DB	BIT,BRES+B5+ZM	; Indicate double density
	RLC			; See if E-disk is single density
	DB	JRC,E$IS$DD-$-1	; No,  leave bit alone
	DB	BIT,BSET+B5+ZM	; Yes, indicate single density
E$IS$DD:
	POP	D		; Recover first select status
	POP	PSW		; .  and command
	INR	A		; Select?
	JZ	SELEDSK		; Yes, perform select
	JMP	FLOPPYIO	; Otherwise use R/W floppy host

PUTS:				; Send zero-terminated str to con:
	MOV	A,M		; Get char
	INX	H		; Bump pointer
	ORA	A		; Zero is end-of-string
        RZ                      ; Yes -- return
	MOV	C,A		; Otherwise move to C register
	CALL	CONOUT1		; .  and send to console
	JMP	PUTS		; Repeat for remaining bytes

;	*	*	*	*	*	*	*	*
;
;  WBOOT	Warm boot system
;
;  This routine reloads the CCP and BDOS from the current logical
;  disk defined as SYSDSK.  While SYSDSK is normally logical drive
;  A:, any other drive may be referenced by setting SYSDSK to the
;  desired logical drive address (0-15).
;
;  The warm boot consists of reading 1600 hex bytes of code, from
;  CP/M track 0, sector 1, for 44 CP/M records.  The code is loaded
;  starting with the CCP address, which is defined at the start of
;  this BIOS listing.
;
;  The disk currently assigned to SYSDSK must have a standard AMPRO
;  system image residing on the boot tracks (tracks 0 and 1).
;
;  If SYSDSK is not logical drive A:, the BDOS will still access the
;  current logical drive A: after the GOCPM entry in order to build
;  the directory allocation vector table for drive A: prior to the
;  selection of the current disk.
; 	
;	*	*	*	*	*	*	*	*
WBOOT:
	LXI	SP,80H		; Set stack to below default DMA
	LDA	SYSDSK		; Get current system disk
	MOV	C,A		; .
	MVI	E,0		; Set autoselect bit
	CALL	SELDSK		; Select the system disk
	MOV	A,H		; Check for a select error
	ORA	L		; .
	DB	JRZ,WBERR-$-1	; Select error -- try again

	LXI	B,0		; Start reading from track 0
	CALL	SETTRK		; .
	MVI	B,NSECTS	; Set the # of sectors to read
	MVI	C,1		; Start from sector 1 of track 0
	LXI	H,CCP		; Where we want the data to go
WBLOOP:
	PUSH	B		; Save sector count
	PUSH	H		; and DMA address
	CALL	SETSEC		; Set the sector to read next
	POP	B		; Get the DMA address
	PUSH	B		; Save it back
	CALL	SETDMA		; Set the current DMA
	CALL	READ		; Read the sector
	ORA	A		; Check for errors
	DB	JRNZ,WBERR-$-1	; Disp msg & try again on error

	POP	H		; Get DMA address
	POP	B		; Get count & current sector
	LXI	D,128		; Update DMA address
	DAD	D		; .
	INR	C		; Bump sector count
	LDA	CPMSPT		; Get sectors/track for this disk
	CMP	C		; Are we at the end of track yet?
	DB	JRNZ,NXTSEC-$-1	; No,  just get the next sector
	PUSH	B		; Yes, save count & current sector
	DW	LBCD80,CPMTRK	; Bump current track
	INX	B		; .
	CALL	SETTRK		; .
	POP	B		; Restore count
	MVI	C,0		; Reset sector to 0
NXTSEC:
	DB	DJNZ,255-($-WBLOOP)

; NOTE: WBOOT falls through to GOCPM
;	*	*	*	*	*	*	*	*
;
;  GOCPM	Setup CP/M vectors and jump to CP/M
;
;	*	*	*	*	*	*	*	*
GOCPM:
	LXI	SP,80H		; set stack pointer
	XRA	A
	STA	HSTACT		; Clear the host active flag
	STA	HSTWRT		; Clear the host written flag
	STA	UNACNT		; clear unacnt value

	MVI	A,0C3H		; Setup the warm boot vector
	STA	0		; .
	LXI	H,WBOOTE	; .
	SHLD	1		; .
	STA	5		; and the BDOS vector
	LXI	H,BDOS		; .
	SHLD	6		; .
	LXI	B,80H		; Setup the default DMA
	CALL	SETDMA		; .
	EI			; Enable interrupts
	LDA	CDISK		; Get current disk/user
	MOV	C,A		; .
	JMP	CCP		; And go to CP/M ...

;	*	*	*	*	*	*	*	*
;
;  WBERR	Display error msg on warm boot error and try again.
;
;	*	*	*	*	*	*	*	*
WBERR:
	LXI	H,BOOTMSG	; Warm boot error -- display
	CALL	PUTS		; .  boot failed message and
	JMP	WBOOT		; .  try again ...
BOOTMSG:
	DB	CR,LF,'BOOT FAILED!',0

;	*	*	*	*	*	*	*	*
;
;  SELFLP	Floppy disk select routine
;
;  See if the disk has been accessed since the last warm or cold
;  boot (bit 0 of the E register is non-zero).  If true, seek to
;  track 2 and read the ID to determine if the disk is SS or DS,
;  48tpi or 96tpi, and in a 48tpi or 96tpi drive.  Set up the disk
;  access tables as appropriate.
;
;	*	*	*	*	*	*	*	*
SELFLP:
	DB	BIT,BTST+B0+ZE	; Check for new mount since boot
	JNZ	SELEND		; Not new, no need to determine type
	CALL	MAPTYPE		; Clear double step bit
	DB	BIT,BRES+B6+ZM	; .
	CALL	GETTYPE		; Set type byte to SS48tpi
	MVI	M,SSDD48	; .
	CALL	SETUP		; Setup parameters for SSDD
	CALL	RESTORE		; Restore floppy to track 00
	JNZ	SELERR		; Error on restore, give select err
	MVI	A,2		; Seek to track 2
	STA	CPMTRK		; .
	CALL	SEEK		; .
	LDA	IDSAVE+3	; Get sector size (bytes/sector)
	DB	BIT,BTST+B1+ZA	; Bit 1 non-zero means 512 or 1024
	JZ	SELERR		; 128, 256 = err for AMPRO fmt
	ANI	01H		; Reset all but bit 0
	ADD	A		; *2 ... now 0=48tpi, 2=96tpi
	MOV	C,A		; Save in C reg for the moment
	LDA	IDSAVE+2	; Get sector #
	CPI	DSBIAS+1	; Compare to DS bias (16+1)
	DB	JRC,SELSS-$-1
	INR	C		; Bump pointer if DS
SELSS:	MVI	B,0		; BC now contains offset to type
	CALL	GETTYPE		; Get ptr to type byte
	XCHG			; Save ptr in DE
	LXI	H,TYPTBL	; Get type table base
	DAD	B		; Add offset
	MOV	A,M		; Get actual type byte
	STAX	D		; Save in PHYTAB

	CALL	GETDPT		; Get ptr to DPT
	LXI	D,10		; Bump by 10 to get ptr to DPB
	DAD	D		; .
	XCHG			; Save ptr in DE
	LXI	H,DPBTBL	; Get DPB ptr base
	DAD	B		; Add offset
	DAD	B		; Add offset again (word ptr)
	MOV	A,M		; Get first byte of DPB ptr
	STAX	D		; Save it
	INX	D		; Bump pointers
	INX	H		; .
	MOV	A,M		; Get second byte of DPB ptr
	STAX	D		; Save it

	LDA	IDSAVE		; Get track number
	DCR	A		; Track=1 means set double step
	DB	JRZ,SET$DSTEP-$-1
	DCR	A		; Track=2 means normal step
	DB	JRZ,SELEND-$-1
	JMP	SELERR		; Any other value = select err

SET$DSTEP:
	CALL	MAPTYPE		; Map byte for this drive,
	DB	BIT,BSET+B6+ZM	; set the double step bit.

; NOTE: We fall through to SELEND ...
;	*	*	*	*	*	*	*	*
;
;  SELEND	Compute proper deblocker variables and DPT addr
;
;  Driver types 1, 2, and 3 all re-enter here.
;
;	*	*	*	*	*	*	*	*
SELEND:
	CALL	GETTYPE		; Get current type byte
	MOV	B,A		; Save for a moment
	ANI	3		; Mask all but sector shift bits
	STA	SECSHF		; Save sector shift value
	MOV	E,A		; Get sector shift byte from table
	MVI	D,0		; .
	LXI	H,SEC$MSK$TBL	; .
	DAD	D		; .
	MOV	A,M		; .
	STA	SECMSK		; Save it

	MOV	A,B		; Get type byte back
	RAR			; Shift over to move alloc size
	RAR			; .  to the proper position
	ANI	3		; Mask all but alloc bits
	MOV	E,A		; Get initial unalloc from table
;	MVI	D,0		; . already zero from start of routine
	LXI	H,M$UNACT$TBL	; .
	DAD	D		; .
	MOV	A,M		; .
	STA	MUNACT		; Save it

; NOTE: We fall through to GETDPT . . .
;	*	*	*	*	*	*	*	*
;
;  GETDPT	Calculate the DPH address for the current logical disk
;
;  Entry with the DPH vector in DPHDRV, exits with the DPH address in
;  the HL register pair, and the current DPB saved at CURDPB (primarily
;  for the warm boot routine).
;
;	*	*	*	*	*	*	*	*
GETDPT:
	LDA	DPHDRV		; Get DPH pointer
	MOV	E,A		; .
	MVI	D,0		; . ****
	LXI	H,DPBASE	; .
	DAD	D		; .
	XCHG			; save it (need to return it in 'hl')
	MVI	L,10		; add offset to dpb 'h'=zero from ****
	DAD	D		; .
	MOV	A,M		; Copy DPB ptr to HL
	INX	H		; .
	MOV	H,M		; .
	MOV	L,A		; .
	SHLD	CURDPB		; Save current DPB pointer
	MOV	A,M		; Get sectors-per-track
	STA	CPMSPT		; Save it
	XCHG			; get dph pointer
	RET			; Leave with DPH pointer in HL

TYPTBL:				; Drive type bytes
	DB	SSDD48
	DB	DSDD48
	DB	SSDD96
	DB	DSDD96

DPBTBL:				; DBP pointers
	DW	SPARM
	DW	DPARM
	DW	DPARM
	DW	QPARM

SEC$MSK$TBL:			; Sector mask table
	DB	00H
	DB	01H
	DB	03H
	DB	07H

M$UNACT$TBL:			; Initial unalloc count table
	DB	8
	DB	16
	DB	32
	DB	64

;	*	*	*	*	*	*	*	*
;
;	SELECT THE E-DISK DRIVE
;
;	Builds the appropriate tables, and attempts to re-zero the
;	drive. Returns with Select Error if can't re-zero.
;
;	*	*	*	*	*	*	*	*
SELEDSK:
	DB	BIT,BTST+B0+ZE	; Check for new mount since boot
	JNZ	SELEND		; Not new, no need to determine type
	CALL	GETTYPE		; Get type byte & pointer
	DB	BIT,BTST+B5+ZA	; Continuous sector numbers?
	DB	JRZ,NOTCONT-$-1	; No,  don't bother computing E-adj
	ANI	03H		; Yes, compute E-adj from sector
	DW	LBCD80,EPARM-1	; .  size and sectors per track
	MVI	C,0FFH		; .  (A=count,B=SPT,C=mask)
SELESHIFT:			; .
	ORA	A		; .  Test if done & clear carry
	DB	JRZ,SDONE-$-1	; .  Yes, save parameter
	DB	0CBH,019H	; .  RR C  (Rotate C right)
	DB	0CBH,018H	; .  RR B  (Rotate B right)
	DCR	A		; .  Decrement count
	JMP	SELESHIFT	; .  Next shift ...
NOTCONT:
	LXI	B,0		; Not continuous, set adj to zero
SDONE:
	MOV	A,B		; Shifts done, get computed E-adj
	ANA	C		; Mask extra bits off
	STA	ESECADJ		; Save as proper adj
	CALL	SETUP		; Setup parameters for the E-disk
	CALL	RESTORE		; Restore floppy to track 00
	INR	A		; Check for error during restore
	JZ	SELERR		; Error code, return with select err
	JMP	SELEND		; No error, finish the select

;---------------------------------------------------------------
;
;	READ CP/M SECTOR FROM THE CURRENTLY SELECTED DISK
;
;	ENTRY VIA THE BIOS JUMP TABLE
;
;--------------------------------------------------------------
READ:
	IF	LOCAL$STACK
	DW	73EDH,OLDSTK	; ld(oldstk),sp - save old stack
	LXI	SP,LOCSTK	; set local stack
	ENDIF
	LDA	SECSHF		; secshf = 0 if 128 byte sector size
	ANA	A
	JZ	READHST		; IF 128 BYTES
	XRA	A
	STA	UNACNT
	INR	A		; 'a' = 1
	STA	READOP		;READ OPERATION
	STA	RSFLAG		;MUST READ DATA
	MVI	A,WRUAL
	STA	WRTYPE		;TREAT AS UNALLOC
	JMP	RWOPER		;TO PERFORM THE READ

;---------------------------------------------------------------
;
;	WRITE CP/M SECTOR FROM THE CURRENTLY SELECTED DISK
;
;	ENTRY VIA THE BIOS JUMP TABLE
;
;--------------------------------------------------------------
WRITE:
	IF	LOCAL$STACK
	DW	73EDH,OLDSTK	; ld(oldstk),sp - save callers stack
	LXI	SP,LOCSTK	; set local stack
	ENDIF
	LDA	SECSHF		; secshf = 0 if 128 bytes
	ANA	A
	JZ	WRITEHST	; IF 128 BYTES
	XRA	A		;0 TO ACCUMULATOR
	STA	READOP		;NOT A READ OPERATION
	MOV	A,C		;WRITE TYPE IN C
	STA	WRTYPE
	CPI	WRUAL		;WRITE UNALLOCATED?
	JNZ	CHKUNA

;	WRITE TO UNALLOCATED, SET PARAMETERS
;	FOR EDSK, WE NEED TO CHECK TO SEE IF SEKDSK IS THE E DISK.
;	IF SO, THEN UNACNT WILL BE SET ACCORDING TO THE DISK TYPE.
;	FOR AMPRO DISKS, ITS ALWAYS BLKSIZ/128.

	LXI	H,MUNACT
	LXI	D,UNACNT
	LXI	B,5
	DW	LDIR80

CHKUNA:
;	CHECK FOR WRITE TO UNALLOCATED SECTOR
;	MORE UNALLOCATED RECORDS REMAIN
;	DISKS ARE THE SAME
;	TRACKS ARE THE SAME

	XRA	A		; clear 'a'
	LXI	H,UNACNT
	ORA	M		; see if any more remain
	DB	JRZ,ALLOC-$-1	; no more remain
	DCR	M		; unacnt -1
	INX	H		; 'hl' = unadsk

; get ready for block match
; sekdsk=unadsk, sektrk=unatrk, seksec=unasec ?

	LXI	D,SEKDSK	;
	LXI	B,4
MORUNA:	LDAX	D
	DW	CPI80
	DB	JRNZ,ALLOC-$-1	; no match
	INX	D		; next byte
	JPE	MORUNA		; count not over

	DCX	H		; correct 'hl' to unasec

; MATCH, MOVE TO NEXT SECTOR FOR FUTURE REF

	INR	M		;UNASEC = UNASEC+1
	MOV	A,M		;END OF TRACK?
	XCHG			; save 'hl' for overflow
	LHLD	CURDPB		; get current DPB as set by select
EMATCH:	CMP	M		; see if end of track
	JC	NOOVF		;SKIP IF NO OVERFLOW

; OVERFLOW TO NEXT TRACK

	XRA	A
	STAX	D		; unasec 'de' = 0
	LHLD	UNATRK
	INX	H
	SHLD	UNATRK

; MATCH FOUND, MARK AS UNNECESSARY READ

NOOVF:	XRA	A		;0 TO ACCUMULATOR
	STA	RSFLAG		;RSFLAG = 0
	JMP	RWOPER+1	;TO PERFORM THE WRITE

; NOT AN UNALLOCATED RECORD, REQUIRES PRE-READ

ALLOC:	XRA	A		;0 TO ACCUM
	STA	UNACNT		;UNACNT = 0
	INR	A		;1 TO ACCUM
	STA	RSFLAG		;RSFLAG = 1

; ENTER HERE TO PERFORM THE READ/WRITE

RWOPER:	XRA	A		;ZERO TO ACCUM
	STA	ERFLAG		;NO ERRORS (YET)
	IF	CLOCK
	CALL	CLKCHK		; tick toc
	ENDIF
	IF	BUFFKBD
	CALL	KEYCHK		; get keyboard input
	ENDIF
	LDA	SECSHF
	MOV	B,A
	LDA	SEKSEC		;COMPUTE HOST SECTOR

SLOOP:
	DW	SRLA		; shift 'a' right, msb = 0
	DB	DJNZ,255-($-SLOOP)
	STA	SEKHST		;HOST SECTOR TO SEEK

;				ACTIVE HOST SECTOR?
	LXI	H,HSTACT	;HOST ACTIVE FLAG
	MOV	A,M
	MVI	M,1		;ALWAYS BECOMES 1
	ORA	A		;WAS IT ALREADY?
	DB	JRZ,FILHST-$-1

;				HOST BUFFER ACTIVE, SAME AS SEEK BUFFER?
	LXI	D,SEKDSK
	LXI	H,HSTDSK
	LXI	B,3
MATMOR:	LDAX	D		; sekdsk=hstdsk, sektrk=hsttrk ?
	DW	CPI80
	DB	JRNZ,NOMATCH-$-1
	INX	D
	JPE	MATMOR
	LDA	SEKHST		; sekhst=hstsec ?
	CMP	M
	DB	JRZ,MATCH-$-1	; match

NOMATCH:
;				PROPER DISK, BUT NOT CORRECT SECTOR
	LDA	HSTWRT		;HOST WRITTEN?
	ORA	A
	LDA	HSTDSK		;SELECT HOST AS DISK TO WORK ON
	STA	LOGDSK		;SET LOGICAL DRIVE = BUFFERS DRIVE
	LHLD	HSTTRK
	SHLD	CPMTRK
	CNZ	WRITEHST	;CLEAR HOST BUFF

FILHST:	LDA	SEKDSK		; May have to fill host buffer
	STA	HSTDSK
	STA	LOGDSK		;UPDATE LOGICAL DRIVE 
	LHLD	SEKTRK
	SHLD	HSTTRK
	SHLD	CPMTRK
	LDA	SEKHST
	STA	HSTSEC
	LDA	RSFLAG		;NEED TO READ?
	ORA	A
	CNZ	READHST 	;YES, IF 1
	XRA	A		;0 TO ACCUM
	STA	HSTWRT		;NO PENDING WRITE

;
;	COPY DATA TO OR FROM BUFFER
;	EDSK: SECTOR MASK (SECMSK) MUST BE A VARIABLE FOR EDISK AND
;	IS CALCULATED BY (HSTSIZ/128)-1. THIS IS THREE FOR AMPRO,
;	KAYPRO, AND OTHER 512-BYTE SECTORS, AND 1 FOR 256 BYTE SECTORS.
;
MATCH:	LDA	SEKSEC		;MASK BUFFER NUMBER
	LXI	H,SECMSK
	ANA	M		;LEAST SIGNIF BITS
	ADD	A		; x2
	MOV	L,A		; Get offset from table
	MVI	H,0
	LXI	D,BUFTBL	; Buffer table
	DAD	D		; 'hl' points to offset address
	MOV	A,M
	INX	H
	MOV	H,M
	MOV	L,A		; 'hl' has hostbuffer address
	DW	LDED80,DMAADR	; load 'de' dmaadress
	LXI	B,128		; set 'bc' up form 128 byte transfer
	LDA	READOP		;WHICH WAY?
	ORA	A
	DB	JRNZ,RWMOVE-$-1

;				WRITE OPERATION, MARK AND SWITCH DIRECTION
	MVI	A,1
	STA	HSTWRT		;HSTWRT = 1
	XCHG			;SOURCE/DEST SWAP

RWMOVE:	DW	LDIR80		; 'hl' = source, 'de' = destination

;	DATA HAS BEEN MOVED TO/FROM HOST BUFFER

	LDA	WRTYPE		;WRITE TYPE
	CPI	WRDIR		;TO DIRECTORY?
	LDA	ERFLAG		;IN CASE OF ERRORS
	RNZ			;NO FURTHER PROCESSING

;	CLEAR HOST BUFFER FOR DIRECTORY WRITE

	ORA	A		;ERRORS?
	RNZ			;SKIP IF SO
	XRA	A		;0 TO ACCUM
	STA	HSTWRT		;BUFFER WRITTEN
	LDA	HSTDSK
	STA	LOGDSK
	LHLD	HSTTRK
	SHLD	CPMTRK
	CALL	WRITEHST
	LDA	ERFLAG
	RET

WRITEHST:
	;LOGDSK = HOST DISK #, CPMTRK = HOST TRACK #,
        ;HSTSEC = HOST SECT #. WRITE "HSTSIZ" BYTES
	;FROM HSTBUF AND RETURN ERROR FLAG IN ERFLAG.
	;RETURN ERFLAG NON-ZERO IF ERROR

	XRA	A
	JMP	HOSTDO

READHST:
	;LOGDSK = HOST DISK #, CPMTRK = HOST TRACK #,
        ;HSTSEC = HOST SECT #. READ "HSTSIZ" BYTES
	;INTO HSTBUF AND RETURN ERROR FLAG IN ERFLAG.

	MVI	A,1
HOSTDO:	STA	RWHOST
HOSTIO:	JMP	PDRVR		;SELECT NEXT ROUTINE VIA PDRVR
;
; Buffer table, determine start address of host buffer for read/write
;
BUFTBL:	DW	HSTBUF+0000
	DW	HSTBUF+0128
	DW	HSTBUF+0256
	DW	HSTBUF+0384
	DW	HSTBUF+0512
	DW	HSTBUF+0640
	DW	HSTBUF+0768
	DW	HSTBUF+0896
;
;--------------------------------------------------------------------
;
;	FLOPPY DISK READ/WRITE HOST ROUTINES
;
;-------------------------------------------------------------------
FLOPPYIO:
	CALL	SETUP		;OUTPUT TO DRIVE SELECT REGISTER
        MVI 	A,FRETRY
        STA 	TRIES
WMI:
  	CALL	MAPTRK
  	CALL	GETTRK		;WHAT TRACK DO WE WANT?
  	CMP	M
  	CNZ	SEEK		;IF NOT THERE, GO THERE
	DB	JRNZ,RWFAIL-$-1	;IN CASE SEEK FAILS

;	TRACK SHOULD BE OK, BUT WE NEED TO SEND IT ANYWAY IN
;       CASE THE LAST UNIT WAS DIFFERENT (THERE'S ONLY ONE TRACK
;	REGISTER). THEN LOAD SECTOR AND DO IT...

	CALL	GETTRK
	OUT	WTRK
	CALL	GETSEC

; CHECK FOR DSDD TO ADD SECTOR BIAS IF NOT 'E' DRIVE

	MOV	E,A		; Save sector # for a moment
	LDA	PHYDRV		; See if this is the E-disk
	CPI	04H		; .  (E-disk is drive "4")
	MVI	A,0		; Restore sector #
        DB	JRZ,NOBIAS-$-1	; No sector bias if 'E' drive
	PUSH	H
	PUSH	D
	CALL	GETTYPE		; get disk type
	POP	D
	POP	H
	ANI	0C0H		; Isolate DD, DS indicators
	CPI	0C0H		; Check DD and DS
	MVI	A,0		; .
	DB	JRNZ,NOBIAS-$-1	; Not DSDD, no bias
	ADI	DSBIAS		; Add double sided bias
NOBIAS:
	ADD	E		; Add sector # to bias
	OUT	WSEC		; Send sector # to FDC
	CALL	SETUP		; Select unit and real side
	CALL	GETBUF		;
	MVI	A,2		; Set up internal retry counter
	STA	INT$RETRIES	; .
	LDA	RWHOST		; Test for read (1) or write (0)
	ORA	A		; .
	JNZ	RDS		; .

	CALL	WRDAT		; Write sector
	DB	JRNZ,RWFAIL-$-1	; Try again if failed
	RET			; Otherwise return

RDS:	MVI	A,FREADS	; Indicate read sector
	CALL	RDATA		; Read sector
	DB	JRNZ,RWFAIL-$-1	; Try again if failed
	RET			; Otherwise return

RWFAIL:	CALL	RESTORE		; Restore to track 00 on failure
	LXI	H,TRIES		; Point to retry counter
	DCR	M		; Bump it closer to doom
	DB	JRNZ,255-($-WMI); and try again if we can . . .
	MVI	A,01		; No more tries left
	STA	ERFLAG		; Set error flag
	RET			; and return to BDOS w/error

;	*	*	*	*	*	*	*	*
;
;  WRDAT	Floppy disk write data routine
;
;  This routine writes the data pointed to by the HL register to
;  the currently selected floppy.
;
;  The number of bytes written is determined by the FDC controller
;  and the ID string currently active for this sector.
;
;  Multiple sector transfers are not supported.
;
;  NOTE:  The fastest way to test for busy and DRQ is to shift the
;  status bit right into the carry bit.  Therefore, when busy drops,
;  the ending status is still shifted right.  The checks for lost
;  data, write protect, record not found, and crc error must take
;  this bit shift into account.
;
;	*	*	*	*	*	*	*	*
WRDAT:
	SHLD	FRWPTR		; Save data ptr in case intr
	CALL	DWAIT		; Wait until the FDC is ready
WRDAT2:
	MVI	A,FWRITES	; Set up write sector command
	LHLD	FRWPTR		; .
	CALL	OUTCMD		; Send the command to the FDC

WR:	IN	STAT		; Get FDC status
	RAR			; Test busy bit
	DB	JRNC,WRDONE-$-1	; Exit if no busy bit
	RAR			; Test DRQ bit
	DB	JRNC,255-($-WR)	; Test status again if no DRQ
	MOV	A,M		; Busy & DRQ ==> OK to write data
	OUT	WDAT		; .
	INX	H		; Bump pointer to next byte
	JMP	WR		; Repeat as long as busy is active

WRDONE:	DB	BIT,BTST+B1+ZA	; Test for lost data
	DB	JRNZ,255-($-WRDAT2)
				; Lost data -- Try write cmd again

WRNLD:	ANI	02CH		; Test for WP, RNF, or CRC
	RZ			; All ok if zero
	LDA	INT$RETRIES	; Get # of internal retries left
	DCR	A		; Bump count one closer to doom!
	STA	INT$RETRIES	; Save count
	JP	WRDAT2		; Try again if 0, 1, or 2
	RET			; Otherwise return with NZ status

;	*	*	*	*	*	*	*	*
;
;  RDATA	Floppy disk read data routine
;
;  This routine reads the data from the currently selected floppy
;  into the area pointed to by the HL register.  This data is from
;  a read address or read data command.
;
;  The number of bytes read is determined by the 1770 controller
;  and the ID string currently active for this sector.
;
;  Multiple sector transfers are not supported.
;
;  NOTE:  The fastest way to test for busy and DRQ is to shift the
;  status bit right into the carry bit.  Therefore, when busy drops,
;  the ending status is still shifted right.  The checks for lost
;  data, record not found, and crc error must take this bit shift
;  into account.
;
;	*	*	*	*	*	*	*	*
RDATA:
	STA	FRWCMD		; Save command and data pointer
	SHLD	FRWPTR		; .  (in case we're interrupted)
	CALL	DWAIT		; Make sure FDC is ready
RDATA2:
	LDA	FRWCMD		; Get command and data pointer
	LHLD	FRWPTR		; .
	CALL	OUTCMD		; Send command to FDC
RD:
	IN	STAT		; Get FDC status
	RAR			; Test busy bit
	DB	JRNC,RDDONE-$-1	; Exit if no busy bit
	RAR			; Test DRQ bit
	DB	JRNC,255-($-RD)	; Test status again if no DRQ
	IN	RDAT		; Busy & DRQ ==> OK to read data
	MOV	M,A		; .
	INX	H		; Bump pointer to next byte
	JMP	RD		; Repeat as long as busy is active
RDDONE:
	DB	BIT,BTST+B1+ZA	; Test for lost data
	DB	JRNZ,255-($-RDATA2)
				; Lost data -- try read again
RDNCD:
	ANI	0CH		; Test for RNF or CRC
	RZ			; All ok if zero
	LDA	INT$RETRIES	; Get # of internal retries left
	DCR	A		; Bump count one closer to doom!
	STA	INT$RETRIES	; Save count
	JP	RDATA2		; Try again if 0, 1, or 2
	RET			; Otherwise return with NZ status

;	*	*	*	*	*	*	*	*
;
;  OUTCMD	Issue a cmd to the 1770 floppy controller
;
;	*	*	*	*	*	*	*	*
OUTCMD:
	CALL	MOTOR		; Insure motor on
OC0:	OUT	CMND		; Send command to FDC
	MVI	A,15		; Wait 60 us for cmd to set up
OC1:	DCR	A		; . 15 x 4 usec
	DB	JRNZ,255-($-OC1)
	RET

;	*	*	*	*	*	*	*	*
;
;  MOTOR	Start floppy  drive motor
;
;  This routine starts the target motor by executing a read address
;  command.  The xTRK, xSEC, and xDAT registers of the 1770 must be
;  saved as the read address command alters their contents.  After
;  the read address command, we have 2 seconds (10 index pulses) to
;  execute the next command before the motor shuts off.
;
;	*	*	*	*	*	*	*	*
MOTOR:
	PUSH	PSW		; save command
	IN	STAT		; Check motor on bit
	RAL			; .
	DB	JRC,MOTOROK-$-1	; Motor already on ...
	PUSH	H		; Save buffer pointer
	IN	RTRK		; Save 1770 trk, sec, & dta regs
	MOV	B,A		; .
	IN	RSEC		; .
	MOV	C,A		; .
	IN	RDAT		; .
	MOV	D,A		; .
	CALL	DWAIT		; Wait for the 1770 to get ready
	XRA	A		; Setup seek to same track cmd
	OUT	WTRK		; .
	OUT	WDAT		; .
	MVI	A,FSEEKNV	; .  (seek, no verify)
	OUT	CMND		; Send the cmd

	PUSH	B		; Save BC reg for wait cmd
	MVI	A,4		; Wait 4 * 250ms = 1 second
WAITMORE:
	PUSH	PSW		; Wait 250ms
	MVI	A,250		; .
	CALL	WAIT		; .
	POP	PSW		; .
	DCR	A		; More waiting?
	DB	JRNZ,WAITMORE-$-1 and 255
	POP	B		; Get BC reg back

	MOV	A,D		; Restore 1770 registers
	OUT	WDAT		; .
	MOV	A,C		; .
	OUT	WSEC		; .
	MOV	A,B		; .
	OUT	WTRK		; .
	POP	H		; restore buffer pointer

MOTOROK:
	POP	PSW		; Get command back
	RET			; and return

;	*	*	*	*	*	*	*	*
;
;  DWAIT	Wait for permission to write a register
;
;  This routine will wait up to 5 seconds for permission to write
;  to one of the FDC registers.  After 5 seconds, a FORCE INTERRUPT
;  command will be issued to the FDC.
;
;	*	*	*	*	*	*	*	*
DWAIT:
	LXI	H,TIMEOUT	; Point to timeout location
	MVI	M,6		; Set 6 major loops

DLOOP:	IN	STAT		; Get FDC status
	DB	BIT,BTST+B0+ZA	; Test bit 0 (BUSY), return with
	RZ			; .  zero status if busy non-active
	DCX	H		; See if enough minor loops
	MOV	A,H		; .  (Approx 58,000 times)
	ORA	L		; .
	JNZ	DLOOP		; Not done with minor loop

	IF	CLOCK
	CALL	CLKCHK		; tick toc
	ENDIF
	IF	BUFFKBD
	CALL	KEYCHK		; get keyboard entry
	ENDIF

	LXI	H,TIMEOUT	; Decrement major loop counter
	DCR	M		; .  (6 times)
	JNZ	DLOOP		; .
	MVI	A,0D0H		; Give up on waiting; issue a FORCED
	OUT	CMND		; .  INTERRUPT to the FDC
	XRA	A		; Set A to 0FFH and status to NZ
	DCR	A		; .
	RET			; Return to caller

;	*	*	*	*	*	*	*	*
;
;  RESTORE	Restore a floppy drive to track 00
;
;  This routine issues a re-zero command to the 1770 which causes
;  the drive indicated by LOGDSK to slowly find its way back to 
;  the track zero (00) sensor.
;
;	*	*	*	*	*	*	*	*
RESTORE:			; Restore the disk head to track 00
	CALL	DWAIT		; Wait for the 1770 to be ready
        RNZ			; Return to caller if timeout
	CALL	GETSTEP		; Get the step rate for this drive
	ORI	FRESTOR		; Add restore command to step rate
	CALL	OUTCMD		; Send the cmd to the 1770
	CALL	DWAIT		; Wait for the cmd to finish
	MVI	A,50		; Wait 50 ms for the drive to settle
	CALL	WAIT		; .
	CALL	MAPTRK		; Set this drive's last track
	MVI	M,0		; .  to track 00

NEXT$BLIP:
	MVI	A,FRDADDR	; Send the READ ADDRESS cmd to the
	CALL	OUTCMD		; .  FDC and see if we get anything
	CALL	DWAIT		; Return if cmd finished (implies

	IF	BEEP
	RZ			; .  both disk & drive present)
	MVI	C,BELL		; Beep if no disk
	CALL	CONOUT1		; .
	CALL	CONST1		; Check console status
	CNZ	CONIN1		; Get char if one is there
	CPI	CTRLC		; Was the char a ctrl-C?
	DB	JRZ,UABORT-$-1	; Yes, abort with error
	CPI	ESC		; Was the char an ESC?
	DB	JRNZ,255-($-NEXT$BLIP) ; No, try again
UABORT:
	XRA	A		; Return with error
	DCR	A		; .
	ENDIF

	RET			; .

;	*	*	*	*	*	*	*	*
;
;  SEEK		Seek to track (floppy only)
;
;  Moves the floppy head to the track indicated by CPMTRK if not
;  already there.
;
;	*	*	*	*	*	*	*	*
SEEK:
	CALL	MOTOR		; Make sure the motor is on
	CALL	GETTRK		; Get track we want
	PUSH	PSW		; Save track #
	CALL	SETUP		; Set up unit and real side
	POP	PSW		; Get track # back
	CALL	MAPTYPE		; Check double step bit
	DB	BIT,BTST+B6+ZM	; .
	DB	JRZ,NODS1-$-1	; Skip double step if bit clear
	ADD	A		; Otherwise multiply by 2
NODS1:	
	OUT	WDAT		; Tell the 1770 where we want to go

	CALL	MAPTRK		; Get the current track #
	MOV	A,M		; .
	CALL	MAPTYPE		; Check double step bit
	DB	BIT,BTST+B6+ZM	; .
	DB	JRZ,NODS2-$-1	; Skip double step if bit clear
	ADD	A		; Otherwise multiply by 2
NODS2:
	OUT	WTRK		; Tell the 1770 where we are
	CALL	GETSTEP		; Get the step rate for this drive
	ORI	FSEEKNV		; OR in seek without verify cmd
	PUSH	PSW		; Save A reg as DWAIT destroys it
	CALL	DWAIT		; Wait until 1770 is ready
	POP	PSW		; Get A reg back (1770 cmd)
	CALL	OUTCMD		; Send the cmd
	CALL	DWAIT		; Wait for the cmd to finish
	ANI	18H		; Isolate possible failures
	RNZ			; Seek failed, return error

	MVI	A,FRDADDR	; Read current track info
	LXI	H,IDSAVE	; .
	CALL	RDATA		; .
	CALL	MAPTRK		; Get ptr to track save area
	CALL	GETTRK		; Get track we were seeking
	MOV	B,A		; Save it for a moment
	IN	RSEC		; Get track # from the FDC
	CMP	B		; Compare to what we wanted
	MOV	M,A		; Save FDC track # in either case
	RET			; Z=ok, NZ=error

;	*	*	*	*	*	*	*	*
;
;  GETSTEP	Get the step rate for a particular drive
;
;  Sets the HL register pair to the location which contains the 
;  step rate for the drive indicated by PHYDRV, and returns the
;  actual step bits in the A register.
;
;  Registers modified:	A, PSW, DE, HL
;
;	*	*	*	*	*	*	*	*
GETSTEP:
	LDA	PHYDRV		; Get physical drive unit
	CPI	04		; E-disk?
	DB	JRNZ,GET$AD-$-1	; No,  use normal unit #
	LDA	EDSD		; Yes, use E-disk unit #
GET$AD:
	MOV	E,A		; Get step rate from table
	MVI	D,0		; .  (based on physical unit)
	LXI	H,STPRAT	; .
	DAD	D		; .
	MOV	A,M		; .
	ANI	03H		; Mask off junk, just in case
	RET			; And return data in A and HL

;	*	*	*	*	*	*	*	*
;
;  MAPTYPE	Get pointer to physical type byte
;
;  Sets the HL register pair to the location which contains the 
;  type byte for the physical drive indicated by PHYDRV.
;
;  The format of the physical type bytes can be found in the 
;  description of DRIVE$TYPES at BIOS+070H.
;
;  Registers modified:	DE, HL
;
;	*	*	*	*	*	*	*	*
MAPTYPE:
	PUSH	PSW		; Save entry A reg and flags
	LDA	PHYDRV		; Get physical drive unit
	MOV	E,A		; Compute pointer to physical
	MVI	D,0		; .  type byte
	LXI	H,DRIVE$TYPES	; .
	DAD	D		; .
	POP	PSW		; Get A reg and flags back
	RET			; And return with ptr in HL

;	*	*	*	*	*	*	*	*
;
;  MAPTRK	Return pointer to current track
;
;  Sets the HL register pair to the location which contains the 
;  current track for the drive indicated by PHYDRV.
;
;  Registers modified:	A, PSW, DE, HL
;
;	*	*	*	*	*	*	*	*
MAPTRK:
	LDA	PHYDRV		; Get physical drive unit
	CPI	04		; E-disk?
	DB	JRNZ,MAP$AD-$-1	; No,  use normal unit #
	LDA	EDSD		; Yes, use E-disk unit #
MAP$AD:
	MOV	E,A		; Compute ptr to current track
	MVI	D,0		; .
	LXI	H,LTRACK	; .
	DAD	D		; .
	RET			; Return with pointer in HL

;	*	*	*	*	*	*	*	*
;
;  SETUP	Setup the system control register for a drive select
;
;  This routine writes to the system control port register at I/O
;  00H, causing the associated drive to be selected, along with
;  the head select signal line.
;
;  NOTE:  This routine always turns the internal system rom OFF,
;  without any regard for those who just may have the rom enabled
;  prior to entry.
;
;  NOTE:  This routine modifies all registers
;
;	*	*	*	*	*	*	*	*
SETUP:
	CALL	MAPTYPE		; Get pointer to drive type byte
	MOV	A,M		; Get byte
	ORI	040H		; Turn off EPROM
	LXI	H,HSTSID	; Point to side select
	ORA	M		; Include proper side select

	MOV	C,A		; Save control byte
	ANI	1000$1111B	; Mask out eprom, density, & side
	LXI	H,CHGDSK	; Compare with previous results
	CMP	M		; .
	MOV	M,A		; .  (save this result anyway)
	DB	JRZ,SETUP2-$-1	; Skip split & delay if same

	MOV	A,C		; Get control byte back
	ANI	0F0H		; Mask out drive bits
	OUT	CONT		; Send speed only to control port
	MOV	A,C		; Get control byte back
	OUT	CONT		; Send full byte to the control port

	MVI	A,HLDELAY	; Delay HLDELAY ms (usually 30ms)
	CALL	WAIT		; .
	RET
SETUP2:
	MOV	A,C		; Get control byte back
	OUT	CONT		; Send to control port
	RET

;	*	*	*	*	*	*	*	*
;
;  WAIT		Wait "A" ms
;
;  Modifies:	A,PSW  (A = 0, Z flag set)
;
;	*	*	*	*	*	*	*	*
WAIT:
	PUSH	B
WAIT1:
	MVI	B,199
WAIT2:	CMP	M
	DB	10H,WAIT2-$-1 and 255	; DJNZ WAIT1

	IF	CLOCK
	CALL	CLKCHK			; tick toc
	ENDIF
	IF	BUFFKBD
	CALL	KEYCHK			; get keyboard entry
	ENDIF

	DCR	A
	DB	JRNZ,WAIT1-$-1 and 255	; JRNZ WAIT2
	POP	B
	RET

;	*	*	*	*	*	*	*	*
;
;  GETTYPE	Get the type byte and ptr for a drive unit
;
;  Returns the current disk type identifier in the A reg, and the
;  address of the entry in the HL reg pair.
;
;  See PHYTAB (driver table) for the definition of the type byte.
;
;  Modifies:	A, DE, HL
;
;	*	*	*	*	*	*	*	*
GETTYPE: 
	LDA	LOGDSK		; Use logical disk entry value
	CALL	PAGET		; Get ptr to table entry
	INX	H		; Type byte is at table+2
	INX	H		; .
	MOV	A,M		; Get type byte
	RET			; Return type in A, ptr in HL

GETSEC: 		;CONVERT LOGICAL SECTOR TO PHYSICAL SECTOR
	PUSH	H
	CALL	GETTYPE
	ANI	3		; 128 BYTE SECTOR?
	LDA	CPMSEC		;NOPE, DO MAPPING
	DB	JRZ,GOTSDS-$-1
	LDA	HSTSEC

GOTSDS:	PUSH	PSW		;SAVE SECTOR
	CALL	GETDPT		;FETCH POINTER TO XLT TABLE
	MOV	E,M
	INX	H
	MOV	D,M
	XCHG			;TRANSLATE TABLE NOW IN HL
	POP	PSW		;RESTORE DESIRED SECTOR
	MOV	E,A
	MVI	D,0
	DAD	D
	MOV	A,M		;GET PHYSICAL SECTOR FROM TABLE
	MOV	E,A		;SAVE IT
	LDA	PHYDRV
	CPI	04H		;WAS IT E DISK?
	DB	JRNZ,GSEXIT-$-1	;NO, THEN NO ADJUST
	LDA	HSTSID		;WHICH SIDE?
	ORA	A
	DB	JRZ,GSEXIT-$-1
	LDA	ESECADJ 	;ELSE GET ADJUSTMENT
	ADD	E
	MOV	E,A		;PUT IT BACK FOR MOVE

GSEXIT:	MOV	A,E		;MOVE REAL SECTOR TO A
	POP	H
	RET

GETBUF: CALL	GETTYPE		;GET THE DMA ADDRESS
	ANI	3		; 128 BYTE SECTORS?
	LXI	H,HSTBUF	;USED FOR DD
	RNZ
	LHLD	DMAADR		;ELSE USE REAL DMA ADDRESS...
	RET

;	*	*	*	*	*	*	*	*
;
;  GETTRK	Convert logical to physical track
;
;  This routine converts the logical track stored in CPMTRK to
;  the actual physical track and side to be used.
;
;  The physical track is returned in the A register, and the side
;  to be used is returned in HSTSID.
;
;	*	*	*	*	*	*	*	*
GETTRK:	PUSH	H		; Save HL register
	CALL	GETTYPE		; Get the type byte for this disk
	LXI	H,HSTSID	; Point to the side flag
	RAL			; Determine SS or DS
	RAL			; .
	LDA	CPMTRK		; Get track #
	DB	JRNC,GETTRK1-$-1 ; Not DS -- leave track alone
	RAR			; Divide track by 2 to get side
	DB	JRNC,GETTRK1-$-1 ; No carry means side 0
	MVI	M,10H		; Indicate side 1
	JMP	GETTRK2		; And finish up
GETTRK1:
	MVI	M,0		; Indicate side 0
GETTRK2:
	POP	H		; Get HL register back
	ANI	07FH		; Mask high bit, just in case
	RET			; ... and return

;	LOGICAL DEVICE	  PHYSICAL DEVICE ASSIGNMENTS
;	--------------	  ---------------------------
;	CON:		  CRT: OR TTY:
;	READER: 	  TTY:
;	PUNCH:		  TTY:
;	LIST:		  CRT: OR TTY: OR LPT:

CONST:	;CONSOLE STATUS, RETURNS 0FFH IF CHARACTER READY, ELSE 00H
	IF	LOCAL$STACK
	DW	73EDH,OLDSTK	; ld(oldstk),sp - save old stack
	LXI	SP,LOCSTK	; set local stack
	ENDIF
CONST1:	
	IF	CLOCK
	CALL	CLKCHK		; tick
	ENDIF
	IF	BUFFKBD
	CALL	KEYSTAT		; see if char in buffer
	RNZ			; return with buffer status
	ENDIF
	LDA	IOBYTE		;CHECK DEVICE ASSIGNMENT
	ANI	11B		;KEEP CON BITS ONLY
	JZ	TTYIST		; check appropriate input status
	JMP	CRTIST		; no third or fourth choices

CONIN:	;CONSOLE CHARACTER INTO REGISTER A
	IF	LOCAL$STACK
	DW	73EDH,OLDSTK	; ld(oldstk),sp - save old stack
	LXI	SP,LOCSTK	; local stack
	ENDIF
CONIN1:
	IF	BUFFKBD
	CALL	KEYSTAT
	JNZ	KEYRET		; get character from buffer
	ENDIF
	LDA	IOBYTE
	ANI	11B
	JZ	TTYIN		; do ttyin
	JMP	CRTIN		; do crtin

CONOUT: ;CONSOLE CHARACTER OUTPUT FROM REGISTER C
	IF	LOCAL$STACK
	DW	73EDH,OLDSTK	; ld(oldstk),sp - save old stack
	LXI	SP,LOCSTK	; set local stack
	ENDIF
CONOUT1:
	IF	CLOCK
	CALL	CLKCHK		; tick toc
	ENDIF
	IF	BUFFKBD
	CALL	KEYCHK		; get any keyboard character
	ENDIF
	LDA	IOBYTE
	ANI	11B
	JZ	TTYOUT		; out tty
	JMP	CRTOUT		; out crt

READER: ;READ CHARACTER INTO REGISTER A FROM READER DEVICE
	JMP	TTYIN		; no choice supported

PUNCH:	;PUNCH CHARACTER FROM REGISTER C
	JMP	TTYOUT		; no choices supported

LISTST: ;RETURN LIST STATUS, 0FFH IF READY, ELSE 00H
	IF	LOCAL$STACK
	DW	73EDH,OLDSTK	; ld(oldstk),sp - save old stack
	LXI	SP,LOCSTK	; local stack
	ENDIF
LSTST1:	LDA	IOBYTE		;CHECK DEVICE ASSIGNMENT
	ANI	11000000B	;KEEP LST BITS ONLY
	DB	JRNZ,LSTST2-$-1	; tty ?
	JMP	TTYOST		; check tty output status

LSTST2:	ANI	01000000B	;CRT?
	DB	JRZ,LPTST-$-1	; lpt
	DB	JR,CRTOST-$-1	; crt

LIST:	;LIST CHARACTER OUTPUT FROM REGISTER C
	IF	LOCAL$STACK
	DW	73EDH,OLDSTK	; ld(oldstk),sp - save old stack
	LXI	SP,LOCSTK	; set stack pointer
	ENDIF
LIST1:	
	IF	CLOCK
	CALL	CLKCHK		; tic toc
	ENDIF
	IF	BUFFKBD
	CALL	KEYCHK		; get any keyboard characters
	ENDIF
	CALL	LSTST1		;WAIT TILL READY TO SEND
	DB	JRZ,255-($-LIST1)
	LDA	IOBYTE		;CHECK DEVICE ASSIGNMENT
	ANI	11000000B	;KEEP LST BITS ONLY
	JZ	TTYOUT
	CPI	01000000B
	DB	JRZ,CRTOUT-$-1
	DB	JR,LPTOUT-$-1	; no forth choice

CRTOST: ; CRT OUTPUT STATUS, RETURN 0FFH IF READY TO SEND, 00H IF NOT
	; CRT "ALL SENT" STATUS NOW CHECKED

	; Note:  This former routine (IN SIOCPA   ANI TBE   RZ) requires
	; autoenables to be set to function correctly.  The present
	; routine uses the "ALL SENT" bit to determine if the previous
	; byte has been sent from the DART.  Under the previous routine,
	; there is a possibility of a character getting into the output
	; buffer when there is no ready signal.

	MVI	A,01H		; Check "all sent" bit in register 1
	OUT	SIOCPA		; .
	IN	SIOCPA		; .
	ANI	01H		; "ALL SENT" is bit 0
	RZ			;TRANSMIT BUFFER NOT READY
	LDA	HSA		;SEE IF CTS H/S REQUIRED
	ORA	A		
	DB	JRZ,CRTRDY-$-1	; if zero, no handshake needed
	;CRT CTS HANDSHAKE SIGNAL STATUS NOW CHECKED
	MVI	A,10H
	OUT	SIOCPA		;UPDATE UART STATUS
	IN	SIOCPA		;FETCH STATUS
	ANI	CTS
	RZ			;CTS NOT ACTIVE
CRTRDY:	ORI	255		;SHOW READY TO END
	RET

CRTIST: ;CRT INPUT STATUS, RETURN 0FFH IF DATA READY, 00H IF NOT
	IN	SIOCPA		;FETCH STATUS
	ANI	RDA
	RZ			;NOT READY
	ORI	255		;GOT SOMETHING, SIGNAL ITS PRESENCE
	RET

CRTOUT:
	IF	BUFFKBD		; Check for console input while waiting
	CALL	KEYCHK		; to output a character (for handshake
	ENDIF			; intensive terminals, Versabrailles)
	IF	CLOCK
	CALL	CLKCHK		; tick toc
	ENDIF
	CALL    CRTOST  	;OK TO SEND?
	DB	JRZ,255-($-CRTOUT)	; NO WAIT
	MOV	A,C		;CHARACTER TO REGISTER A
	OUT	SIODPA
	RET

CRTIN:	
	IF	CLOCK
	CALL	CLKCHK		; tick toc
	ENDIF
	CALL	CRTIST		;READY?
	DB	JRZ,255-($-CRTIN)	; NO, WAIT
	IN	SIODPA		;ELSE FETCH IT
	ANI	7FH		;STRIP PARITY BIT
	RET

LPTOUT: ;PRINTER CHARACTER OUTPUT FROM REGISTER C
	IF	BUFFKBD
	CALL	KEYCHK		; test for console input
	ENDIF
	IF	CLOCK
	CALL	CLKCHK		; tick toc
	ENDIF
	CALL	LPTST		;PRINTER READY?
	DB	JRZ,255-($-LPTOUT)	; not ready, wait
	MOV	A,C
	OUT	PIO1		;SET UP THE DATA
	OUT	STBSET		;SEND A DATA STROBE
        OUT     STBCLR          ;  (DATA DOESN'T MATTER)
	RET

LPTST: ;RETURN LIST STATUS, 0FFH IF READY, ELSE 00H
	MVI	A,10H		;UPDATE UART STATUS
	OUT	SIOCPB
	IN	SIOCPB		;READ PRINTER BUSY SIGNAL
	ANI	10H
	RZ			;NOT READY
	ORI	255		;SHOW READY
	RET

TTYOST: ;TTY OUTPUT STATUS, RETURN 0FFH IF READY TO SEND, 00H IF NOT
	;TTY "ALL SENT" STATUS NOW CHECKED

	; Note:  This former routine (IN SIOCPB   ANI TBE   RZ) requires
	; autoenables to be set to function correctly.  The present
	; routine uses the "ALL SENT" bit to determine if the previous
	; byte has been sent from the DART.  Under the previous routine,
	; there is a possibility of a character getting into the output
	; buffer when there is no ready signal.

	MVI	A,01H		; Check "all sent" bit in register 1
	OUT	SIOCPB		; .
	IN	SIOCPB		; .
	ANI	01H		; "ALL SENT" is bit 0
	RZ			;TRANSMIT BUFFER NOT READY
	LDA	HSB		;SEE IF CTS H/S REQUIRED
	ORA	A		
	DB	JRZ,TTYRDY-$-1	; if zero, no h/s needed
;TTY CTS HANDSHAKE SIGNAL STATUS NOW CHECKED
	MVI	A,10H
	OUT	SIOCPB		;UPDATE UART STATUS
	IN	SIOCPB		;FETCH STATUS
	ANI	CTS
	RZ			;CTS NOT ACTIVE
TTYRDY:	ORI	255		;SHOW READY TO SEND
	RET

TTYIST: ;TTY INPUT STATUS, RETURN 0FFH IF DATA READY, 00H IF NOT
	IN	SIOCPB		;FETCH STATUS
	ANI	RDA
	RZ			;NOT READY
	ORI	255		;GOT SOMETHING, SIGNAL ITS PRESENCE
	RET

TTYOUT:
	IF 	BUFFKBD
	CALL	KEYCHK		; check for console char
	ENDIF
	IF	CLOCK
	CALL	CLKCHK		; tic toc
	ENDIF
	CALL	TTYOST		;OK TO SEND?
	DB	JRZ,255-($-TTYOUT)	;no wait
	MOV	A,C		;CHARACTER TO REGISTER A
	OUT	SIODPB
	RET

TTYIN:	
	IF	CLOCK
	CALL	CLKCHK		; tick toc
	ENDIF
	IN	SIOCPB
	ANI	RDA		;CHAR READY?
	DB	JRZ,255-($-TTYIN)	; no, wait
	IN	SIODPB		;ELSE FETCH IT
	ANI	7FH		;STRIP PARITY BIT
	RET

; Keychk will poll the console for characters during disk waits or
; during common read/write routines and during terminal output.  
; this will prevent most loss of input characters during disk i/o 
; when programs are not polling for input.  
; NOTE: test for console input only (SIO A side).
; 
	IF	BUFFKBD
KEYCHK:	
	PUSH	PSW
	IN	SIOCPA		; get SIO status
	ANI	RDA		; recive ready
	DB	JRZ,KEYEXT-$	; jrz,keyext+1

	PUSH	H		; save 'hl'
	LHLD	KEYEND		; next character position in buffer
	LDA	KEYLEN		; get low byte of max addr
	CMP	L		; see if end of buffer
	DB	JRZ,KEYEXT-$-1	; no more buffer available
	IN	SIODPA		; get data
	ANI	7FH		; strip parity
	MOV	M,A		; save char
	INX	H		; next position in buffer
	SHLD	KEYEND		; save new pointer

KEYEXT:	POP	H
	POP	PSW
	RET

; Keystat is called by the console stat routine. returns non-zero if
; character in buffer or zero if no character.
;
KEYSTAT:
	PUSH	B		; save 'B' register
	LDA	KEYEND		; get low byte of last byte in buffer
	MOV	B,A		; save
	LDA	KEYST		; get low byte of start of buffer
	CMP	B
	MVI	A,0
	DB	JRZ,KSTEX-$-1	; if same exit, nothing in buffer
	ORI	255		; show buffer has character

KSTEX:	POP	B		; restore register
	RET			; zero flag has status
;
; Keyret returns the next character in buffer is called by conin
;
KEYRET:	PUSH	H		; save registers
	PUSH	B

	LHLD	KEYST		; first character position
	MOV	B,M		; get character
	INX	H
	SHLD	KEYST		; next character
	LDA	KEYEND		; get low byte of last char in buffer
	CMP	L		; same
	MOV	A,B		; get character for exit
	DB	JRNZ,KREX-$-1	;
	LXI	H,KEYBUF	; reset buffer start and end
	SHLD	KEYST
	SHLD	KEYEND

KREX:	POP	B		; restore register
	POP	H
	RET
	ENDIF

; clkchk will poll the ctc for change in count. Was adapted from
; the clock routines developed by Roger Ward.

	IF	CLOCK
CLKCHK:	
	PUSH	H		; save registers
	PUSH	PSW
	LXI	H,TICK
	IN	CTCA3		; get down count value
	CMP	M		; see if count has rolled over
	JZ	CLKEXT		; same count value
	MOV	M,A		; save new count
	JC	CLKEXT		; counter < last count, not rolled
	LXI	H,SECNT		; seconds count
	INR	M		; increment seconds
	MVI	A,60
	CMP	M
	DB	JRNZ,CLKEXT-$-1	; exit if no rollover
	MVI	M,0		; seconds to 0
	INX	H		; point to min.
	INR	M		; one more min.
	CMP	M		; 'a' = 60
	DB	JRNZ,CLKEXT-$-1	; exit if no rollover
	MVI	M,0		; min=0
	INX	H		; point to hours
	INR	M		; one more hour
	MVI	A,24
	CMP	M
	DB	JRNZ,CLKEXT-$-1	; exit, same day still
	MVI	M,0		; hours=0
	LHLD	DAYCNT
	INX	H		; increment days
	SHLD	DAYCNT
	DB	BIT,BTST+B0+ZH	; test less than >256
	DB	JRZ,CLKEXT-$-1	;
	MVI	A,366-256	; days in calendar(include leap year)
	CMP	L
	DB	JRNZ,CLKEXT-$-1	; exit no wrap
	LXI	H,0
	SHLD	DAYCNT		; day 0
	LXI	H,YRCNT
	INR	M
	MVI	A,100
	CMP	M		; year wrap ?
	DB	JRNZ,CLKEXT-$-1	; no, exit
	MVI	M,0		; else clear
	
CLKEXT:	POP	PSW		; restore stack
	POP	H
	RET

TOD:	LXI	H,SECNT		; point to clock storage
	RET
;
SECNT:	DB	0		; sec count
MINCNT:	DB	0
HRCNT:	DB	0
DAYCNT	DW	0
YRCNT:	DB	0
TICK:	DB	255		; storage for clock tick
	ENDIF
;
;	*	*	*	*	*	*	*	*
;
; LOCSTK is the common return point for operations that use
;      the local bios stack. Routine reatores the callers
;      stack pointer and returns.
;	*	*	*	*	*	*	*	*
; local stack area
	IF	LOCAL$STACK
	DS	32		; 16 level stack
LOCSTK:	DW	EXIT		; exit routine
OLDSTK:	DS	2		; save area for old stack
EXIT:	DW	7BEDH,OLDSTK	; ld sp,(oldstk) - restore stack
	RET
	ENDIF

;
; Storage for buffered keyboard
;
	IF	BUFFKBD
KEYST:	DW	KEYBUF		; Storage for begining of buffer pointer
KEYEND:	DW	KEYBUF		; Storage for end of buffer pointer
KEYLEN:	DW	KEYSTOP		; last address in keyboard buffer
	ENDIF

MAINEND	EQU	$		; End of main body of code

	IF	HARD$DISK
;	*	*	*	*	*	*	*	*
;
;	HARD DISK Disk parameter blocks
;
;	A system configuration entry, or a user defined assembly
;	is to be used to set up the track offset entry to create
;	various hard disk partitions. This is done to support drives
;	with greater than 20Mbyte storage.
;
;	*	*	*	*	*	*	*	*
HD$DPB$BASE:	EQU	$
;
;		# of  --mask--  disk  # of   rsv'd  chk  trk
;		sect  bs bm em  size  d ent  block  siz  ofs  x

FPARM:	DB	64,0,  5,31, 1, 16,0, 255,3, 255,0, 0,0, 2,0, 0
GPARM:	DB	64,0,  5,31, 1, 16,0, 255,3, 255,0, 0,0, 2,0, 0
HPARM:	DB	64,0,  5,31, 1, 16,0, 255,3, 255,0, 0,0, 2,0, 0
IPARM:	DB	64,0,  5,31, 1, 16,0, 255,3, 255,0, 0,0, 2,0, 0
JPARM:	DB	64,0,  5,31, 1, 16,0, 255,3, 255,0, 0,0, 2,0, 0
KPARM:	DB	64,0,  5,31, 1, 16,0, 255,3, 255,0, 0,0, 2,0, 0
LPARM:	DB	64,0,  5,31, 1, 16,0, 255,3, 255,0, 0,0, 2,0, 0
MPARM:	DB	64,0,  5,31, 1, 16,0, 255,3, 255,0, 0,0, 2,0, 0
NPARM:	DB	64,0,  5,31, 1, 16,0, 255,3, 255,0, 0,0, 2,0, 0
OPARM:	DB	64,0,  5,31, 1, 16,0, 255,3, 255,0, 0,0, 2,0, 0
PPARM:	DB	64,0,  5,31, 1, 16,0, 255,3, 255,0, 0,0, 2,0, 0

HD$DPB$LEN:	EQU	$-HD$DPB$BASE

MY$ID:		DB	0	; Board SCSI ID saved here
HD$BYTE$BLOCK:	DB	0	; SCSI Byte/block mode saved here

;	*	*	*	*	*	*	*	*
;
;  DIRECT SCSI DRIVER  
;
;  Entry here via the BIOS Jump tables as JMP SCSI
;
;  Enter with the following registers set:
;
;	A   SCSI target address to be used
;	HL  Pointer to SCSI Command block
;	DE  Pointer to SCSI Data Block
;
;  Exits with HL preserved.  DE is preserved if the operation is 
;  successful, otherwise DE contains the sense data (error bytes).
;  The A regisner contains the ending status of the SCSI command.
;
;  NOTE: This routine saves and restores the previous TARGET
;	address for normal operations.
;
;	*	*	*	*	*	*	*	*
SCSI:
	MVI	C,1		; Set C register for 1 byte block
SCSI$HSPEED:
	STA	TARGET		; Save target address
 	PUSH	H		; Save entry command pointer
	PUSH	D		; .  and entry data pointer
	SHLD	CMDPTR		; Set CMDPTR to what HL is pointing to
	XCHG			; Set the data pointer to HL

	CALL	SCSI$CMD	; Do the direct routines

	JZ	SCSI$END$OK	; If ok, restore cmd & data ptrs
	POP	H		; Otherwise, throw away data ptr
	JMP	SCSI$END$ERR	; .  by popping cmd pointer twice

SCSI$END$OK:
	POP	D		; Restore data pointer
SCSI$END$ERR:
	POP	H		; Restore command pointer
	RET			; All done

;
;  SCSI return sense data command (Cmd 03)
;
SCSI$STAT$CMD:
  	DB 	3		; 00 - REQUEST SENSE COMMAND
  	DB 	0		; 01 - LOGICAL UNIT
  	DB 	0		; 02 - RESERVED
  	DB 	0		; 03 - RESERVED
  	DB 	4		; 04 - NUMBER OF BYTES
  	DB 	0		; 05 - RESERVED

;
;  SCSI read/write command (Cmd 08/0A)
;
SCSI$RD$CMD:	EQU	08H		; 08 IS READ DATA
SCSI$WR$CMD:	EQU	0AH		; 0A IS WRITE DATA

SCSI$RW$CMD:	DB 	SCSI$RD$CMD	; 00 - 08=Read, 0A=Write
HIGH$ADDR: 	DB 	0		; 01 - High address
MED$ADDR:	DB 	0		; 02 - Middle address
LOW$ADDR:	DB 	0		; 03 - Low address
  		DB 	1		; 04 - Number of sectors
STEP$RATE:	DB 	0		; 05 - Step rate (Xebec)

;	*	*	*	*	*	*	*	*
;
;  Hard disk driver (Driver #3)
;
;	FF	Select Disk
;	00	Write Disk
;	01	Read Disk
;
;	*	*	*	*	*	*	*	*
DRVR3:	INR	A		; select command = 0ffh
	JZ	SELEND		; do select
	LXI	H,TAGPHY	; Get address & step rate
	MOV	A,M		; .
	ANI	0FH		; Mask off address
	STA	STEP$RATE	; Store step rate in SCSI cmd
	INX	H		; Bump ptr to unit #
	MOV	A,M		; Get unit # & type 
	ANI	0E0H		; Mask off type
	STA	LOGUNIT		; Save unit #
	INX	H		; Bump ptr to SCSI addr
	MOV	A,M		; Get SCSI address
	STA	TARGET		; Save as target address

; fall through to SCSI$IO and do the read/write

;	*	*	*	*	*	*	*	*
;
;  SCSI READ/WRITE HOST ROUTINES
;
;  Entry here from the HOSTIO section of the blocking/deblock
;  routines. Assumes that the TARGET address has been pre-set.
;
;  Exits with the error code in (A), and the sense status saved
;  at SCSI$STAT$DAT if an error occurs.
;
;	*	*	*	*	*	*	*	*
SCSI$IO:
  	LDA 	RWHOST
	ANA	A		; write = 0
;
;  Write to the hard disk described by (TARGET) and (LOGUNIT)
;
SCSI$WR:
	MVI	A,SCSI$WR$CMD	; Set write command 
	DB	JRZ,SCSI$DO$RW-$-1

;
;  Read from the hard disk described by (TARGET) and (LOGUNIT)
;
SCSI$RD:
	MVI	A,SCSI$RD$CMD	; Set read command

;
;  Do the rest of the setup for the read/write command
;
SCSI$DO$RW: 
	LXI	H,SCSI$RW$CMD	; Get command string
	MOV	M,A		; Plug the SCSI command 
	SHLD	CMDPTR		; Save the command pointer
	CALL	BLD$SCSI$SCTR	; Build SCSI sector address
  	LXI 	H,HSTBUF	; Get pointer to host buffer
	LDA	HD$BYTE$BLOCK	; Get SCSI byte/block mode value
	MOV	C,A		; Stuff in C register

;
;  Enter here from the SCSI direct driver routine
;
;  Exits with status in A.  0FFH = timeout error
;
SCSI$CMD:
	SHLD 	DATPTR 		; Save the data pointer
	LXI	H,INT$RETRIES	; retry save area
	MVI	M,2		; retries
	LXI	H,SCSI$IO$COUNT	; And the # of bytes to transfer
	MOV	M,C		; .  at one time (each block)

SCSI$CMD$RETRY:
	CALL 	SELECT 		; Perform the SCSI operation
	LDA 	STATUS		; Get the return status
	STA 	ERFLAG		; Save ending status
	CPI	0FFH		; Timeout?
	DB	JRZ,SCSI$DONE-$-1 ; Yes, go save timeout status
	ANI 	2		; Check for SCSI error status
	RZ			; No error -- return

	LXI	H,INT$RETRIES	; see if any retries remain
	DCR	M
	DB	JRZ,SCSI$DONE-$-1 ; NO more remain, exit and set flags

; Save current command and data pointers.

	LHLD	DATPTR		; save old data pointer
	SHLD	SAVE$DATPTR
	LHLD	CMDPTR		; save old command pointer
	SHLD	SAVE$CMDPTR
	MVI	A,1
	STA	SCSI$IO$COUNT	; force byte xfer mode for retries..

; Request SCSI sense.

	LXI	H,SCSI$STAT$DAT	; .  for request sense command
	SHLD	DATPTR
	LXI	H,SCSI$STAT$CMD	; Set up data and command pointers
	SHLD	CMDPTR
	LDA	LOGUNIT		; Get logical unit number
	INX	H		; Stuff in proper location in
	MOV	M,A		; .  SCSI command
	CALL	SELECT		; Execute request sense command

; Restore pointers and retry command

	LHLD	SAVE$DATPTR	; restore old data pointer
	SHLD	DATPTR
	LHLD	SAVE$CMDPTR	; restore old command pointer
	SHLD	CMDPTR
	JMP	SCSI$CMD$RETRY	; command retry

SCSI$DONE:
	ORA	A		; Set Z/NZ for user
	RET			; and return

;	*	*	*	*	*	*	*	*
;
;  Build 3-byte SCSI sector number and logical unit number
;
;  NOTE:  This routine assumes 16 sectors per track.
;
;  It is safe to assume 16 sectors per track even though some hard
;  disk controllers format more (Xebec=17, Adaptec=18, etc.) as the
;  read and write commands use a block number, rather than a track
;  and sector number to move to the correct block to read or write.
;
;	*	*	*	*	*	*	*	*
BLD$SCSI$SCTR:
	XRA	A		; Clear A & Carry

				; A reg     C  HL reg pair
	LHLD	CPMTRK		; --------  -  FEDCBA9876543210
	DAD	H		; --------  F  EDCBA9876543210-
	RAL			; -------F  -  EDCBA9876543210-
	DAD	H		; -------F  E  DCBA9876543210--
	RAL			; ------FE  -  DCBA9876543210--
	DAD	H		; ------FE  D  CBA9876543210---
	RAL			; -----FED  -  CBA9876543210---
	DAD	H		; -----FED  C  BA9876543210----
	RAL			; ----FEDC  -  BA9876543210----
	MOV	B,A		; 
	LDA	LOGUNIT		; 
	ORA	B		; Or in logical unit number
	STA	HIGH$ADDR	; Save unit number & high byte
	MOV	A,H		; 
	STA	MED$ADDR	; Save middle byte
	LDA	HSTSEC		; Get sector number
	ORA	L		; Or in low byte
	STA	LOW$ADDR	; Save new low byte
	RET	

;
;  Select controller, and fall through to phase if selected ok.
;
busbsy:	equ	40h

SELECT:	XRA	A
	OUT	NCRICR		; Clear initiator command register
	OUT	NCRTCR		; .  and target command register

CLEAR$ARBIT:
	XRA	A
	OUT	NCRMR		; .
	IN	NCRRPI		; reset interrupts
	ENDIF

	IF	HARD$DISK AND ARBITRATION
ARBITRATE:
	LDA	MY$ID		; Assert my ID (the initiator)
	OUT	NCRODR		; .
	MVI	A,1		; start arbitration
	OUT	NCRMR		; .

IN$PROGRESS:
	IN	NCRICR		; Wait for "arbitration in 
	ANI	40H		; .  progress" bit
	JNZ	ARBITRATE$WON	; we have arbitration
	IN	NCRBSR
	ANI	10H		; see if scsi reset has occured 
	JZ	IN$PROGRESS
	JMP	CLEAR$ARBIT

ARBITRATE$WON:
	LDA	MY$ID
	MOV	B,A
	IN	NCRCSD		; See if we're the highest priority
	SUB	B		; .  remove my addr
	SUB	B		; .  compare my addr to bus data
	JM	I$WIN		; We win if result < 0
	JMP	CLEAR$ARBIT	; .  otherwise we lose -- start over

I$WIN:	IN	NCRICR		; Check again for lost arbitration
	ANI	20H		; .  (just in case)
	JNZ	CLEAR$ARBIT	; We lost -- start over


	MVI	A,08H		; Set assert BSY bit in ICR
	OUT	NCRICR		; .
	IN	NCRMR		; Reset arbitration bit
	ANI	0FEH		; .
	OUT	NCRMR		; .

	IN	NCRICR		; OR in SEL to ICR
	ORI	04H
	OUT 	NCRICR		
	ENDIF

	IF HARD$DISK
	LDA	MY$ID		; Select target: get our ID,
	MOV	B,A		; .
	LDA	TARGET		; .  or in target ID
	ORA	B		; .
	OUT	NCRODR		; .  and send to NCR chip

	IN	NCRICR
	ORI	01H		; Assert data bus
	OUT	NCRICR		; .

	MVI	A,05H		; Release BSY, keep SEL	
	OUT	NCRICR		; .  and assert data bus

	LXI	B,6000H		; 250 ms loop (1M cycles)
STIM:
	IN	NCRCSBS		; Wait for BSY
	ANI	BUSBSY		; .
	JNZ	SELECT$OK	; Got him!

	DCR	C
	JNZ	STIM		; inner loop:  41*256 = 10496 cycles
	DCR	B
	JNZ	STIM		; outer loop: 10510*96 = 1M cycles

	XRA	A		; Select timeout -- clear bus
	OUT	NCRODR

	DCR	A		; set timeout to 'a'
	STA	STATUS
	JMP	ALL$DONE	; and clear the registers

SELECT$OK:
	XRA	A		; Set good status

ALL$DONE:
	MOV	B,A		; save status
	MVI	A,01H		; Release SEL
	OUT	NCRICR		; .
	XRA	A		; Release data bus
	OUT	NCRICR		; .
	MOV	A,B		; get status back
	ORA	A		; Set status
	RNZ
	DCR	A		; clear scsi status to timeout, 0ffh
	STA	STATUS		; Save status for PWIDIR routine

; SCSI.011
; * * * * *  
; *  --------\	NOTE: we fall through if we successfully
; *  --------/	selected the controller!!
; * * * * *

	MVI	A,00000110B	; Set DMA mode and Monitor Busy  
	OUT	NCRMR		; .

SCSI$RDY:
; Wait for either a 5380 "Interrupt" or a REQ from Target.
; The REQ is needed since it may have come too soon after
; selection to register an Interrupt.
	IN	NCRBSR		; Check for "Interrupt"
	ANI	00010000B	; .
	DB	JRNZ,SCSI$INT-$-1
	IN 	NCRCSBS		; Check for REQ
	ANI	NCRREQ		; .
				; Wait for Interrupt or REQ
	DB	JRZ,255-($-SCSI$RDY)
	JMP	PHASE		; Process phase vector

SCSI$INT:
;  Determine cause of 5380 "Interrupt".  Either phase
;  changed, busy dropped, or bus was reset.  If bits 2 and 3
;  of the NCRBSR are not 0's when the Interrupt flag (bit 4)
;  is set, then it is either a loss of BUSY or an SCSI RESET.
	XRA	A
	OUT 	NCRICR		; Release data bus
	IN	NCRBSR		; Read 5380 Bus and Stat Reg
	ANI	00001100B	; Keep interesting bits
	DB	JRNZ,SCSI$EXIT-$-1
				; Reset or Busy Loss: Exit
				; 00 --> Process phase vector

PHASE:
; DMA mode and Monitor Busy must be cleared prior to clearing
; of the 5380 Interrupt Flag.  Then mode register is restored.
; Otherwise the interrupt flag may not clear and the DMA Mode 
; may not be useable.  
	XRA	A		; Clear 5380 Mode Register
	OUT	NCRMR		; .
	IN	NCRRPI		; Reset interrupts
	MVI	A,00000110B	; Set DMA mode and Monitor Busy
	OUT	NCRMR		; .
	IN 	NCRCSBS		; Update phase...
      	ANI 	00011100B	; Mask all but phase bits, clear carry bit
      	RAR			; Rotate over for target
	MOV	E,A		; . (Save for use with jump table)
      	RAR			; .
      	OUT 	NCRTCR		; Set phase
      	MVI 	D,0		; E is already set (3 ins ago)
      	LXI 	H,PHASE$TABLE	; Get phase jump table base
      	DAD 	D		; Add offset for this phase
	MOV	A,M		; Get phase pointer into HL
	INX	H		; .
	MOV	H,M		; .
	MOV	L,A		; Pointer is now together
	MVI	D,01000000B	; DMA request mask(used by RSCSI and WSCSI)
      	PCHL			; Go to it!

PHASE$TABLE:
  	DW	PHASE0
  	DW	PHASE1
	DW	PHASE2
  	DW	PHASE3
	DW 	PHASE4
	DW	PHASE5
	DW	PHASE6
	DW	PHASE7

PHASE0:				; Data out phase ...
	LXI	H,SCSI$IO$COUNT	; Point to byte/block transfer mode
	MOV	E,M		; Get transfer mode value
  	LHLD 	DATPTR		; Use data pointer
  	JMP 	WSCSI		; Execute SCSI write routine

PHASE1:				; Data in phase ...
	LXI	H,SCSI$IO$COUNT	; Point to byte/block transfer mode
	MOV	E,M		; Get transfer mode value
  	LHLD 	DATPTR		; Use data pointer
  	JMP 	RSCSI		; Execute SCSI read routine

PHASE2:				; Command out phase ...
  	LHLD 	CMDPTR		; Use command pointer
  	MVI 	E,1		; Set mode to byte transfer
  	JMP 	WSCSI		; Execute SCSI write routine

PHASE3:				; Status in phase ...
  	LXI 	H,STATUS	; Use status pointer
  	MVI 	E,1		; Set mode to byte transfer
  	JMP 	RSCSI		; Execute SCSI read routine

PHASE7:				; Message in phase ...
  	LXI 	H,MESSAGE	; Use message pointer
  	MVI 	E,1		; Set mode to byte transfer
  	JMP 	RSCSI		; Execute SCSI read routine

; Currently unused phases

PHASE4:
PHASE5:
PHASE6:
SCSI$EXIT:
	XRA	A		; Clean up 5380 and exit.
	OUT 	NCRTCR		; .
	OUT	NCRMR		; .
	IN	NCRRPI		; Reset interrupts
	RET			; .

; Generalized SCSI write routine

WSCSI:
  	MVI 	A,1		; Assert data bus
  	OUT 	NCRICR
    	MVI 	C,NCRDACK	; Set up destination port address
  	OUT 	NCRSDS		; Start DMA send

; Wait for DMA request, keeping an eye on phase.  Note that the NCR
; will not issue an ACK, nor will it generate DMA requests once the
; phase changes, so it is best to treat DMA request checking as a
; higher priority than phase change checking.

WSCSI1:
  	IN 	NCRBSR
  	MOV 	B,A		; Save status for use below
  	ANA 	D          	; Check for DMA request
	DB	JRZ,WSCSI2-$-1

; This is the heart of the pseudo-DMA transfer.  On entry, HL points
; to the data buffer and E should be 0 for 256 byte block transfer,
; or 1 for byte-by-byte transfer.  NOTE: Use block transfer only if
; you can be sure the controller can buffer 256 bytes of data and
; can transfer at 5.25 us per byte.  Extra DACK's after the last REQ
; will do no harm.
;
; OTIR register use: H = memory pointer, C = I/O port, B = counter
; 
    	MOV 	B,E		; Set up loop count
	DW	OTIR80		; ED B3 = OTIR instruction op code
    	JMP 	WSCSI1		; Write more bytes until phase changes

; This code skipped when data is being transferred ...
WSCSI2:
   	MOV 	A,B		; Check 5380 "interrupt" flag
	ANI	00010000B	; .
	JZ	WSCSI1		; Wait for DMA request,
   	JMP 	SCSI$INT	;  or process "interrupt"

; Generalized SCSI read routine

RSCSI:
; Initiator command reg is already initialized

	MVI	C,NCRDACK	; Source port address
	OUT 	NCRSDIR		; Write to this port starts dma recieve

; Wait for DMA request, keeping an eye on phase.  Note: we must do
; a check for DMA request before checking for a phase change, since
; a byte may be queued up waiting to be DACKed prior to the phase
; change.
;
RSCSI1:
  	IN 	NCRBSR
  	MOV 	B,A		; Keep for phase change checking
  	ANA 	D          	; Mask for DMA request
	DB	JRZ,RSCSI2-$-1

; This is the heart of the pseudo-DMA transfer.  On entry, HL points
; to the data buffer and E should be 0 for 256 byte block transfer,
; or 1 for byte-by-byte transfer.  NOTE: Use block transfer only if
; you can be sure the controller can buffer 256 bytes of data and
; can transfer at 5.25 us per byte.  Extra DACK's after the last REQ
; will do no harm.
;
; INIR register use: H = memory pointer, C = I/O port, B = counter
; 
    	MOV 	B,E		; Set up loop count
	DW	INIR80		; ED B2 = INIR instruction op code
    	JMP 	RSCSI1		; Read more bytes until phase changes
				; Be sure and check phase if no DMA
				; request, since NCR won't issue any
				; unneeded DACKs

; This code skipped when data is being transferred ...
RSCSI2:
 	MOV 	A,B		; Check 5380 "interrupt" flag
	ANI	00010000B	; .
	JZ	RSCSI1		; Wait for DMA request,
	JMP 	SCSI$INT	;  or process "interrupt"

	ENDIF

HDCODE	EQU	$		; End of hard disk code

;	*	*	*	*	*	*	*	*
;
;	COLD BOOT ENTRY
;
;	NOTE: The following code does not stay resident. It is 
;	overlayed for use by all bios system variables.
;
;	*	*	*	*	*	*	*	*
BOOT:
	MVI	A,41H		; Turn off EPROM, but leave drive 0
	OUT	CONT		; . selected (1770 turns it off)
	LDA	IOBYT		; Setup initial IOBYTE value
	STA	IOBYTE		; .
	LXI	SP,80H		; Initialize DART, CTC, etc.
	CALL	IOINIT		; .

	IF	HARD$DISK	; Init HD parameters, if present
	IN	029h		; Get ID value
	ANI	07H		; Mask off extra bits
	PUSH	PSW		; . (Put ID in inital message)
	ADI	'0'		; . .
	STA	SCSI$ID$FOUND	; . .
	POP	PSW		; . .
	INR	A		; Bump to make 1 - 8
	MOV	B,A		; Convert to SCSI address
	XRA	A		; .  (Clear receiving register)
	STC			; .  (Carry = bit to rotate)
NEXT$BIT:			; .
	RAL			; .  (Rotate left through carry)
	DCR	B		; .  (Decrement counter)
	JNZ	NEXT$BIT	; .  (Not done -- repeat)
	STA	MY$ID		; Save ID value
	ENDIF

; SCSI reset is done by SCSI boot EPROM, and so should not
; be repeated here unless boot is from floppy.  In any
; case, only boards jumpered for SCSI ID # 7 will do reset.

	IF	HARD$DISK AND SCSI$RESET
	CPI	1000$0000B	; Perform SCSI reset if this is ID # 7
	DB	JRNZ,NOT7-$-1	; .
	MVI	A,80H		; Set reset line high
	OUT	NCRICR		; .
HOLD$RESET:
	DCR	A		; Hold reset high for at least 50us
	DB	JRNZ,HOLD$RESET-$-1 and 255
	OUT	NCRICR		; Clear reset line
	IN	NCRRPI		; .  and interrupt line
	ENDIF

	IF	HARD$DISK
NOT7:
  	XRA 	A		; Clear 5380 registers (SCSI init)
  	OUT 	NCRICR		; .
  	OUT 	NCRMR		; .
  	OUT 	NCRTCR		; .
  	OUT 	NCRSER		; .
	ENDIF			; End of HD initialization

	XRA	A		; Patch out old CP/M message
	STA	CPMMSG
	LXI	H,LOGMSG	; Display signon message
	CALL	PUTS	
	LXI	H,Z80MSG
	CALL	PUTS

	LXI	H,DRIVE$TDATA	; Init drive type data
	LXI	D,DRIVE$TYPES
	LXI	B,8
	DW	LDIR80

	IF	ZCPR3		; Init ZCPR3 stuff if present

	LXI	H,LAST$SG	; Set up HL and DE to clear memory
	LXI	D,LAST$SG+1	; from the end of boot to 0ffffh
	MVI	M,0
	MOV	A,H
	CMA
	MOV	B,A
	MOV	A,L
	CMA
	MOV	C,A
	DW	LDIR80

ZBOOT:	LXI	B,3		; Set up the ZCPR3 command line
	LXI	H,CMDSET	; .  pointers
	LXI	D,Z3CL		;
	DW	LDIR80		;

	LXI	B,10		; Move the automatic command to
	LXI	H,AUTOCMD	; .  the ZCPR3 command line
	LXI	D,Z3CL+3	;
	DW	LDIR80		;

	LXI	B,11		; Move the initial path descriptor
	LXI	H,PATH		; .  to the proper location
	LXI	D,EXPATH	;
	DW	LDIR80		;

	LXI	H,Z3WHL		; Turn the wheel byte on
	MVI	M,0FFH		;

	LXI	H,ENV		; Move environment and TCAP to the
	LXI	D,Z3ENV		; .  proper location
	LXI	B,ENVEND-ENV+1	; .
	DW	LDIR80		;
	ENDIF			; ZCPR3 init

	XRA	A		; 
	STA	CDISK		; Indicate disk 0 selected
	STA	HSTACT		; Set host buffer inactive
	STA	UNACNT		; Clear unalloc count
	STA	HSTSID		; Assume side zero
	JMP	GOCPM		; Initialize & jump to CP/M

LOGMSG:	DB	CR,LF,LF
	DB	MSIZE/10 +'0',MSIZE MOD 10 +'0'
CPMMSG: DB	'k CP/M vers 2.2'
Z80MSG: DB      'K Z80 Operating System'
        DB      CR,LF,'AMPRO BIOS Version ',VERS/10+'0','.'
	DB	VERS MOD 10+'0'

	IF	INTERNAL	; Display internal revision #
	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,'Copyright (C) 1983,84,85,86 '
	DB	'AMPRO Computers, Inc.'

	IF	HARD$DISK	; Display SCSI ID for this unit
	DB	CR,LF,LF,'SCSI initiator ID = '
SCSI$ID$FOUND:
	DB	'x'
	ENDIF
	IF	HARD$DISK AND ARBITRATION
	DB	CR,LF,'(Arbitration Enabled)'
	ENDIF
	IF	HARD$DISK AND (NOT ARBITRATION)
	DB	CR,LF,'(Arbitration Disabled)'
	ENDIF
	IF	CLOCK
	DB	CR,LF,'(Clock Enabled)'
	ENDIF
	IF	BUFFKBD
	DB	CR,LF,'(Console Buffer Enabled)'
	ENDIF
	IF	INTERNAL
	DB	CR,LF,'WARNING!!! Unreleased BIOS.'
	ENDIF
	DB	CR,LF,0

	IF	ZCPR3		; Include ZCPR3 definitions?
ENV:
	JMP	0		; Leading JMP
ENV1:				; ZCPR3 enviornment descriptor ...
	DB	'Z3ENV'		; . Environment ID
	DB	1		; . Class 1 environment (external)
	DW	EXPATH		; . External path (PATH)
	DB	EXPATHS		; 
	DW	RCP		; . Resident command package (RCP)
	DB	RCPS		; 
	DW	IOP		; . Input/output package (IOP)
	DB	IOPS		; 
	DW	FCP		; . Flow command package (FCP)
	DB	FCPS		; 
	DW	Z3NDIR		; . Named directories (NDR)
	DB	Z3NDIRS		; 
	DW	Z3CL		; . Command line (CL)
	DB	Z3CLS		; 
	DW	Z3ENV		; . Environment (ENV)
	DB	Z3ENVS		; 
	DW	SHSTK		; . Shell stack (SH)
	DB	SHSTKS		; 
	DB	SHSIZE		; 
	DW	Z3MSG		; . Message buffer (MSG)
	DW	EXTFCB		; . External FCB (FCB)
	DW	EXTSTK		; . External stack (STK)
	DB	0		; . Quiet flag (1=quiet, 0=not quiet)
	DW	Z3WHL		; . Wheel byte (WHL)
	DB	4		; . Processor speed (Mhz)
	DB	'P'-'@'		; . Max disk letter
	DB	31		; . Max user number
	DB	1		; . 1=ok to accept DU:, 0=not ok
	DB	0		; . CRT selection
	DB	0		; . Printer selection
	DB	80		; . CRT 0: Width
	DB	24		; 	 # of lines
	DB	22		; 	 # of text lines
	DB	132		; . CRT 1: Width
	DB	24		; 	 # of lines
	DB	22		; 	 # of text lines
	DB	80		; . PRT 0: Width
	DB	66		; 	 # of lines
	DB	58		; 	 # of text lines
	DB	1		; 	 FF flag (1=can form feed)
	DB	96		; . PRT 1: Width
	DB	66		; 	 # of lines
	DB	58		; 	 # of text lines
	DB	1		; 	 FF flag (1=can form feed)
	DB	132		; . PRT 2: Width
	DB	66		; 	 # of lines
	DB	58		; 	 # of text lines
	DB	1		; 	 FF flag (1=can form feed)
	DB	132		; . PRT 3: Width
	DB	88		; 	 # of lines
	DB	82		; 	 # of text lines
	DB	1		; 	 FF flag (1=can form feed)
	DB	'SH      '	; . Shell variable filename
	DB	'VAR'		; . Shell variable filetype
	DB	'        '	; . File 1
	DB	'   '		; 
	DB	'        '	; . File 2
	DB	'   '		; 
	DB	'        '	; . File 3
	DB	'   '		; 
	DB	'        '	; . File 4
	DB	'   '		;
	DB	0		; Public drive area (ZRDOS +)
	DB	0		; Public user area (ZRDOS +)
	;ENV 128 bytes long

ENV2:				; Terminal capabilities data
	DB	'ADM-3A  '	; . Name of terminal (ADM 3A)
	DB	'        '	; .
	DB	'K'-'@'		; . Cursor up
	DB	'J'-'@'		; . Cursor down
	DB	'L'-'@'		; . Cursor right
	DB	'H'-'@'		; . Cursor left
	DB	00		; . Clear screen delay
	DB	00		; . Cursor motion delay
	DB	00		; . Clear to EOL delay
	DB	1BH,'*',0	; . (CL) Clear screen string
	DB	1BH,'=%+ %+ ',0	; . (CM) Cursor motion string
	DB	1BH,'T',0	; . (CE) Clear to EOL string
	DB	1BH,')',0	; . (SO) Start hilite string
	DB	1BH,'(',0	; . (SE) End hilite string
	DB	0		; . (TI) Terminal init string
	DB	0		; . (TE) Terminal de-init string
	DB	0		;
ENVEND:

; End of environment and TCAP descriptors

CMDSET: DW	Z3CL+4		; Point to first chr in cmd line buf
	DB	Z3CLS		; Command line buffer size

PATH:				; Initial path description
	DB	'$',0		; .  Current drive, user 0
	DB	'$',15		; .  Current drive, user 15
	DB	1,'$'		; .  Drive A:, current user
	DB	1,0		; .  Drive A:, user 0
	DB	1,15		; .  Drive A:, user 15
  	DB	0		; (end of path)

	ENDIF			; ZCPR3 data

DRIVE$TDATA:			; Drive type data
	DB	01H,02H,04H,08H
	DB	02H,00H,00H,00H

LAST$SG	EQU	$

	ORG	BOOT

UNINIT		EQU	$
MUNACT:		DS	1	; UNALLOCATED COUNT VALUE
SEKDSK:		DS	1	;SEEK DISK NUMBER
SEKTRK:		DS	2	;SEEK TRACK NUMBER
SEKSEC:		DS	1	;SEEK SECTOR NUMBER

HSTDSK:		DS	1	;HOST DISK NUMBER
HSTTRK:		DS	2	;HOST TRACK NUMBER
HSTSEC:		DS	1	;HOST SECTOR NUMBER

CPMDSK:		DS	1	;SINGLE DENSITY	DSK PARM
CPMTRK:		DS	2	; AND TRK
CPMSEC:		DS	1	; AND SECTOR

SEKHST:		DS	1	;SEEK SHR SECSHF
HSTACT:		DS	1	;HOST ACTIVE FLAG
HSTWRT:		DS	1	;HOST WRITTEN FLAG

UNACNT:		DS	1	;UNALLOC REC CNT
UNADSK:		DS	1	;LAST UNALLOC DISK
UNATRK:		DS	2	;LAST UNALLOC TRACK
UNASEC:		DS	1	;LAST UNALLOC SECTOR

CPMSPT:		DS	1	; LOGICAL SECTORS PER TRACK
SECMSK:		DS	1	; SECTOR MASK
SECSHF:		DS	1	; SECTOR SHIFT

INT$RETRIES:	DS	1	; Internal retry counter for RD & WR
ERFLAG:		DS	1	;ERROR REPORTING
RSFLAG:		DS	1	;READ SECTOR FLAG
READOP:		DS	1	;1 IF READ OPERATION
WRTYPE:		DS	1	;WRITE OPERATION TYPE
DMAADR:		DS	2	;LAST DMA ADDRESS
HSTBUF:		DS	1024	;HOST BUFFER
STATUS:		DS	1
MESSAGE:	DS	1
CMDPTR		DS	2
DATPTR		DS	2
LOGDSK		DS	1
PHYDRV		DS	1
DPHDRV		DS	1
PHYTAG		DS	2
PHYCMD		DS	1
TAGDRV		DS	1
TAGPHY		DS	1
TAGTYP		DS	1
TAGCTL		DS	1
SCSI$IO$COUNT	DS	1
SCSI$STAT$DAT	DS	4
TARGET		DS	1
LOGUNIT		DS	1
CURDPB		DS	2

;	AMPRO BIOS-SPECIFIC STORAGE

IDSAVE:		DS	6		;READ ADDRESS BUFFER AREA
LTRACK:		DS	5		;LAST FLOPPY TRACK ACCESSED
LDISK:		DS	1		;LAST DISK SELECTED
TRIES:		DS	1		;NUMBER OF TIMES TO DO IT
RWHOST:		DS	1		;LOCAL READ/WRITE FLAG
HSTSID:		DS	1		;HOST DISK SIDE SELECT MASK
TIMEOUT:	DS	1		;TIMEOUT LOOP COUNTER
SECTOR:		DS	1		;TEMPORARY STORAGE

CHGDSK:		DS	1		; Flag to tell if we changed drives
FRWCMD:		DS	1		; FDC command
FRWPTR:		DS	2		; FDC data pointer
SAVE$CMDPTR:	DS	2		; SCSI command pointer save area
SAVE$DATPTR:	DS	2		; SCSI data pointer save area

;	SPECIAL E DISK PARAMETERS - FILLED IN WHEN E DISK IS
;	FIRST SELECTED, AND USED IN DEBLOCKING.

ESECADJ: DS	1		;SECTOR NUMBER ADJUST FOR SIDE 1

	IF	BUFFKBD
KEYBUF:	 DS	BUFFSIZE	; allocate buffer space for 80 character
KEYSTOP: EQU	$		; end of buffer address
	ENDIF

DIRBUF:	 DS	128		;DIRECTORY ACCESS BUFFER

;	*	*	*	*	*	*	*	*
;
;  Floppy drive directory check vector storage
;
;  The length of these vectors allows the use of up to 256
;  directory entries, all of which are checked by BDOS.
;
;  Note that these are used for removable floppy media only.
;
;  Do not change QPARM to increase the number of directory
;  entries without adjusting the variable FD$CKS.
;
;	*	*	*	*	*	*	*	*
FD$CKS	EQU	(255/4)+1	; maximum of 256 directory entries 

CSVA:	DS	FD$CKS
CSVB:	DS	FD$CKS
CSVC:	DS	FD$CKS
CSVD:	DS	FD$CKS
CSVE:	DS	FD$CKS

;	*	*	*	*	*	*	*	*
;
;  Floppy drive allocation vector storage
;
;  This area is used by BDOS to map all CP/M blocks for the
;  target disk drive, and is maintained to indicate which blocks
;  on the disk are in use.
;
;  The length of the Floppy area is set to 50 Bytes, which is
;  enough to handle a 96 TPI DS/DD disk as described in QPARM.
;
;  Do not change QPARM to increase storage without adjusting
;  the variable FD$ALV.
;
;	*	*	*	*	*	*	*	*
FD$ALV		EQU	(394/8)+1	; maximum of 395 disk blocks

ALVA:		DS	FD$ALV
ALVB:		DS	FD$ALV
ALVC:		DS	FD$ALV
ALVD:		DS	FD$ALV
ALVE:		DS	FD$ALV

FDATAEND	EQU	$
HD$VECTORS:	EQU	$	; HD check & allocation vectors 

;	*	*	*	*	*	*	*	*
;
;  Hard disk directory check vector storage
;
;	No Check storage is required - just an address
;
;	*	*	*	*	*	*	*	*
CSVF:		DS	0
CSVG:		DS	0
CSVH:		DS	0
CSVI:		DS	0
CSVJ:		DS	0
CSVK:		DS	0
CSVL:		DS	0
CSVM:		DS	0
CSVN:		DS	0
CSVO:		DS	0
CSVP:		DS	0

;	*	*	*	*	*	*	*	*
;
;  Hard disk allocation vector storage
;
;	*	*	*	*	*	*	*	*
HD$ALV		EQU	(1279/8)+1	; Maximum 1280 disk blocks
ALVF:		DS	0
ALVG:		DS	0
ALVH:		DS	0
ALVI:		DS	0
ALVJ:		DS	0
ALVK:		DS	0
ALVL:		DS	0
ALVM:		DS	0
ALVN:		DS	0
ALVO:		DS	0
ALVP:		DS	0

HD$CURRENT	EQU	$	; Current Bios Buffer Area ptr

ENDDATA		EQU	$
					; Mark the last avail. byte
		IF	ZCPR3		;
RESERVE:	EQU	0FD00H		; (0FD00H if ZCPR3)
		ENDIF			;
		IF	NOT ZCPR3	;
RESERVE:	EQU	00000H		; (00000H if no ZCPR3)
		ENDIF			;

FDCSIZE:	EQU	(MAINEND-BIOS) + (LAST$SG-HDCODE)
FDDSIZE:	EQU	FDATAEND-LAST$SG

HDCSIZE:	EQU	HDCODE-MAINEND
HDDSIZE:	EQU	ENDDATA-FDATAEND

;	*	*	*	*	*	*	*	*
;
;  Show the available free space and the number of sectors needed
;  to hold a SYSGEN image of CP/M with this BIOS:
;
;	*	*	*	*	*	*	*	*
FREEMEM:	EQU	RESERVE-ENDDATA		; Free memory left

SGSIZE:		EQU	(LAST$SG-CCP+127)/128	; Sysgen size
			; Number of sectors -- Must be 50H or less!

	END	BIOS
