
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; *								*
; *  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.8E I3.01	rvw	Added code to re-map lower tracks of Floppies under
;			external program control.  By mapping lower half of
;			a disk to the center (eg. track 2 to 40 and 40 to 2
;			for an 80 track disk) operations like BACKUPS become
;			less time-consuming due to degreased seek time (the
;			directory is never more than half a disk away)!
;  3.8D G7.04	rvw	SEARCHING FOR POWER-UP FUBAR!
;  3.8C FA.06	rvw	Due to a minor gotcha with BYE's desire to re-write
;			BIOS jump table, the IOPRET pointer cannot be located
;			caused via the COLD BOOT jump ADDRESS field while BYE
;			is running.  To solve this an ADDITIONAL
;			IOPRET table pointer can be "located" via the
;			old method of clock: BIOS GETTBL returns H/L pointer
;			to NXTTBL.  IOPRET table pointer is 16 bits just
;			prior to NXTTBL (this won't conflict with AMPRO
;			future NXTTBL expansions). IOPs can use either method,
;			but the NEW one MUST be used when running under BYE (or
;			any program modifying the COLD BOOT vector)
;  3.8B F9.17	rvw	Continued IOP support. Added extended IOPRET table
;			and included power-up IOP initialization.
;  3.8A F9.13	rvw	Added Roger's own favorite patches/changes. AND
;			IOP support jump tables
;  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	03	; Today's month
THIS$DAY	EQU	01	;	  day
THIS$YEAR	EQU	89	;	  year

INT$REV		EQU	05	; Internal revision number

; TRUE and FALSE

; 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
neg		equ	044edh		; neg  (0edh,044h)

; 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 to host disk constants

WRALL		EQU	0		; Write to allocated
WRDIR		EQU	1		; Write to directory
WRUAL		EQU	2		; Write to unallocated


;  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

;	*	*	*	*	*	*	*	*
;
;		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	ambios		; Bios starting location

	JMP	BOOT		; Cold start
WBOOTE: JMP	WBOOT		; Warm start
	 if	not (zcpr3)
	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
	 endif
	 if	zcpr3
	  if	(iops eq 0)
	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
	  endif
	  if	(iops ne 0)
	jmp	iop+12		; IOP console status
	jmp	iop+15		; IOP console input
	jmp	iop+18		; IOP console output
	jmp	iop+21		; IOP list output
	jmp	iop+24		; IOP punch output
	jmp	iop+27		; IOP reader input
	  endif
	 endif
	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
	 if	not (zcpr3)
	JMP	LISTST		; Return list status
	 endif
	 if	zcpr3
	  if	(iops eq 0)
	JMP	LISTST		; Return list status
	  endif
	  if	(iops ne 0)
	jmp	iop+30		; IOP list status
	  endif
	 endif
	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

;	*	*	*	*	*	*	*	*
;
;  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	$
	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

OOT 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)

	xra	a
	sta	0
	lxi	h,0
	lxi	d,1
	lxi	b,amccp-1
	dw	ldir80

	mvi	a,high(ctcvec)	; z80 interrupt init
	db	0edh,047h	; ld i,a
	db	0edh,05eh	; im 2
	mvi	a,low(ctcvec)
	out	ctca0		; ctc interrupt

	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		; GeEKDSK	;
	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
	nop			; space for EI if required	
	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
	ei			; space for EI if required	
	DB	JRNZ,RWFAIL-$-1	; Try again if failed
	RET			; Otherwise return

RDS:	MVI	A,FREADS	; Indicate read sector
	CALL	RDATA		; Read sector
	ei			; space for EI if required	
	DB	JRNZ,RWFAIL-$-1	; Try again if failed
	RET			; Otherwise return

RWFAIL:	CALL	RESTORE		; Restore to track 00 on failure
	ei			; space for EI if required	
	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:
	di			; space for DI if required	
	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:
	di			; space for DI if required	
	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
	di			; space for DI if required	
	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:
	di			; space for DI if required	
	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
	dw	lded80,phydrv	; Just quickly pick up index to...
	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
	push	b		; Save BC, too
	lda	phydrv		; Get physical drive
	cpi	4		; E drive?
	mov	e,a		; Copy to E Reg
	lda	cpmtrk		; Get track #
	db	jrz,itse-$-1	; Jump if E drive
	cpi	2		; See if system tracks
	db	jrc,itse-$-1	; Jump if system tracks
	mvi	d,0		; Prepare to index into table of offsets
	lxi	h,maptbl	; Base of table.
	dad	d		; Index
	cmp	m		; Compare table to a (if table is 0, will
	db	jrnc,itse-$-1	;  perform jump)
	dw	neg		; Negate
	inr	a		; Goose
	add	m		; Remap track
itse:
	mov	c,a		; Save in C reg
	CALL	GETTYPE		; Get the type byte for this disk
	LXI	H,HSTSID	; Point to the side flag
	RAL			; Determine SS or DS
	RAL			; .
	mov	a,c		; Restore from C reg
;;	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
	db	jr,gettrk2-$-1	; And finish up
GETTRK1:
	MVI	M,0		; Indicate side 0
GETTRK2:
	pop	b		; get BC register back
	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
	 if	not(r8bit)
	ANI	7FH		;STRIP PARITY BIT
	 endif
	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
	 if	not(r8bit)
	ANI	7FH		;STRIP PARITY BIT
	 endif
	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
	 if	not(r8bit)
	ANI	7FH		; strip parity
	 endif
	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		; ge