; LAST UPDATED ON:	10 OCTOBER 83 -- Ver 2.2i (Release is M)
; REASON FOR UPDATE:	Changed DISK3 for new firmware.  Change M-DRIVE/H
;                       to be consistent with other CompuPro OS's           llo
;
; 10 JUN 83, Ver 2.2i	Add DISK3 driver as per initial specs.		    aep
; 25 MAY 83, Ver 2.2h	Add format structure to sector xlate table.  Move I/O
;			initialization to the boot loader to achieve invariance
;			against CBIOS size variations.			    aep
; 03 MAY 83, Ver 2.2g	Continued debug and streamline of DISK2 driver with
;			addition for multiple drives.  BIOS split to permit
;			boot from floppy of hard disk drivers in addition to
;			full I/O compliment (loaded by file in "STARTUP.SUB"
;			sequence.)					    aep
; 24 MAR 83, Ver 2.2f	Update M-Drive/H with message, restructure hard disk
;			driver for faster algorythm including relocation of up
;			to one track's worth of "bad media" sectors.	    aep
; 02 JAN 83, Ver 2.2e	General debug of M-DRIVE/H.			    aep
; 16 NOV 82, Ver 2.2e	Add M-DRIVE/H driver.				    aep
; 10 OCT 82, Ver 2.2d	Add DISK2 controller software to match existing
;			but without the crazy sector relocation algorythm.  aep
; 15 JUL 82, Ver 2.2L	Modify to accomodate use of boot switch again,
;			eliminate bit banger.				    aep
; 16 MAY 82, Ver 2.2LXmMN Restructure code (massive overhaul) to permit
;			interrupt handler overlay.			    aep
; 25 MAR 82, Ver 2.2LXM	Add mini-drives, M-BOOT, fix SPECIFY,
;			full I/O byte implimentation.			    aep
;
; PROGRAM NAME:	HMX1BIOS.ASM	-- Customized BIOS for CP/M-80 version 2.2,
;		developed for use with CompuPro Systems Components.
;
;	==========================	Copyright 1983, CompuPro Corporation.
;	||			||
;	||     HMX1BIOS.ASM	||
;	||			||
;	==========================
;
;	This product is a copyright program product of CompuPro and is
;	supplied for use with the CompuPro Computer Systems.
;
; CONSTANTS:
VERS	EQU	22	;CP/M version number
CBIOSV	EQU	'M'	;CBIOS revision level (2.2x) (CompuPro level)
;
; LIBRARY CONSTANTS:
    MACLIB  COMPUPRO	;Disk and Serial/Parallel interface constants
    MACLIB  ASCII	;Mnemonics for common ASCII, other special characters
    MACLIB  ACTIVE	;Flags directing construction for the various
			;CompuPro products to "customize" the BIOS
    MACLIB  CPMDISK	;CP/M disk defaults, CBIOS offsets, BDOS functions
    MACLIB  BOOTSCPM	;CP/M cold/warm boot routines for each of the
			;possible controller types
;
; Footnote #1.
	PAGE
; PROGRAM:
;
; The next statement produces a harmless error message if MAC is used instead.
    ASEG		;Used Digital Research RMAC assembler and
	ORG	BIOS	;LINK linker to assemble this code
;
	JMP	CBOOT	;+00h	Cold boot
LOADVEC EQU	BIOS+1	;Base vector address of interrupt or other code loaded
			;by CP/M to extend the CBIOS beyond what the boot track
			;can hold -- value stored here at end of cold boot.
	JMP	WBOOT	;+03h	Warm boot
CIS:	JMP	CONST	;+06h	Console status (input)
CI:	JMP	CONIN	;+09h	Console input
CO:	JMP	CONOUT	;+0Ch	Console output
	JMP	LISTOUT	;+0Fh	List output
	JMP	PUNCH	;+12h	Punch output
	JMP	READER	;+15h	Reader input
	JMP	HOME	;+18h	Set track to zero
	JMP	SELDSK	;+1Bh	Select disk unit
	JMP	SETTRK	;+1Eh	Set track
	JMP	SETSEC	;+21h	Set sector
	JMP	SETDMA	;+24h	Set Disk Memory Address
	JMP	READ	;+27h	Read from disk
	JMP	WRITE	;+2Ah	Write onto disk
	JMP	LISTST	;+2Dh	List status (output)
	JMP	SECTRN	;+30h	Translate sector number
	JMP	SETNUM	;+33h	Set number of sectors to R/W (dangerous)
	JMP	SETXAD	;+36h	Set extended address (unblocked data only)
;
; Footnote #2.
;
;****************************************************************
;*	DISK LOGICAL TO PHYSICAL SECTOR TRANSLATION TABLES	*
;****************************************************************
    if FLOPPY8
;			GPL1,	GPL2,	Physical sectors/track
X8TABLE:DW  XLT8S0				  ;Single 128
	   DB		 07h,	 1Bh,	 26,	0 ;Format information "header"
	DW  XLT8D1				  ;Double 256
	   DB		 0Eh,	 36h,	 26,	0
	DW  XLT8D2				  ;Double 512
	   DB		 1Bh,	 54h,	 15,	0
	DW  XLT8D3				  ;Double 1024
	   DB	 	 35h,	 74h,	 08,	0
;---------------------------------------
XLT8S0:	DB	0,6,12,18,24,4,10,16,22,2,8,14,20
	DB	1,7,13,19,25,5,11,17,23,3,9,15,21
;---------------------------------------
XLT8D1:	DB	 0, 1,18,19,36,37, 2, 3,20,21,38,39
	DB	 4, 5,22,23,40,41, 6, 7,24,25,42,43
	DB	 8, 9,26,27,44,45,10,11,28,29,46,47
	DB	12,13,30,31,48,49,14,15,32,33,50,51
	DB	16,17,34,35
;---------------------------------------
XLT8D2:	DB	 0, 1, 2, 3,16,17,18,19
	DB	32,33,34,35,48,49,50,51
	DB	 4, 5, 6, 7,20,21,22,23
	DB	36,37,38,39,52,53,54,55
	DB	 8, 9,10,11,24,25,26,27
	DB	40,41,42,43,56,57,58,59
	DB	12,13,14,15,28,29,30,31
	DB	44,45,46,47
;---------------------------------------
XLT8D3:	DB	 0, 1, 2, 3, 4, 5, 6, 7
	DB	24,25,26,27,28,29,30,31
	DB	48,49,50,51,52,53,54,55
	DB	 8, 9,10,11,12,13,14,15
	DB	32,33,34,35,36,37,38,39
	DB	56,57,58,59,60,61,62,63
	DB	16,17,18,19,20,21,22,23
	DB	40,41,42,43,44,45,46,47
    endif
;=======================================
    if FLOPPY5
;			GPL1,	GPL2,	Physical sectors/track
X5TABLE:DW  XLT5S0			  ;Single 128
	   DB		 08h,	 10h,	 16,	0 ;Format information
;	   DB		 07h,	 09h,	 18,	0 ;Optional replacement (tight)
	DW  XLT5D1			  ;Double 256
	   DB		 10h,	 19h,	 16,	0
;	   DB		 07h,	 09h,	 18,	0 ;Optional replacement (tight)
	DW  XLT5D2			  ;Double 512
	   DB	 	 2Ah,	 50h,	 08,	0
	DW  XLT5D3			  ;Double 1024
;	   DB		 2Ah,	 50h,	 04,	0 ;Optional replacement (loose)
	   DB		 20h,	 32h,	 05,	0
;---------------------------------------
XLT5S0:	DB	 0, 5,10,15, 4, 9,14, 3		; 0, 5,10,15, 2, 7,12,17
	DB	 8,13, 2, 7,12, 1, 6,11		; 4, 9,14, 1, 6,11,16, 3
	DB	00,00				; 8,13
;---------------------------------------
XLT5D1:	DB	 0, 1,10,11,20,21,30,31		; 0, 1,10,11,20,21,30,31
	DB	 8, 9,18,19,28,29		; 4, 5,14,15,24,25,34,35
	DB	 6, 7,16,17,26,27		; 8, 9,18,19,28,29
	DB	 4, 5,14,15,24,25		; 2, 3,12,13,22,23,32,33
	DB	 2, 3,12,13,22,23,00,00,00,00	; 6, 7,16,17,26,27
;---------------------------------------
XLT5D2:	DB	 0, 1, 2, 3,16,17,18,19
	DB	12,13,14,15,28,29,30,31
	DB	 8, 9,10,11,24,25,26,27
	DB	 4, 5, 6, 7,20,21,22,23
	DB	00,00,00,00,00,00,00,00	;Additional space for other allocations
;---------------------------------------
XLT5D3:	DB	 0, 1, 2, 3, 4, 5, 6, 7
	DB	16,17,18,19,20,21,22,23
	DB	32,33,34,35,36,37,38,39
	DB	 8, 9,10,11,12,13,14,15
	DB	24,25,26,27,28,29,30,31
    endif
	PAGE
;****************************************************************
;*	Macro for generating the Disk Parameter Blocks.		*
;****************************************************************
;
; Control Blocks (DPB's in CP/M-eze) for disk drives:
;
;   MAKEDPB	Name, Type, Heads, Sectors/Track, Records(128 bytes)/sector,
;		Reserved cylinders, Ending cylinder number (starting at 1),
;		Allocation block size (in bytes from 1024 to 16*1024)
;		Directory entries, # of directory entries to be checked
;	(and optional),0 will force 16K extents for CP/M 1.4 compatibility
;
MAKEDPB MACRO	?NM,TY,HDS,SPT,SIR,?F,?L,BLS,NDIR,NCKS,K16
BSF	SET	3	;;Init block shift factor
EXTMSK	SET	0	;;Init extent mask
ACCU	SET BLS/1024
   REPT 6		;;6 bit shift max
      if ACCU = 1
	EXITM
      endif
BSF	SET BSF+1	;;Bump block shift factor
EXTMSK	SET 1+(EXTMSK*2);;Shift in a bit from right
ACCU	SET ACCU/2	;;Shift 1 bit right until in zero position
   ENDM
SIB	SET BLS/128	;;Sectors in block
DSMC	SET ((((?L*HDS)-(?F))*SPT*SIR)+SIB-1)/SIB ;;Maximum data blocks
      if DSMC > 256
EXTMSK	SET EXTMSK/2	;;Shift out one for double byte allocation
      endif
      if not NUL K16	;;optional [0] in last position
EXTMSK	SET	K16	;;forces 16K extent mask
      endif
DIRBLK	SET	8000h	;;Init allocation block for directory
ADIR	SET	BLS/32	;;32 bytes/dir entry
ACCU	SET	NDIR	;;Set accumulator to number of dir entries
   REPT 15		;;16 Bits of directory alloc max
ACCU	SET ACCU-(ADIR)	;;32 bytes per directory entry
      if ACCU <= 0	;;Done when all accounted for
	EXITM
      endif		;;Subtract # of entries in one block
DIRBLK	SET (DIRBLK/2)+8000h;;Shift in one bit from the left
   ENDM
      if (TY > 40h) and (TY < 80h)
DIRBLK	SET 0FFFFh	;;Reserve all dir blocks for hard disks
      endif
;;
;;---- Disk Parameter Block Generated ----
;;
;;	Disk type definition blocks for each particular mode.
;;	The produced format of these areas are as follows:
;;
;; 8 bit  = disk type code (not standard CP/M)
;; 16 bit = Sectors per track (real start of DPB)
;; 8 bit  = Block shift factor (block size in 128 byte segments)
;; 8 bit  = Block Shift mask
;; 8 bit  = Extent mask (a somewhat magical number best left alone)
;; 16 bit = Disk size in blocks (less 1 -- for expansion purposes)
;; 16 bit = Directory size. (less 1 in case size = 64K)
;; 16 bit = Allocation for directory. (vector to bitmap for this drive)
;; 16 bit = Check size. (# of 128 byte directory segments for checksums)
;; 16 bit = Offset to first track. (# of reserved cylinders to this drive)
	DB TY				;;Disk type identifier
DPB&?NM	DW SPT * SIR			;;Sectors/track
	DB BSF,	SIB-1,	EXTMSK		;;Block size factor, records/blk, mask
	DW DSMC-1,	NDIR-1		;;Disk size (blocks), Directory entries
	DB high DIRBLK,	low DIRBLK	;;Directory allocation reserved blocks
	DW (NCKS+3)/4,	?F 		;;Checksum vector size, reserved tracks
?NM&CSVZ EQU (NCKS+3)/4	;;Checksum vector size
?NM&ALVZ EQU (DSMC+7)/8	;;Max allocation vector size (bytes = blocks / 8 bits)
   ENDM
;
DPB8TBL:	;8 inch floppy disk DPB table
  MAKEDPB	F8S0,	00h,	1,26,1,	  2,77,	1024,	 64, 64
  MAKEDPB	F8S1,	01h,	2,26,1, 2*2,77,	1024,	128,128
  MAKEDPB	F8D2,	02h,	1,26,2,   2,77,	2048,	128,128, 0 ;16K extents
  MAKEDPB	F8D3,	03h,	2,26,2, 2*2,77,	2048,	256,256
  MAKEDPB	F8D4,	04h,	1,15,4,	  2,77,	2048,	128,128
  MAKEDPB	F8D5,	05h,	2,15,4, 2*2,77,	2048,	256,256
  MAKEDPB	F8D6,	06h,	1, 8,8,	  2,77,	2048,	128,128
  MAKEDPB	F8D7,	07h,	2, 8,8, 2*2,77,	2048,	256,256
FD8CSVZ	EQU  F8D7CSVZ	;Max checksum vector size (4 dir entries / record)
FD8ALVZ	EQU  F8D7ALVZ	;Max allocation vector size (bytes = blocks / 8 bits)
DPBFD8	EQU  DPBF8D7	;DPB with largest size alloc, checksum vectors
;
    if FLOPPY5
DPB5TBL:	;5 1/4 inch floppy disk DPB table
;	    TYPE base = 20h, then 77 tracks
;	    TYPE base = 28h, then 40 tracks
;	    TYPE base = 30h, then 80 tracks
  MAKEDPB  F5S0,  BIAS5+0,   1,16,1,	 2,TRK5,  1024,	 64, 64
  MAKEDPB  F5S1,  BIAS5+1,   2,16,1,   2*2,TRK5,  1024,	128,128
  MAKEDPB  F5D2,  BIAS5+2,   1,16,2,	 2,TRK5,  2048,	128,128, 0 ;16K extents
  MAKEDPB  F5D3,  BIAS5+3,   2,16,2,   2*2,TRK5,  2048,	256,256
  MAKEDPB  F5D4,  BIAS5+4,   1, 8,4,	 2,TRK5,  2048,	128,128
  MAKEDPB  F5D5,  BIAS5+5,   2, 8,4,   2*2,TRK5,  2048,	256,256
  MAKEDPB  F5D6,  BIAS5+6,   1, 5,8,	 2,TRK5,  2048,	128,128
  MAKEDPB  F5D7,  BIAS5+7,   2, 5,8,   2*2,TRK5,  2048,	256,256
FD5CSVZ	EQU  F5D7CSVZ	;Max checksum vector size (4 dir entries / record)
FD5ALVZ	EQU  F5D7ALVZ	;Max allocation vector size (bytes = blocks / 8 bits)
DPBFD5	EQU  DPBF5D7	;DPB with largest size alloc, checksum vectors
    endif
;
    if DISK2	;Disk 2 hard disk controller active.
      if D2M10		;Memorex (Fujitsu) 10 Mbyte drive
  MAKEDPB	D2N1,	43h,	4,11,8,	  4*1,123,  4096,  1024,0	;First 5M
  MAKEDPB	D2N2,	43h,	4,11,8, 4*123,243,  4096,  1024,0	;Second 5M+
      endif
      if D2M20		;Memorex (Fujitsu) 20 Mbyte drive
  MAKEDPB	D2N1,	43h,	8,11,8,	  8*1, 94,  4096,  1024,0	;First 8M
  MAKEDPB	D2N2,	43h,	8,11,8,	 8*94,187,  4096,  1024,0	;Second 8M
  MAKEDPB	D2N3,	43h,	8,11,8,	8*187,243,  4096,  1024,0	;Third 4M+
      endif
      if D2F20B		;Fujitsu "BE" 20 Mbyte drive
  MAKEDPB	D2N1,	43h,	4,22,8,	  8*1, 94,  4096,  1024,0	;First 8M
  MAKEDPB	D2N2,	43h,	4,22,8,	 8*94,187,  4096,  1024,0	;Second 8M
  MAKEDPB	D2N3,	43h,	4,22,8,	8*187,243,  4096,  1024,0	;Third 4M+
      endif
      if D2M26		;Shugart 26 Mbyte drive
  MAKEDPB	D2N1,	43h,	8,16,8,	  8*1, 57,  4096,  1024,0	;First 8M
  MAKEDPB	D2N2,	43h,	8,16,8,	 8*57,113,  4096,  1024,0	;Second 8M
  MAKEDPB	D2N3,	43h,	8,16,8,	8*113,169,  4096,  1024,0	;Third 8M
  MAKEDPB	D2N4,	43h,	8,16,8,	8*169,202,  4096,  1024,0	;Fourth 2M+
      endif
      if D2F40B		;Fujitsu "BE" 40 Mbyte drive
  MAKEDPB	D2N1,	43h,	8,22,8,   8*1, 47,  4096,  1024,0	;First 8M
  MAKEDPB	D2N2,	43h,	8,22,8,  8*47, 93,  4096,  1024,0	;Second 8M
  MAKEDPB	D2N3,	43h,	8,22,8,  8*93,139,  4096,  1024,0	;Third 8M
  MAKEDPB	D2N4,	43h,	8,22,8, 8*139,185,  4096,  1024,0	;Fourth 8M
  MAKEDPB	D2N5,	43h,	8,22,8, 8*185,241,  4096,  1024,0	;Fifth 8M
      endif
    endif
;
    if DISK3	;Disk 3 hard disk controller active.
      if D3ST506	;Seagate  5 Mbyte drive (ST506)
  MAKEDPB	D3N1,	53h,	4,9,8,	  1,153,  4096,  1024,0	;First 5M
      endif
      if D3M20		; 20 Mbyte drive
  MAKEDPB	D3N1,	53h,	8,9,8,	  1, 57,  4096,  1024,0	;First 8M
  MAKEDPB	D3N2,	53h,	8,9,8,	 57,113,  4096,  1024,0	;Second 8M
  MAKEDPB	D3N3,	53h,	8,9,8,	113,153,  4096,  1024,0	;Third 4M+
      endif
      if D3Q540		; 40 Mbyte Quantum drive
  MAKEDPB	D3N1,	53h,	8,9,8,	       2, 72,  4096,  1024,0	;First 5M
  MAKEDPB	D3N2,	53h,	8,9,8,  2+(8*75),180,  4096,  1024,0	;Second 7.4M
  MAKEDPB	D3N3,	53h,	8,9,8, 2+(8*180),285,  4096,  1024,0	;Third 7.4M
  MAKEDPB	D3N4,	53h,	8,9,8, 2+(8*285),390,  4096,  1024,0	;Fourth 7.4M
  MAKEDPB	D3N5,	53h,	8,9,8, 2+(8*390),492,  4096,  1024,0	;Fourth 7.3M
  MAKEDPB	D3N6,	53h,	8,9,8, 2+(8*492),509,  2048,   256,0	;Sixth 1.2M
      endif
    endif
;
;	Memory Drives:
;
    if XMDRIVE		;M-drive using 8088 extended addressing
  MAKEDPB  MEM,	MEMTYPE,  1,239,1,	0,32,	2048,	128,0,	0 ;16K ext
    endif
;
    if HMDRIVE		;M-DRIVE/H dynamic RAM disk drive
HM$FTRK	EQU	4	;32 Sectors/Board = reserved parity area = tracks 0-3
HM$DIRS EQU  ((HM$NUM+1)/2)*128	;Directory entries to allocate
      if HM$NUM gt 4
  MAKEDPB  HMD,	HMDTYPE,  1,8*HM$NUM,1,	 HM$FTRK,256,	2048,	HM$DIRS,0
      else
  MAKEDPB  HMD,	HMDTYPE,  1,8*HM$NUM,1,	 HM$FTRK,256,	2048,	HM$DIRS,0
      endif
    endif
;
	PAGE
;************************************************************************
;*	Macro for generating Control Headers for disk drives (DPH's):	*
;************************************************************************
;
; Set all letter drives to not in use status.
	IRPC	?X,ABCDEFGHIJKLMNOP
DRV&?X	SET	0
ALVZ&?X	SET	0
CSVZ&?X	SET	0
	ENDM
;
; The produced format of these Disk Parameter Headers are as follows:
;  8 bits = Mask for drive selection (drive dependent and not std CP/M)
; 16 bits = -> translation table. (real start of DPH)
; 48 bits = Work area for CP/M.
; 16 bits = -> DIRBUF.
; 16 bits = -> Parameter block.
; 16 bits = -> check vector.
; 16 bits = -> allocation vector.
;
; To generate a DPH, use the format:
;       MAKEDPH Drive letter, Physical drive selection mask,
;		Initial Sector translation table for that drive,
;		Initial DPB call letter name (see first entry of MAKEDPB).
;
MAKEDPH MACRO	?DR,?MASK,?XLT,?DPBN
;; ?DR	 = disk name [A,B,C,...,P]
;; ?MASK = disk selection mask for port control
;; ?XLT	 = sector translation table address
;; ?DPBN = code name for largest corresponding DPB
DRV&?DR	SET	$
	DB	?MASK		;;Address of this drive letter's DPH
DPH&?DR	DW	?XLT		;;Sector translation table
	DW  0000h, 0000h, 0000h	;;Scratchpad for CP/M
	DW  DIRBUF, DPB&?DPBN	;;Dir buffer, disk parameter block
	DW  CSV&?DR, ALV&?DR	;;Checksum vector, Allocation vector start
CSVZ&?DR SET	?DPBN&CSVZ	;;Set checksum vector reserve size
ALVZ&?DR SET	?DPBN&ALVZ	;;Set allocation vector reserve size
    ENDM
;
    if not (DISK2 or DISK3)				;=1
; Floppy disks are only permanent storage.
      if not (FLOPPY5 and BOOT5X)			;=2
  MAKEDPH  A, 00h, XLT8D3, FD8	;Drive A: 8 inch floppy
  MAKEDPH  B, 01h, XLT8D3, FD8	;Drive B: 8 inch floppy
        if FPY8X4	;If 4 drives are present 	;=3
  MAKEDPH  C, 02h, XLT8D3, FD8	;Drive C: 8 inch floppy
  MAKEDPH  D, 03h, XLT8D3, FD8	;Drive D: 8 inch floppy
        endif						;=3
;
        if FLOPPY5					;=3
  MAKEDPH  E, 00h, XLT5D3, FD5	;Drive E: 5 inch floppy
  MAKEDPH  F, 01h, XLT5D3, FD5	;Drive F: 5 inch floppy
	  if FPY5X4	;If 4 drives are present	;=4
  MAKEDPH  G, 02h, XLT5D3, FD5	;Drive G: 5 inch floppy
  MAKEDPH  H, 03h, XLT5D3, FD5	;Drive H: 5 inch floppy
	  endif						;=4
	endif						;=3
;
      else		;Booting from 5 1/4 inch disks	;=2
;
  MAKEDPH  A, 00h, XLT5D3, FD5	;Drive A: 5 inch floppy
  MAKEDPH  B, 01h, XLT5D3, FD5	;Drive B: 5 inch floppy
	if FPY5X4	;If 4 drives are present	;=3
  MAKEDPH  C, 02h, XLT5D3, FD5	;Drive C: 5 inch floppy
  MAKEDPH  D, 03h, XLT5D3, FD5	;Drive D: 5 inch floppy
	endif						;=3
;
	if FLOPPY8					;=3
  MAKEDPH  E, 00h, XLT8D3, FD8	;Drive E: 8 inch floppy
  MAKEDPH  F, 01h, XLT8D3, FD8	;Drive F: 8 inch floppy
          if FPY8X4	;If 4 drives are present 	;=4
  MAKEDPH  G, 02h, XLT8D3, FD8	;Drive G: 8 inch floppy
  MAKEDPH  H, 03h, XLT8D3, FD8	;Drive H: 8 inch floppy
          endif						;=4
	endif						;=3
      endif						;=2
;
    else						;=1
; Hard Disks exist, and should be used for drive A:
      if D2M10		;If Memorex 10 megabyte drive	;=2
D2UNIT0	EQU  'A' - 'A'	;Unit #0 base drive ID select
  MAKEDPH  A, 10h, 00, D2N1	;Drive A: 8 inch hard disk, select #0, 8M
  MAKEDPH  B, 10h, 00, D2N2	;Drive B: 8 inch hard disk, select #0, 2M+
	if DISK2X	;If second drive present	;=3
D2UNIT1	EQU  'C' - 'A'	;Unit #1 base drive ID select
  MAKEDPH  C, 21h, 00, D2N1	;Drive C: 8 inch hard disk, select #1, 8M
  MAKEDPH  D, 21h, 00, D2N2	;Drive D: 8 inch hard disk, select #1, 2M+
	endif						;=3
	if DISK2Y	;If third drive present		;=3
D2UNIT2	EQU  'E' - 'A'	;Unit #2 base drive ID select
  MAKEDPH  E, 42h, 00, D2N1	;Drive E: 8 inch hard disk, select #2, 8M
  MAKEDPH  F, 42h, 00, D2N2	;Drive F: 8 inch hard disk, select #2, 2M+
	endif						;=3
	if DISK2Z	;If fourth drive present	;=3
D2UNIT3	EQU  'G' - 'A'	;Unit #3 base drive ID select
  MAKEDPH  G, 83h, 00, D2N1	;Drive G: 8 inch hard disk, select #3, 8M
  MAKEDPH  H, 83h, 00, D2N2	;Drive H: 8 inch hard disk, select #3, 2M+
	endif						;=3
      endif						;=2
;
      if (D2M20 or D2F20B)	;If Memorex 20 megabyte drive	;=2
				; or Fujitsu "BE" 20 megabyte drive
D2UNIT0	EQU  'A' - 'A'	;Unit #0 base drive ID select
  MAKEDPH  A, 10h, 00, D2N1	;Drive A: 8 inch hard disk, select #0, 8M
  MAKEDPH  B, 10h, 00, D2N2	;Drive B: 8 inch hard disk, select #0, 8M
  MAKEDPH  C, 10h, 00, D2N3	;Drive C: 8 inch hard disk, select #0, 4M+
	if DISK2X	;If second drive present	;=3
D2UNIT1	EQU  'D' - 'A'	;Unit #1 base drive ID select
  MAKEDPH  D, 21h, 00, D2N1	;Drive D: 8 inch hard disk, select #1, 8M
  MAKEDPH  E, 21h, 00, D2N2	;Drive E: 8 inch hard disk, select #1, 8M
  MAKEDPH  F, 21h, 00, D2N3	;Drive F: 8 inch hard disk, select #1, 4M+
	endif						;=3
	if DISK2Y	;If third drive present		;=3
D2UNIT2	EQU  'G' - 'A'	;Unit #2 base drive ID select
  MAKEDPH  G, 42h, 00, D2N1	;Drive G: 8 inch hard disk, select #2, 8M
  MAKEDPH  H, 42h, 00, D2N2	;Drive H: 8 inch hard disk, select #2, 8M
  MAKEDPH  I, 42h, 00, D2N3	;Drive I: 8 inch hard disk, select #2, 4M+
	endif						;=3
	if DISK2Z	;If fourth drive present	;=3
D2UNIT3	EQU  'J' - 'A'	;Unit #3 base drive ID select
  MAKEDPH  J, 83h, 00, D2N1	;Drive J: 8 inch hard disk, select #3, 8M
  MAKEDPH  K, 83h, 00, D2N2	;Drive K: 8 inch hard disk, select #3, 8M
  MAKEDPH  L, 83h, 00, D2N3	;Drive L: 8 inch hard disk, select #3, 4M+
	endif						;=3
      endif						;=2
;
      if D2M26		;If Shugart 26 megabyte drive	;=2
D2UNIT0	EQU  'A' - 'A'	;Unit #0 base drive ID select
  MAKEDPH  A, 10h, 00, D2N1	;Drive A: 14 inch hard disk, select #0, 8M
  MAKEDPH  B, 10h, 00, D2N2	;Drive B: 14 inch hard disk, select #0, 8M
  MAKEDPH  C, 10h, 00, D2N3	;Drive C: 14 inch hard disk, select #0, 8M
  MAKEDPH  D, 10h, 00, D2N4	;Drive D: 14 inch hard disk, select #0, 2M+
	if DISK2X	;If second drive present	;=3
D2UNIT1	EQU  'E' - 'A'	;Unit #1 base drive ID select
  MAKEDPH  E, 21h, 00, D2N1	;Drive E: 14 inch hard disk, select #1, 8M
  MAKEDPH  F, 21h, 00, D2N2	;Drive F: 14 inch hard disk, select #1, 8M
  MAKEDPH  G, 21h, 00, D2N3	;Drive G: 14 inch hard disk, select #1, 8M
  MAKEDPH  H, 21h, 00, D2N4	;Drive H: 14 inch hard disk, select #1, 2M+
	endif						;=3
	if DISK2Y	;If third drive present		;=3
D2UNIT2	EQU  'I' - 'A'	;Unit #2 base drive ID select
  MAKEDPH  I, 42h, 00, D2N1	;Drive I: 14 inch hard disk, select #2, 8M
  MAKEDPH  J, 42h, 00, D2N2	;Drive J: 14 inch hard disk, select #2, 8M
  MAKEDPH  K, 42h, 00, D2N3	;Drive K: 14 inch hard disk, select #2, 8M
  MAKEDPH  L, 42h, 00, D2N4	;Drive L: 14 inch hard disk, select #2, 2M+
	endif						;=3
      endif						;=2
;
      if D2F40B		;If Fujitsu "BE" 40 megabyte drive	;=2
D2UNIT0	EQU  'A' - 'A'	;Unit #0 base drive ID select
  MAKEDPH  A, 10h, 00, D2N1	;Drive A: 8 inch hard disk, select #0, 8M
  MAKEDPH  B, 10h, 00, D2N2	;Drive B: 8 inch hard disk, select #0, 8M
  MAKEDPH  C, 10h, 00, D2N3	;Drive C: 8 inch hard disk, select #0, 8M
  MAKEDPH  D, 10h, 00, D2N4	;Drive D: 8 inch hard disk, select #0, 8M
  MAKEDPH  E, 10h, 00, D2N5	;Drive E: 8 inch hard disk, select #0, 8M
	if DISK2X	;If second drive present	;=3
D2UNIT1	EQU  'G' - 'A'	;Unit #1 base drive ID select
  MAKEDPH  G, 21h, 00, D2N1	;Drive G: 8 inch hard disk, select #1, 8M
  MAKEDPH  H, 21h, 00, D2N2	;Drive H: 8 inch hard disk, select #1, 8M
  MAKEDPH  I, 21h, 00, D2N3	;Drive I: 8 inch hard disk, select #1, 8M
  MAKEDPH  J, 21h, 00, D2N4	;Drive J: 8 inch hard disk, select #1, 8M
  MAKEDPH  K, 21h, 00, D2N5	;Drive K: 8 inch hard disk, select #1, 8M
	endif						;=3
      endif						;=2
;
      if DISK3
	if D3ST506
  MAKEDPH  A, 00h, 00, D3N1	;Drive G: 5 inch hard disk, select #0, 5M
	 if DISK3X
  MAKEDPH  B, 01h, 00, D3N1	;Drive G: 5 inch hard disk, select #0, 5M
	 endif
	endif
	if D3Q540
  MAKEDPH  A, 00h, 00, D3N1	;Drive G: 5 inch hard disk, select #0, 5M
  MAKEDPH  B, 00h, 00, D3N2	;Drive G: 5 inch hard disk, select #0, 7.5M
  MAKEDPH  C, 00h, 00, D3N3	;Drive G: 5 inch hard disk, select #0, 7.5M
  MAKEDPH  D, 00h, 00, D3N4	;Drive G: 5 inch hard disk, select #0, 7.5M
  MAKEDPH  E, 00h, 00, D3N5	;Drive G: 5 inch hard disk, select #0, 7.5M
  MAKEDPH  F, 00h, 00, D3N6	;Drive G: 5 inch hard disk, select #0, 1.2M
	 if DISK3X
  MAKEDPH  G, 01h, 00, D3N1	;Drive G: 5 inch hard disk, select #0, 5M
  MAKEDPH  H, 01h, 00, D3N2	;Drive G: 5 inch hard disk, select #0, 7.5M
  MAKEDPH  I, 01h, 00, D3N3	;Drive G: 5 inch hard disk, select #0, 7.5M
  MAKEDPH  J, 01h, 00, D3N4	;Drive G: 5 inch hard disk, select #0, 7.5M
  MAKEDPH  K, 01h, 00, D3N5	;Drive G: 5 inch hard disk, select #0, 7.5M
  MAKEDPH  L, 01h, 00, D3N6	;Drive G: 5 inch hard disk, select #0, 1.2M
	 endif
        endif
      endif
;
      if FLOPPY8					;=2
        if ((DRVI eq 0) and (DRVJ eq 0))		;=3
  MAKEDPH  I, 00h, XLT8D3, FD8	;Drive I: 8 inch floppy
  MAKEDPH  J, 01h, XLT8D3, FD8	;Drive J: 8 inch floppy
          if FPY8X4	;If 4 drives are present 	;=4
  MAKEDPH  K, 02h, XLT8D3, FD8	;Drive K: 8 inch floppy
  MAKEDPH  L, 03h, XLT8D3, FD8	;Drive L: 8 inch floppy
          endif						;=4
        else						;=3
  MAKEDPH  N, 00h, XLT8D3, FD8	;Drive N: 8 inch floppy
  MAKEDPH  O, 01h, XLT8D3, FD8	;Drive O: 8 inch floppy
        endif						;=3
      endif						;=2
;
      if FLOPPY5					;=2
        if ((DRVK eq 0) and (DRVL eq 0))		;=3
  MAKEDPH  K, 00h, XLT5D3, FD5	;Drive K: 5 1/4 inch floppy
  MAKEDPH  L, 01h, XLT5D3, FD5	;Drive L: 5 1/4 inch floppy
        else						;=3
	  if ((DRVN eq 0) and (DRVO eq 0))		;=4
  MAKEDPH  N, 00h, XLT5D3, FD5	;Drive N: 5 1/4 inch floppy
  MAKEDPH  O, 01h, XLT5D3, FD5	;Drive O: 5 1/4 inch floppy
          endif						;=4
        endif						;=3
      endif						;=2
    endif						;=1
;
; Memory drives.
    if HMDRIVE		;M-DRIVE/H present
      if XMDRIVE	;If extended memory drive, then can't use "M:"
	if (DRVH eq 0)	;If drive "H:" not already specified
  MAKEDPH  H, 00h, 00, HMD	;Drive H: M-DRIVE/H memory drive
  	else		;Use "N:" instead if so
  MAKEDPH  N, 00h, 00, HMD	;Drive N: M-DRIVE/H memory drive
	endif
      else		;Use "M:" if this is the only memory drive
  MAKEDPH  M, 00h, 00, HMD	;Drive M: M-DRIVE/H memory drive
      endif
    endif
;
    if XMDRIVE
  MAKEDPH  M, 00h, 00, MEM	;Drive M: M-DRIVE using 8088
    endif
	PAGE
;----------------------------------------
;
;	HOME ROUTINE:
;
; Return disk to home.  This routine sets the track number to zero.
; The current host disk buffer is flushed to the disk, and made inactive.
;
HOME:	CALL	FLUSH		;Flush host buffer if incomplete write
	CALL	HSTOFF		;Clear host active flag
	LHLD	SEKDSK		;Move desired drive, type back to active
	SHLD	ACTDSK
	LDA	SAVSEC		;Restore last desired sector to active status
	STA	ACTSEC
	LDA	SEKGPL		;Restore associated Gap Length for floppies
	STA	ACTGPL
	LHLD	DMAADR		;Move selected transfer address to
	SHLD	BUFADR		;the internal buffer address pointer
	XRA	A		;Clear the extended DMA byte (always bank 0)
	STA	DMAADE
	STA	BUFADE		;and extended buffer DMA address
	LXI	B,0		;Init to track 0 (theoretical position)
;
;----------------------------------------
;
;	SET TRACK ROUTINE:
;
; Set track number.  The track number is saved for later use during
; a disk transfer operation.
;
;Entry:	B,C = track number.
;
SETTRK:	MOV L,C! MOV H,B	;Put track number in "H,L"
	SHLD	ACTTRK		;Save as active track number
	SHLD	SEKTRK		;And as desired (blocking/deblocking)
	RET
;
;----------------------------------------
;
;	SECTOR TRANSLATION ROUTINE:
;
; Translate sector number from logical to physical.
;
;Entry:	D,E = 0, then no translation required,
; else:	D,E = translation table address and
;	B,C = sector number to translate.
;
;Exit:	H,L = translated sector number.
;
SECTRN:	MOV L,C! MOV H,B	;Offset (or record #) in H,L
	MOV A,E! ORA D		;See if translation required
	RZ			;Done if not
	DAD	D		;Add base to offset
	MOV L,M! MVI H,0	;Translation is always 1 byte for floppies
	RET
;
;----------------------------------------
;
;	SET SECTOR ROUTINE:
;
; Set the sector for later use in the disk transfer.  No
; actual disk operations are perfomed.
;
;Entry:	B,C = sector number (sometimes "B" contains an invalid number
;		if less than 256 sectors used for selected drive).
;
SETSEC:	MOV	A,C
	STA	ACTSEC		;Sector to seek
	STA	SAVSEC		;Special save area for blocking/deblocking
	RET
;
;----------------------------------------
;
;	SET NUMBER OF CONTINUOUS SECTORS TO USE:
;
; Set the number of sectors to be used during a single transfer.  This
; is a potentially dangerous value to alter in that the READ and WRITE
; routines will currently only use this value correctly if the disk
; is marked as "non-blocked", or having 128 byte sectors.  Unless you
; really have a need to alter this value, leave it at "1".
;
;Entry:	C = number of sectors to use in the next disk transfer.
;
SETNUM:	MOV	A,C
	STA	NUMSEC
	RET
;
;----------------------------------------
;
;	SET DIRECT MEMORY ACCESS (Lower 2 bytes):
;
; Set Direct Memory Address (DMA) for subsequent disk read or
; write routines.  This is the place the actual requested 128
; byte record (CP/M 1.4 sector) goes.
;
;Entry:	B,C = Disk memory address.
;
SETDMA:	MOV H,B! MOV L,C
	SHLD	DMAADR		;Save as Direct Memory Access start byte
	SHLD	BUFADR		;And as desired (blocking/deblocking)
	RET
;
;----------------------------------------
;
;	SET DIRECT MEMORY ACCESS (Upper byte of 24 bit address):
;
; Set extended bank address of DMA as above, currently inactive due
; to the potential havoc it wreaks if not used very, very carefully.
;
;Entry:	C = extended bank address byte.
;
SETXAD:	MOV	A,C		;Get extended address byte from "C"
	STA	DMAADE		;Save in memory
	RET
;
;----------------------------------------
;
; Create disk drive "existance" table to the actual drives presesnt.  This
; makes a list of the relative DBH's for individual logical drives which occupy
; absolute letter identifications.  Those logical drives with no associated
; physical counterpart have their pointers set to zero, indicating an invalid
; or non-existant drive.
;
DSKTBL:	IRPC	?D,ABCDEFGHIJKLMNOP
	DW	DRV$&?D
	ENDM
;
;	SELECT DISK DRIVE:
;
; Select the disk drive for subsequent disk transfers and return the
; appropriate DPB address.   This routine diverges from the normal CP/M
; implementation of just saving the disk selection value until the
; transfer is performed.  This divergence is required because floppy
; disks are a removable media and come in more than on format.  This
; routine determines the correct format and initializes the DPH with
; the appropriate values in agreement with the format type.
;
;Entry:	C = Disk selection value.
;	D,E and 1 = 0, ==> Must determine disk type,
;  else	          = 1, ==> Drive type has been determined.
;
;Exit:	H,L = 0, If drive not selectable,
;	H,L = DPH address if drive is selectable, and is initialized for the
;		appropriate floppy disk format if D,E = 0,
;	else the DPH pointed to contains data about the last disk accessed,
;  (the following are not required by CP/M, but are by the FORMAT utility)
;	D,E = DPB address if floppy disk was selected and initialized,
;	B,C = Base address of sector translate, format info if floppy selected.
;
SELDSK:	LXI	H,0		;Indicate not found or drive not active
	MOV A,C!  CPI 16!  RNC	;Done if invalid drive specified
	ADI	'A'		;Add in ASCII bias to drive ID
	STA	NRDYM2		;Set drive letter into "not ready" message
	MOV	B,H		;Clear upper byte of offset word
	LXI	H,DSKTBL	;Base of disk DPH lookup table
	DAD B!	DAD B		;Add offset times 2
	MOV A,M!  INX H		;Get low order byte of DPH pointer
	MOV H,M!  MOV L,A	;Get word DPH pointer in "H,L"
	ORA H!	JZ SELINV	;Check for valid drive, done if not
;
	MOV	A,M		;Get disk selection mask
	STA	ACTDSK		;Set into ACTDSK (make active disk)
	STA	SEKDSK		;And desired (seek to)
	INX	H		;Point to true DPH
	PUSH	H		;Save DPH address
	LXI	B,5*2		;Add offset within DPH to its DPB pointer
	DAD	B		;Result in "H,L" = DPH(DPB)
	MOV C,M! INX H		;Put start of DBP in "B,C"
	MOV B,M! DCX B		;Point to disk type for this DPH
	LDAX	B		;Get it
	STA	ACTTYP		;Save active disk type
	STA	SEKTYP		;And desired
	POP	H		;Restore DPH address
	CPI	D2$TYPE		;See if fixed media (starting hard disk type)
	RNC			;Done if hard disk or memory drive
;
	LXI	B,X8TABLE	;Show format parameter table base
    if FLOPPY5
	CPI	FD5TYPE		;See if 5 1/4 inch disk type
	JC	SEL8XLT		;Use that format table if so
	LXI	B,X5TABLE	;5 1/4 format parameter table base
SEL8XLT:CALL	FIX58		;Patch controller ports
    endif
	MOV A,E! ANI 1		;Mask "Force selected" bit, set status
	RNZ			;Done if drive previously selected
;
	PUSH B!	PUSH H		;Save XLATE, DPH address again
	CALL	TREAD		;Determine disk type
	POP D!	POP B		;Restore DPH, XLATE address
	LXI	H,0		;Indicate drive not active
	RNZ			;If disk type determined, fix DPH to match
;
FIX$DPH:PUSH B!	PUSH D		;Save base of track info, Drive's DPH address
	LXI	D,DPB8TBL+1	;Get base of DPB's to get correct one
    if FLOPPY5
	CPI	FD5TYPE		;See if 5 1/4 inch disk type
	JC	SEL8DPB		;Use that format table if so
	LXI	D,DPB5TBL+1	;Get, save base of DPB's to get correct one
    endif
SEL8DPB:ANI 07h!  MOV L,A	;Get disk type for DPB locate in "H,L"
	DAD H!	DAD H		;times 4
	DAD H!	DAD H		;times 4 = 16 bytes/DPB
	XCHG!	DAD D!	XCHG	;Add base of DPB's to get correct one in "D,E"
	ANI 06h! MOV L,A! ADD A	;Remove sided bit (offset 2X), times 2 = 4X
	ADD L!	MOV L,A		;Plus 2X = 6X, offset to table in "H,L"
	DAD	B		;Add "Sector translation" base to offset
	POP B!	PUSH B		;Recover, save again DPH base address
	MOV A,M!  STAX B	;Set translation table address into DPH
	INX H!	  INX B		; as word value
	MOV A,M!  STAX B	;At start of DPH
	INX H!	MOV A,M		;Point to associated Gap 3 Length, get it
	STA	SEKGPL		;Save for read/write operations
	STA	ACTGPL
	LXI	H,(5*2)-1	;Offset from present DPH address to DPB pointer
	DAD	B		;Move current pointer to DPH [DPB] pointer
	MOV	M,E!	INX H	;Set DPB address into DPH
	MOV	M,D
	POP H!	POP B		;Restore DPH start address, format table base
	RET
SELINV:
	STA	CDISK		;Put zero in A so drive A: is selected   
	RET                     ;  upon selection of nonexistent drive   
;
;	TREAD - Determine floppy disk type.
;
;Exit:	Zbit set = no error and
;	A = disk type (8n + (0-7)) if 8 inch.
;		      (8n + 20h + (0-7)) if 5 1/4 inch.
;			n = 0-3 depending on the number of cylinders/drive.
;
TREAD:	CALL	GTREADY		;Ask for drive status
	RNZ			;Abort if not ready
	LDA	TEMPBF		;Get status byte
	ANI	FD2SIDE		;Mask TS (two sided) bit
	RRC!	RRC!	RRC
	MOV	B,A		;Save sided flag in "B"
	LXI	H,SEKTYP	;Make into drive type with other info
	MOV A,M! ANI 38h	;Mask to select drive size bits
	ORA B!	MOV M,A
	LXI	H,RECAL		;Home to track 0
	MVI	B,LRECAL	;No status bytes for command
	CALL	MOVETO		;Process command, (Always reports success)
	MVI	A,2		;Seek to track two (only done if recal worked)
	CALL DOSEEK!	RNZ	;Abort if error (not reported if not tried)
	LXI	H,DRID
	MVI	M,FD$DRID	;Get disk ID (format info)
TRD2:	PUSH	H
	LXI	B,(DRIDL*256)+7 ;Command length + 7 bytes of status
	CALL	EXEC		;Process command
	POP	H
	JZ	TRD3		;Get proper drive type if successful
	MOV A,M!  XRI FD$MFM	;Compliment MFM/FM bit
	MOV M,A!  ANI FD$MFM
	JNZ	TRD2		;Loop for MFM if read not valid
	ORI	0FFh		;Abort if both FM and MFM tried and failed
	RET
;
TRD3:	LDA	TEMPBF+6	;Get number of bytes
	ADD A!	MOV B,A		;times 2 in "B"
	LDA  SEKTYP!	ORA B	;Combine N with sided flag
	STA  SEKTYP		;Save disk type
	CMP	A		;Set zero flag (show no error)
	RET
;
	PAGE
;----------------------------------------
;
;	READ SECTOR ROUTINE:
;
; Read a CP/M 128 byte sector (also known as a "record").
;
;Exit:	A = 0, Z bit set for successful read operation.
;	A = non-zero value if unsucessful read operation.
;
READ:	CALL	CHKBKD		;Check for blocked drive
	MVI	A,FD$RDAT	;Read from single density floppy
	JNC	FINAL		;If non-blocked transfer
	XRA	A		;Set flag to force a read
	CALL	FILL		;Fill buffer with data
	POP	H		;Host record address
	POP	D		;User DMA
	PUSH	PSW		;Save error status byte
	CALL	MOVDTA		;Move 128 bytes
	POP	PSW
	RZ			;Done if no error
;
;	HSTOFF - Turn off host active to prevent write to host.
; Used if error on fill to write buffer and if HOME command issued.
;
HSTOFF:	XRA	A		;Clear host active flag
	STA	HSTACT
	INR	A		;Indicate error condition
	RET
;
;	Check Blocked Disk transfer.
;
;Exit:	Carry bit set if blocked device.
;	Carry bit clear if unblocked device (logical and physical
;	sectors are both 128 bytes in length).
;
CHKBKD:	LDA	SEKTYP		;Get type of device in use
    if (XMDRIVE or HMDRIVE)
	CPI	MEMTYPE		;If M-drive
	RNC			;(not blocked)
    endif
    if (DISK2 or DISK3)
	CPI	D2$TYPE		;If Hard Disk
	CMC!	RC		;Always blocked if so
    endif
	ANI	06h		;See if 128 byte sector 8 or 5 inch
	RZ			;Not blocked device if so
    if FLOPPY5
	LDA	SEKTYP		;Get seek type again
	CPI FD5TYPE!  CMC!  RC	;Blocked if 5 1/4 inch, check track if 8"
    endif
	LDA	SEKTRK		;See if track 0, single density possible
	ADI	0FFh!	RC	;Blocked sectors if not track 0
	XRA	A		;Force type 0, reset blocked select bit
	STA	ACTTYP		;Use as actual type in use, non-blocked set
	RET
;
;----------------------------------------
;
;	WRITE SECTOR ROUTINE:
;
; Write the selected 128 byte CP/M sector.
;
;Entry:	C = 0, write to a previously allocated block.
;	C = 1, write to the directory.
;	C = 2, write to the first sector of unallocated data block.
;
;Exit:	A = 0, Z bit set for successful write operation.
;	A = non-zero value if unsucessful write operation.
;
WRITE:	CALL	CHKBKD		;Check for blocked drive
	MVI	A,FD$WRT	;Write to single density floppy
	JNC	FINAL		;If non-blocked transfer
	XRA	A		;Set for forced pre-read
	MOV	B,C		;Save original write type in "B"
	DCR	C		;Write type in "C"
	DCR	C		;See if write to first of unallocated
	JNZ	WRIT2		;Forced read if not
	CMA			;No physical read from disk required
WRIT2:	PUSH	B		;Save write type
	CALL	FILL		;If "A"=0, then a physical read is required
	POP	D		;Host record address
	POP	H		;DMA address
	PUSH	PSW		;Save error status
	CALL	MOVDTA		;Move 128 bytes into host buffer
	POP	PSW		;Recover error status and byte
	POP	B		;Recover write type
	JNZ	HSTOFF		;Abort if any errors occurred
	INR	A		;Make status byte a "1"
	STA	HSTWRT		;HSTWRT = 1 (active for next query)
	ANA	B		;"B" = 1 if write to directory
	RZ			;Done if not
;
;	FLUSH - Write out active host buffer onto disk.
;
FLUSH:	LXI	H,HSTWRT	;See if host write flag is active
	MOV A,M!  ORA A!  RZ	;Done if host buffer already on disk
	DCR	M		;Mark inactive for next query
	LHLD	HSTDSK		;Move disk and type
	SHLD	ACTDSK
	LHLD	HSTTRK
	SHLD	ACTTRK
	LHLD	HSTSEC
	SHLD	ACTSEC
	LXI	H,HSTBUF	;Set DMA xfer from host buffer address
	SHLD	BUFADR
	MVI   A,FD$WRT+FD$MFM	;Write double density
	JMP	FINAL		;From host buffer
;
;	FILL - fill host buffer with approprite host sector.
;
;Entry:	A = 0 means read physical sector required if not in buffer.
;	A = 0FFh means read not required.
;
;Exit:	On exit the stack will contain the following values:
;	POP	x = host record address.
;	POP	y = caller's buffer address.
;	A = 0 and Z-bit set if no errors occurred.
;
FILL:	STA	RDFLAG		;Save read flag
	LXI	H,HSTBUF	;Set DMA xfer to host buffer address
	SHLD	BUFADR
	LDA	SEKTYP		;Get disk type
    if (DISK2 or DISK3)
	CPI	D2$TYPE		;See if hard disk
	JNC	FILL1		;Use sector block size (log base 2) if so
    endif			;For both 8 inch and 5 1/4 inch floppies,
	RRC			;Put "N" field In the least significant bits
FILL1:	ANI	3		;Strip to get log base 2 of sector size
	MOV	B,A		;B = log base 2 (sector size) - 7
	XCHG			;Host buffer base in D,E
	LXI	H,128		;128 byte records
	LDA	SAVSEC		;Get logical sector (desired)
FILL2:	XCHG
	RRC			;Divide sector # by 2's to get physical
	JNC	FILL3		;If low bit not set
	DAD	D		;Add bias to offset
FILL3:	XCHG
	DAD	H		;Double significance of offset for next bit
	ANI	7Fh		;Mask to get physical sector
	DCR	B		;Bump log count for bit shifts
	JNZ	FILL2		;If not all bits checked
	STA	SEKSEC		;Physical sec # = desired / (sec size/128)
	LHLD	DMAADR		;Get DMA address from user
	XTHL			;Set return parameters
	PUSH	D		;Save host buffer pointer to 128 byte sector
	PUSH	H		;Set return address of user
	LXI	H,HSTACT	;Get current Host active flag
	MOV A,M! ORA A		;and it's status
	MVI	M,1		;which always becomes 1 (active)
	JZ	FILL6		;If host buffer was inactive, do a forced read
	LXI	H,HSTSEC-1	;Compare host values with desired
	LXI	D,SEKSEC-1
	MVI	C,SEKTYP-SEKSEC+1+1
FILL4:	INX H!	INX D		;Bump pointers for comparison
	XRA	A		;Clear error byte to zero value
	DCR C!	RZ		;Bump comparison count, done if all bytes match
	LDAX D!	CMP M		;See if current pair match
	JZ	FILL4		;Loop until all checked or mis-match
	CALL	FLUSH		;Flush host buffer to disk if mis-match
	RNZ			;Abort if error
FILL6:	LHLD	SEKDSK		;Move disk and type
	SHLD	HSTDSK		;For new host physical read from disk
	SHLD	ACTDSK
	LHLD	SEKTRK
	SHLD	HSTTRK
	SHLD	ACTTRK
	LHLD	SEKSEC
	SHLD	HSTSEC
	SHLD	ACTSEC
	LDA	RDFLAG		;See if read required
	INR A!	RZ		;Done if not
;
RDMFM:	MVI   A,FD$RDAT+FD$MFM	;Read double density
;
;	FINAL  --  Perform final transfer processing, actual data
;	moved from/to memory to/from storage device.
;
;Entry:	A = Command, either READ or WRITE.
;
;Exit:	A = 0, Z-bit set if no errors occured during transfer with
;	a possibility of "MRTRY" retries at the transfer.
;	A = non-zero, Z-bit reset if errors occurred on all "MRTRY"
;	efforts at a transfer.
;
; --- Footnote #3. ---
;
FINAL:	LXI	H,CIOPB		;Put the initial command in it's buffer
	MOV  M,A!	XCHG	;And save pointer in "D,E"
	LHLD	ACTTRK		;Get track (head, cylinder) number in "H,L"
	LDA  ACTSEC!  MOV C,A	;Get active sector number in "C"
	LDA	ACTTYP		;Get drive type in "A"
    if (XMDRIVE or HMDRIVE)
	CPI	MEMTYPE		;See if either M-DRIVE
	JNC	MEMFNL		;Special finish if so
    endif
	XCHG			;Track in "D,E" and pointer in "H,L"
    if (DISK2 or DISK3)
	CPI	D2$TYPE		;See if hard disk type for drive
	JNC	HARDFNL		;Use it's controls if so
    endif
	INX	H		;Point to next buffer specification (drive)
    if FLOPPY5
	MOV	B,A		;Save type
	CALL	FIX58		;Set ports for 8 or 5 inch floppies
	MOV	A,B
    endif
	ANI	07h		;Remove mini-floppy select
	RAR			;Get N (sector size), reset carry if 1 sided
	MOV	B,A		;Save in "B"
	JNC	FPYFNL		;Done if single sided
	XRA	A		;Clear carry
	MOV	A,E		;Convert to head and cylinder (CY becomes head)
	RAR			;(divide by 2)
	MOV	E,A
FPYFNL:	MVI	A,0
	RAL			;Set head number bit from CY
	MOV	D,A		;Move to head field
	RLC!	RLC		;And head bit in drive # field
	MOV	M,A		;Save temporary value of head
	LDA	ACTDSK		;Get drive #
	ORA	M		;Combine head with drive
	MOV	M,A		;Set drive (0 0 0 0 0 HDS DS1 DS0)
	INX H!	MOV M,E		;+2 = Set cylinder
	INX H!	MOV M,D		;+3 = Set head
	INX H!	MOV A,C! INR A	;+4 = Set sector (+1 for controller)
	MOV	M,A		;Set beginning sector
	INX H! MOV A,B! MOV M,A	;+5 = Set N field
	INX H!	LDA NUMSEC	;+6 = Ending sector, calculated by N*2
	ADD C!	MOV M,A		;Set EOT
	INX H!	LDA ACTGPL	;+7 = Set Gap length
	MOV	M,A		;Set GPL field
	INX H!	MVI M,0FFh	;+8 = Set DTL to double density
	DCR B!	JP FNL0		;Use it if not single type (0)
	MVI	M,80h		;Set DTL to 128 bytes
FNL0:	MVI	E,MRTRY		;Set retry count in "E"
FNL1:	CALL	GTREADY		;Ask for drive status
	RNZ			;Abort if still not ready
	DCR E!	MOV A,E!  RM	;Bump retry count, done if negative status
	LDA	CIOPB+2		;Get cylinder number
	CALL	DOSEEK		;Seek to proper track
	JNZ	FNL1		;Retry if seek error
	LXI	H,BUFADE	;Load blocking/deblocking buffer as DMA address
	MVI	B,3		;24 bits (3 bytes)
FNL2:	MOV	A,M		;Get DMA address to controller
DMAPT:	OUT	FD8PORT+FDMA
	DCX H!	DCR B		;Data is backward in memory
	JNZ	FNL2		;Loop until all 3 bytes sent
	LXI	H,CIOPB		;Point to Command Input/Output Parameter Block
	LXI	B,(CIOPL SHL 8)+7 ;Set command length, get 7 status bytes
	CALL	EXEC1		;perform operation
	SUI	40h		;Remove "abnormal termination of command" bit
; -- This bit is set because at the end of the last sector read or written,
; there was no "Terminal Count" flag issued from an external DMA controller.
; On the DISK 1 this signal is grounded, and although the FDC indicates this
; error, if the "End of Track Encountered" error bit is also set and no others
; are, this represents a successful read or write by the DISK 1.
	JNZ	FNL1		;Try again if any other error
	LDA	TEMPBF+1	;Get second result status byte
	SUI	FD$EOC		;Remove sector past end of cylinder bit
	RZ			;Done if no other errors
	ANI	FD$NWRT		;See if write protect error
	JZ	FNL1		;Loop if not write error
;
;	WRTERR - Get drive write protect status, ask to unprotect if necessary.
;
WRTERR:	CALL	GTDSTS		;Get drive ready status
	MOV	A,M		;Recover full status byte
	ANI	FD$WRTP		;Select write protect status bit
	LXI	H,WRTPMSG	;Write protected disk detected message
	CNZ	PRINT		;Send to console if needs to be changed
GTWRT1:	CALL	CIS		;See if character at console
	RNZ			;Abort if so
	CALL	GTDSTS		;Get drive status
	JNZ	GTWRT1		;Loop if drive now not ready
	MOV	A,M		;Recover full status byte
	ANI	FD$WRTP		;Select write protect status bit
	JNZ	GTWRT1		;If drive still protected, check again
	JMP	FNL1		;Loop if not protected
;
;	GTREADY - Wait until selected drive is ready or console driven abort
;		 (any char typed).
;
;Exit:	Z-bit set if drive is ready, or was made ready after request.
;	Z-bit reset if console abort -- drive not ready.
;
GTREADY:MVI	A,80h		;Turn on drives
FDCMOT:	OUT	FD8PORT+FDON	;Serial port used as "motor on" control bit
	CALL	GTDSTS		;Ask for drive status, ignore first reading
	CNZ	GTDSTS		;Ask again if first reading didn't show ready
	RZ			;Done if ready
	LXI	H,NRDYM1	;Show not ready
	CALL	PRINT
GTRDY1:	CALL	GTDSTS		;Get drive status
	RZ			;Done if now ready
	CALL	CIS		;See if character at console (abort)
	JZ	GTRDY1		;Loop until console abort or drive ready
	RET
;
;	DOSEEK -- Seek to specified Track:
;
;Entry:	A = Track number to seek to.
;
DOSEEK:	STA	DSEKC+2		;Save cylinder #
	LXI	H,DSEKC		;Seek command pointer
	MVI	B,DSEKL		;Seek command with no status bytes recieved
;
;	MOVETO -- Move head according to command:
;
;Entry:	H,L = address of command buffer.
;	B = length of command buffer.
;
;Exit:	Z-bit set if no error.
;
MOVETO:	CALL	EXECP		;Set up seek command in controller
SEEKI:	CALL	INTREQ		;Wait for interrupt service request
	MVI	A,FD$RSTS	;Send Request for interrupt status command
FDCD0:	OUT	FD8PORT+FDCD
	MVI	C,2		;2 status bytes recieved
	CALL	GCMPS		;Get status
	SUI  FDC$SKE!	RZ	;Verify seek end, no other errors -- done if so
	LDA	ACTDSK		;See if disk sampled matches
	XRA M!	ANI 3		;One in status byte
	JNZ	SEEKI		;Loop until so
	INR	A		;Show error
	RET
;
;	GTDSTS	- Get drive ready status of ACTDSK:
;
;Exit:	Z-bit set if ready.
;
GTDSTS:	LXI	H,DSTS		;Ask for drive status
	MVI	B,DSTSL		;Command length and 1 status byte
	CALL	EXECP		;Perform command
	INR	C		;Get 1 status byte
	CALL	GCMPS
	CMA			;Flip polarity of status byte
	ANI	FD$RDY		;Mask ready bit
	RET
;
;	EXEC, execute normal floppy disk controller command sequence:
;	EXECP, execute floppy disk controller command sequence with no
;	status check afterwards ("C" = 0):
;
;Entry:	H,L = Pointer to first byte of command buffer,
;	B  = number of bytes to output to controller in sequence,
;	C  = number of bytes to input to status buffer.
;
;Exit:	If C <> 0 then see GCMPS.
;	H,L = 1 past last byte of command buffer if no status bytes.
;	C = 0 on exit always.
;
EXECP:	MVI	C,0		;No status bytes for this execution
EXEC:	INX	H		;Point to drive # byte
	LDA	ACTDSK		;Set drive into command buffer
	MOV	M,A		;at second byte
	DCX	H		;Point to start of command sequence
EXEC1:	CALL	GETRQM		;Wait for request for master service
	MOV	A,M		;Get command byte
FDCD1:	OUT 	FD8PORT+FDCD	;Send to controller
	INX H!	DCR B		;Point to next
	JNZ	EXEC1		;Loop if more bytes
	MOV	A,C		;# of status bytes
	ORA A!	RZ		;Done if no status bytes
;
; Command Phase completed, now wait for Execution Phase completion indication.
	CALL	INTREQ		;Wait for interrupt service request
;
;	Get result status byte(s) into TEMPBF buffer.
;
;Entry:	C = number of Result Phase status bytes to read.
;
;Exit:	H,L = TEMPBF = pointer to status bytes read in.
;	A = first status byte anded with 0F8h with flags set accordingly.
;
GCMPS:	LXI	H,TEMPBF	;Set status buffer address
	PUSH	H		;Save status buffer start pointer
GCMPS2:	CALL	GETRQM		;Wait for request for master service
FDCD2:	IN	FD8PORT+FDCD	;Read result status byte
	MOV M,A!  INX H		;In status buffer, point to next location
	DCR	C		;Decrement status byte counter
	JNZ	GCMPS2		;Wait until all done
	POP	H		;Point to first status byte
	MOV	A,M		;Get first status byte
	ANI not (FD$HDS + FD$DRVS) ;Get completion status less drive, head bits
	RET
;
; Poll for RQM status bit, done when master requests service.
GETRQM:
FDCS1:	IN	FD8PORT+FDCS	;Get main status byte
	ORA	A		;See if request for master service bit set
	JP	GETRQM		;Loop if not ready
;	ANI	FD$DIO		;Mask data direction (DIO) bit
	RET
;
; Wait for Interrupt Service Request from FDC (8272).
INTREQ:
INTS1:	IN	FD8PORT+INTS	;Get interrupt status byte
	ORA	A		;Sample interrupt service request bit
	JP	INTREQ		;Wait for interrupt if not set
	RET
;
;	Self modifying code to fix ports for 8 or 5 inch disk controller.
;
    if FLOPPY5
FIX58:	CPI	FD5TYPE		;See if 8 inch drive selected
	MVI	A,FD8PORT
	JC	FIXPORT
FIX5:	MVI	A,FD5PORT	;Use 5 1/4 inch controller if not
FIXPORT:STA	FDCS1+1		;Fix control/status ports to correspond
	INR	A
	STA	FDCD0+1		;Data ports
	STA	FDCD1+1
	STA	FDCD2+1
	INR	A
	STA	DMAPT+1		;DMA port as output
	STA	INTS1+1		;Status as input
	INR	A
	STA	FDCMOT+1	;Motor on control port (bit banger unused)
	RET
    endif
;
	PAGE
;****************************************************************
;*	FINAL PROCESSING FOR HARD DISK DRIVES (DISK 2,3)	*
;****************************************************************
;
;	Routines for driving the DISK2 and DISK3 controllers, and drives
; corresponding SELECTOR CHANNEL boards if DISK2 in conjunction
; with one to four hard disk drives.
;
HARDFNL:
    if (DISK2 and DISK3)
	CPI	D3$TYPE		;See if DISK3 driver
	JC	D2$FNL		;Finish with DISK2 if not
    endif
    if DISK3
D3$FNL:	LXI	H,D3CIOPB+2	;Point to DISK3 command parameter block
	LDA  ACTDSK!	MOV M,A	;Setup active disk as 0-3 selection of drive
	LDA	CIOPB		;Get original command
	CMA!	ANI 1		;Correct for DISK3 read/write control bit
	INX H!	MOV M,A		;Save Read/Write command type
	INX H!	MOV M,C		;Save lower half of sector count
	INX H!	XRA A!	MOV M,A	;Upper half is zero
	INX H!	MOV M,E		;Save track lower half
	INX H!	MOV M,D		;and upper track half
	INX H!	MVI M,1		;Transfer 1 sector
	INX H!	MOV M,A		;Upper half is zero
	XCHG!	LHLD BUFADR	;Get local DMA address
	XCHG!	INX H!	MOV M,E	;Lower byte saved
		INX H!	MOV M,D	;Middle byte saved
	LDA	BUFADE		;Get upper byte
		INX H!  MOV M,A	;Upper byte saved to complete command load
; Linkage remains fixed to loop back to start of D3CIOPB
	MVI	A,D3$XFER	;Set up for data Input/Output transfer
D3EXEC:	LXI	H,D3CIOPB	;Point to parameter block in use
D3EXEC0:MOV	M,A		;Put command in first byte of parameter block
	INX H!	MVI M,0		;Clear the status byte
; Begin command execution, wait for result status.
	MVI	A,D3$ATTN	;Issue "attention" command to DISK3 to start
	OUT	D3$PORT		;Disk controller operation
D3EXEC1:MOV A,M! ORA A		;See if command execution has completed
	JZ	D3EXEC1		;End status check if byte now non-zero
D3EXEC2:INR A!	RZ		;See if result was 0FFh (valid completion)
	ADI	'0'-1		;Add ASCII bias for error type
	STA	D3MSG2
	PUSH B!	PUSH D!	PUSH H
	LXI	H,D3MSG		;Show error message and type
	CALL	PRINT
	CALL	CI		;Wait for operator to respond
	POP H!	POP D!	POP B
	ORI	0FFh		;Show error condition on return
	RET
;
    endif
;
	PAGE
;********************************************************
;*	FINAL PROCESSING FOR DISK 2 HARD DISK DRIVES 	*
;********************************************************
;
    if DISK2
D2$FNL:	MOV	A,E		;Get low order portion of track
	XCHG			;Put desired track number in "H,L"
	DAD H!	DAD H		;Shift left 4 bits to put cylinder in "H"
	DAD H!	DAD H
;     if (D2M??)		;16 heads
;	ANI	0000$1111b	;Select head value (0-15)
;     endif
      if (D2M20 or D2F40B or D2M26) ;8 heads
	ANI	0000$0111b	;Select head value (0-7)
	DAD	H	;Shift left (6 bits) again for proper cylinder in "H"
      endif
      if (D2M10 or D2F20B)	;4 heads
	ANI	0000$0011b	;Select head value (0-3)
	DAD H!	DAD H		;Shift left again for proper cylinder in "H"
      endif
	MOV	L,A		;Put head value in "L"
	XCHG			;Return both to "D,E" - command pointer "H,L"
	MOV A,M!  ANI 1		;Select Read/Write bit from floppy command
	MVI	M,D2$READ	;Replace with hard disk read
	JZ	D2FNLX		;if was a floppy read command
	MVI	M,D2$WRT	;Use write command instead if not
D2FNLX:	MVI	L,MRTRY		;Load max retry count
D2FNLP:	DCR L!	MOV A,L!  RM	;Bump retry count, done if neg (0FFh) status
	CALL	D2SELECT	;(Re-)select appropriate drive, verify ready
	RNZ			;Abort if not
	PUSH	H		;Save retry counter on stack
	CALL	D2XFER		;Seek to proper cylinder, perform data transfer
	POP	H		;Recover retry counter
	RZ			;Done if no errors
	ADD	A		;See if timeout error (header not found)
	JP	D2FNLP		;Loop in case of soft error (not timeout)
;
;	D2RELOCATE -- Search relocation table for matching sector ID, load
;	corresponding relocated sector values if found and try transfer
;	again using alternate sector.
;
D2RELOC:RRC			;Restore error byte
	ANI	D2$OVR		;See if overrun was cause of timeout
	JNZ	D2FNLP		;Treat as soft error if so
	LXI  H,(RELTBL0 - 2) - (3 * D2$SCNT)
;	    Point to base of relocation table(s), pre-comp for 2 indexes
	PUSH	D		;Save head, cylinder specification
	LXI	D,(3 * D2$SCNT)	;Point to next disk unit's faulty sector table
D2REL1:	DAD	D		;Add unit table size until correct one found
	DCR	B		;Bump unit number count
	JP	D2REL1		;Loop until pointer at this unit's table
	POP	D		;Recover head, cylinder specification
;
; Loop entry points -- if no match found, skip other tests for match of
; current head, sector and advance to next 3 byte entry in table.
D2REL5:	INX	H		;Skip head storage byte
D2REL4:	INX	H		;Skip sector storage byte
D2REL3:	MVI	A,D2$SCNT-1	;See if all available spare sectors checked
	INR B!	CMP B		;Bump count of table entries, see if past max
	RC			;Irrecoverable error if no entries match
	MOV A,M!  INX H!  CMP D	;See if CYLINDER matches table
	JNZ	D2REL5		;Skip head, sector check if not
	MOV A,M!  INX H!  CMP E	;See if HEAD number matches table
	JNZ	D2REL4		;Skip sector check if not
	MOV A,M!  INX H!  CMP C	;See if SECTOR matches table
	JNZ	D2REL3		;See if scan next triplet if not
;
; Place new values in the registers to correspond with the relocated sector,
; and continue with the Disk 2 read/write operation using the "spare" storage.
D2XRELS:MOV	C,B		;Corresponding sector number counted in "B"
	LXI	D,2		;Relocated sectors at cylinder 0, head #2
	JMP	D2FNLX		;Continue with execution using new values
;
;	D2SELECT -- Select appropriate DISK 2 hard disk drive.
;
;Entry:	ACTDSK set to current active drive to get ready status.
;
;Exit:	C,D,E unaltered,
;	B = Lower nibble of drive select (0-3) from ACTDSK value,
;	Zero bit set if drive is ready.
;
D2SELECT:CALL	D2READY		;See if drive is ready, ignore first if not
	CNZ	D2READY		;Reset drive fault bit, again check drive ready
	RZ			;Return valid if so
	PUSH B!	PUSH D!	PUSH H	;Save desired positions
	LXI	H,D2$MSG	;Show unit not ready
	CALL	PRINT
D2SEL1:	CALL	CIS		;See if character from console
	JNZ	D2SEL2		;Return with zero bit reset (error) if so
	CALL	D2READY		;See if drive is ready
	JNZ	D2SEL1		;Keep trying until so or console abort
D2SEL2:	POP H!	POP D!	POP B	;Recover desired positions
	RET
;
; Routine to get the drive ready status of the active hard disk.
D2READY:LDA	ACTDSK		;Get unit select (upper and lower nibbles set)
	ANI 3!	MOV B,A		;Select lower nibble in range 0-3, save in "B"
	ORI (D2$STRB + D2$RST)	;Add in drive strobe, fault clear bits
	OUT	D2$CNTL		;Set drive select register
	ADI '0'-(D2$STRB+D2$RST);Add bias and save as drive ASCII ID
	STA	D2$MSGX		;in error message
	LDA	ACTDSK		;Get unit select (upper and lower nibbles set)
	ANI	1111$0000b	;Use only upper nibble single select
	OUT	D2$DATA		;Set up DISK 2 with drive select
	IN	D2$STAT		;Get status byte
	ANI (D2$ATTN + D2$NRDY)	;Strip out attention, drive ready bits
	XRI	D2$ATTN		;Flip status of attention bit
	RET			;Return with drive ready status
;
;	D2XFER - DISK 2 Data Transfer (to/from Hard Disk) Operation.
;
; -- Two part transfer (with seek to home position if cylinder unknown).
; First the command sequence is given to seek correct cylinder, and for drives
; having automatic settling time delays, the concurrent loading of the
; controller to execute the actual data transfer.  For drives not having an
; automatic settling time delay, the No-op command is issued after a seek,
; which allows between 1 and 2 extra revolutions of the disk to allow the
; heads to settle.
;
;Entry:	CIOPB = command, (read or write)
;	D = Desired cylinder to seek,
;	E = Desired head of cylinder,
;	C = Desired sector of track,
;	B = Actual disk unit in use.
;
;Exit:	B,C,D,E unaltered,
;	H,L = Pointer to current cylinder table storage,
;	Zero bit set if no errors occurred.
;
D2XFER:	LXI	H,HD2CYL	;Base address of last known cylinder positions
	PUSH	B		;Get this drive's
	MOV C,B!  MVI B,0	;Offset for last cylinder position in "B,C"
	DAD B!	POP B		;Add offset for desired drive unit number (0-3)
	MOV	A,M		;Get last position for active unit
	CPI	-1		;Minus one indicates unknown -- seek home
	CZ	D2HOME		;Execute sequence if "forced" home
				;Current cylinder again (after home) in "A"
	SUB	D		;Get the difference of desired and current
	CNZ	D2SEEK		;If current not at desired cylinder, seek to it
;
; DISK 2 Transfer data to/from hard disk.
	PUSH	H		;Save cylinder pointer
	MVI	A,D2$STRB	;Add drive select command
	ORA	B		;to unit desired
	OUT	D2$CNTL		;Output to DISK 2 control register
	LDA	ACTDSK		;Get active drive number
	ANI	1111$0000b	;Mask single select bits (high nibble)
	ORA E!	  OUT D2$DATA	;Select drive, head
	MVI	A,D2$CYL	;Select cylinder number storage
	OUT	D2$CNTL
	MOV A,D!  OUT D2$DATA	;Get desired Cylinder number
	MVI	A,D2$HEAD	;Select head number storage
	OUT	D2$CNTL
	MOV A,E!  OUT D2$DATA	;Get the desired Head number
	MVI	A,D2$SEC	;Select sector storage
	OUT	D2$CNTL
	MOV A,C!  OUT D2$DATA	;Load desired sector
;
; Set up selector channel to correspond, with desired DMA value ready.
	IN	SELCHAN		;Initialize selector channel to accept data
	LXI	H,BUFADE	;Get extended DMA address byte
	MOV A,M! DCX H		;from storage, point to middle byte
	OUT	SELCHAN		;Upper byte Loaded first
	MOV A,M! DCX H		;Load with remaining DMA mid, low bytes
	OUT	SELCHAN
	MOV A,M			;For full 24 bit DMA transfer address
	OUT	SELCHAN
	LDA	CIOPB+0		;Get hard disk Read/Write command
	CPI	D2$READ		;See if read operation
	PUSH	PSW		;Save Read/Write operation type
	MVI	A,SELBYT+00h	;Upper bit not set if write operation
	JNZ	D2XFER4
	MVI	A,SELBYT+80h	;Set up selector channel to correspond
D2XFER4:OUT	SELCHAN		;Last byte of 4 in sequence
	POP PSW!  ORA B		;Get read/write command that begins transfer
	OUT	D2$CNTL		;Send to controller to begin execution
	XTHL!	XTHL		;Let the state machine start it's sequence
; Loop until command completed.
D2XFER5:IN	D2$STAT		;Get status of controller
	ORA	A		;See if command still in process
	JM	D2XFER5		;Loop until transfer complete
	ANI (D2$TOUT + D2$CRC + D2$OVR + D2$NRDY + D2SEKD + D2$WRTF)
; Select Timeout, CRC error, Overrun, Not Ready, Seek Done, Write Fault* bits
	XRI	D2$WRTF		;Flip status of write fault bit for final
	POP	H		;Recover table pointer for cylinder updates
	PUSH	PSW		;Save error status
	MVI	A,D2$STRB	;Reset interrupt status bit to inactive state
	OUT	D2$CNTL
	MVI	A,0001$0000b	;Reselect Unit #0 to execute command
	OUT	D2$DATA
	POP	PSW!	RZ	;Done if no errors from transfer operation
	MVI	M,0FFh		;Show drive at unknown position, force home
	RET			;Return with error type set
;
;	D2SEEK -- DISK 2 Execute seek to desired cylinder position.
;
;Entry:	H,L = Pointer to current cylinder table storage,
;	D = Desired cylinder to seek,
;	E = Desired head of cylinder,
;	C = Desired sector of track,
;	B = Actual disk unit in use.
;
;Exit:	B,C,D,E,H,L unaltered, return when desired cylinder has been reached.
;	Storage for unit positioner [H,L] updated, indicating new location.
;
D2SEEK:	PUSH	D		;Save head, cylinder numbers
	MVI	E,D2$SOU	;Seek outward control byte
	JNC	D2SEEKO		;If seek outward
	MVI	E,D2$SIN	;Seek inward control byte
	CMA!	INR A		;Two's compliment count to make positive
D2SEEKO:MOV	D,A		;Move number of tracks
	MOV A,B! ORA E		;Get selected drive, add in step direction
	OUT	D2$CNTL		;Send to DISK2
	XTHL!	XTHL		;Allow the direction bit to settle
D2STEP1:IN	D2$DATA		;Activate a step
      if (D2M10 or D2M20 or D2F20B or D2F40B) ;All steps accumulated by drive
	DCR	D		;Bump step counter
	JNZ	D2STEP1		;If not all steps sent
	POP	D		;Recover desired cylinder
	MOV	M,D		;Set current storage = desired
      else			;Steps are issued individually
D2SEEK2:IN	D2$STAT		;Get drive status
	ANI	D2$SEKD		;See if seek complete
	JNZ	D2SEEK2		;Wait until so
	DCR	D		;Bump step counter
	JNZ	D2STEP1		;Loop for another if not all steps sent
	  if D2M26		;Shugart drive requires computed settling time
	MVI	A,D2$TIME	;Issue a NO-OP command to get settling delay
	ORA	B		;Add in selected unit control bits
	OUT	D2$CNTL		;Send to controller, use next instructions for
				; command settling delay (no interrupt active)
	POP	D		;Recover desired cylinder
	MOV	M,D		;Set current storage = desired
D2SKDLY:IN	D2$STAT		;Get controller status byte
	ANI	D2$TOUT		;Mask timeout bit (1-2 index pulse delay)
	JZ	D2SKDLY		;Loop for settling time (approx.) till timeout
	  else			;No extra delay necessary
	POP	D		;Recover desired cylinder
	MOV	M,D		;Set current storage = desired
	  endif
      endif
	RET
;
;	D2HOME -- DISK 2 Execute seek to Home position (cylinder #0).
; This routine is called when the actual position of a drive unit (as measured
; by it's cylinder number) is in doubt.  It returns the positioner to the
; special "known" place at the outermost cylinder.
;
;Entry:	H,L = Pointer to current cylinder table storage,
;	B = Actual disk unit in use.
;
;Exit:	B,C,D,E,H,L unaltered, return when cylinder 0 has been reached,
;	A = 0, and storage for unit (H,L pointer) reset, indicating proper
;		location of positioner.
;
D2HOME:	INR	M		;Mark current with proper "home" value (zero)
	PUSH B!	MOV C,M		;Save sector, use 256 count in "C"
D2HOME0:IN	D2$STAT		;Get drive status
	ANI	D2$CYL0		;See if cylinder 0 has been reached
	JZ	D2HOME3		;Done with seek operation if so
	MOV	A,B		;Get active drive unit
	ORI (D2$SOU + D2$RST)	;Add in step out command, reset fault bit
	OUT	D2$CNTL		;Send to DISK2
D2HOME1:IN	D2$DATA		;Cause a step to be activated
	DCR	C		;Bump step counter
      if (D2M10 or D2M20 or D2F20B or D2F40B)
				; Fujitsu drives accept all pulses at once
	JNZ	D2HOME1		;Loop if not done with steps
      endif
D2HOME2:IN	D2$STAT		;Get drive status
	ANI	D2$SEKD		;See if seek complete
	JNZ	D2HOME2		;Wait until so
      if D2M26			;Shugart drives require separate pulses
	JMP	D2HOME0		;Loop if not done with steps
      endif
D2HOME3:POP	B		;Recover desired sector, active unit
	MOV	A,M		;Get current cylinder again (after home) in "A"
	RET
    endif
;
	PAGE
;****************************************************************
;*	FINAL PROCESSING FOR BOTH TYPES OF MEMORY DRIVES	*
;****************************************************************
;
; First is M-DRIVE/H which may optionally utilize "parity byte"
; software error checking stored at its lowest value "tracks".
;
   if (XMDRIVE or HMDRIVE)
MEMFNL:;**
	MOV	A,C		;Get pseudo sector (4K bank)
;**
	MOV B,H ! MOV C,L	;Save requested cylinder ***	
	DAD H!	DAD H!	DAD H	;Pseudo cylinder (128 byte block in 4K bank)
	DAD H!	DAD H!	DAD H	;Multiply by 128 to get offset in bank
	DAD	H		;32 Pseudo cylinders max (32 * 128 = 4K)
	XCHG			;MDRIVE "track" address in "D,E"
	LHLD	DMAADR		;Direct memory address in "H,L"
      if (XMDRIVE and HMDRIVE)	;If both MDRIVE's are active
	JZ	MEMXFNL		;Use 8088 mapped memory if exactly "MEMTYPE"
      endif
;
      if HMDRIVE		;If M-DRIVE/H is active
; ----- For M-DRIVE/H -----
;Entry:	D,E = Translated "Track" in range of (4-511)*128,
;	A = "Sector" in range of 0-"8n-1", where "n" is # of M-DRIVE/H boards.
;** 	B,C = Actual track
;
;** Rearranged so that "sector" info is actually the high order address byte -
; "D" becomes the low order track address byte, and since "D" may hold
; only a "1" or "0" in it's MSB, use it to identify the "high or low"
; sector being addressed by rotating that bit to the high order position.
;
; Lines commented out with a ";**" are a slightly more efficient way of keeping
; track of parity bytes. The line below a ";**" keeps parity the way other 
» CompuPrï BIOS'ó dï tï maintaiî compatibilitù across OS's.
HMD$FNL:;**
	PUSH	B	;Save "track" for parity
	PUSÈ PSW¡ OUÔ HM$CNTÌ	;Savå "sector¢ foò parity¬ higè "track¢ byte
	MOV A,D!  OUT HM$CNTL	;Low order byte of "track"
	MOV A,E!  OUT HM$CNTL	;Set "record" 1 or 0 ID in final byte setup
;**	LXI	B,5A80h		;Start parity ("B"), 128 Byte operation ("C")
	LXI	B,8080H
	LDA	CIOPB		;See if read operation requested
	CPI	FD$RDAT
	JNZ	HMD$WRT		;Do write if not
HMD$RD:	IN	HM$DATA		;Get data byte from "drive"
	MOV M,A! INX H		;In memory, point to next location
	ADD B!	MOV B,A		;Accumulate parity byte
	DCR	C		;Until all 128 bytes done
	JNZ	HMD$RD		;If done, point to stored disk parity byte
	POP PSW!  OUT HM$CNTL	;Set High order byte of track
;**
	POP	D
;**	MOV A,E	
	MOV A,D
;**	RLC			;Move high bit into low order position
		  OUT HM$CNTL	;Set Low order byte of track (single bit)
;**	MOV A,D! ;** OUT HM$CNTL	;Use old byte of "track" to find single byte
	MOV A,E!  OUT HM$CNTL	;Use old byte of "track" to find single byte
	IN  HM$DATA!	SUB B	;Get it and see if parity matches
	RET			;Return with error status
;
; Write operation to MDRIVE/H.
HMD$WRT:MOV A,M! INX H		;Get byte from memory, point to next
	OUT	HM$DATA		;Xmit to "drive"
	ADD B!	MOV B,A		;Accumulate parity byte
	DCR	C		;Until 128 bytes done
	JNZ	HMD$WRT		;If done, point to storage for parity byte
	POP PSW!  OUT HM$CNTL	;Set High order byte of track
;**
	POP 	D		;Recover selected track
	MOV A,D	
;**	MOV A,E! ;**	RLC	;Move high bit into low order position
		  OUT HM$CNTL	;Set Low order byte of track (single bit)
	MOV A,E!  OUT HM$CNTL
;**	MOV A,D! ;** OUT HM$CNTL	;Use old byte of "track" to find single byte
	MOV A,B!  OUT HM$DATA	;Save it to memory "drive" reserved area
	XRA A!	RET		;Show valid write operation
      endif
;
; ---- Memory Mapped Drive using 8088 as "DMA controller" ----
;Entry:	D,E = "Track" in range of 0-32,
;	C = "Sector" in range of 0-"n", where "n" is 4K memory block number.
;
      if XMDRIVE
MEMXFNL:  if XMBOOT
	ADI	18		;Add offset to get past host bank (64K=16*4K)
	  else			; and boot + (2*4K)
	ADI	16		;Add offset to get past host bank (64K=16*4K)
	  endif
	MOV	C,A		;And save
	MVI	B,128/2		;Word count for sector (64 * 16 bits)
	LDA	CIOPB		;See if read operation
	CPI	FD$RDAT
	MOV	A,C		;Get pseudo sector (4K bank)
	JZ	OUTOF		;Do a read from external memory operation if so
;----	JMP	INTO		;Do a write to external memory operation if not
      endif
    endif
;
;	MOVDTA	- Move data (128 bytes) in memory.
;
;Entry:	H,L = Source address,
;	D,E = Destination address,
;	A   = Source / Destination extended address byte (direction dependent).
;
    if I8088
INTO:	XCHG
		 STA ES88+1	;Set 8088 DS register to destination bank addr
	XRA A!	 STA DS88+1	;Set 8088 ES register to current source address
	JMP	TRN88
;
MOVDTA:	XCHG			;Make "D,E" = source, "H,L" = destination
	MVI	B,128/2		;128 bytes to move (64 words)
	XRA A
OUTOF:		 STA DS88+1	;Set 8088 DS register (Data Segment) to source
	XRA A!	 STA ES88+1	;Set 8088 ES register (Extra Segment) to dest
TRN88:		SHLD DI88	;8088 Destination Index
	XCHG!	SHLD SI88	;8088 Source Index
	MOV A,B! STA CX88	;Get # of words to move in CX reg storage
	XRA A!	 STA CX88+1	;Clear high byte of 8088 CX reg storage
		 STA DS88	;Clear low byte of 8088 DS reg storage
		 STA ES88	;Clear low byte of 8088 ES reg storage
	IN SWAPP!  XRA A	;Switch to 8088 CPU, show no errors for M-DRIVE
	RET
;
; The following is 8086/8088 code which is used for moving
; data via the dual processor's 8088.
;
LOOP88:
   DB	0E4h,SWAPP	;INB BANKP	;Wait for transfer
   DB	0FCh		;CLD		;Set direction
   DB	0B8h		;MOV AX,#BXADR	;Set AX to current bank
   DW	0				;Always 0 for dual processor
   DB	08Eh,0D8h	;MOV DS,AX	;Set DS to current bank
   DB	08Bh,00Eh	;MOV CX,[CX88]	;Set CX (word count)
   DW	  CX88				;Auto decrement on MOVS
   DB	0C4h,03Eh	;LES DI,[DI88]	;Set DI and ES (destination)
   DW	  DI88				;Auto increment on MOVS
   DB	0C5h,036h	;LDS SI,[SI88]	;Set SI and DS (source)
   DW	  SI88				;Auto increment on MOVS
   DB	0F2h		;REP		;Repeat next op until CX=0
   DB	0A5h		;MOVS word	;Move source word to dest
   DB	0EBh		;JMP LOP88	;Loop when block moved
   DB	-($-LOOP88+1)
;
    else
     if	z80
MOVDTA:	LXI	B,128		;128 bytes to move	
	DB  0EDH,0B0H	;LDIR	;Perform move 
	RET
     else
MOVDTA:	MVI	B,128		;128 bytes to move
MOVE1:	MOV A,M! STAX D		;Get source byte, save in destination
	INX H!	INX D		;Point to next in each
	DCR	B		;Byte count
	JNZ	MOVE1		;Loop until done
	RET
     endif	
    endif
	PAGE
;
;	PRINT -- Print message terminated by zero byte:
;Entry:	H,L = message pointer, terminated by NULL character.
;Exit:	H,L = zero byte + 1.
;
PRINT:	PUSH	H		;Save string pointer
	MVI	C,CR		;Xmit a carriage return
	CALL	CO		;Output to the console
	MVI	C,LF		;Followed by a line feed
	POP	H		;Recover string pointer
PRINT1:	PUSH	H		;Save string pointer
	CALL	CO		;Output to the console
	POP	H		;Recover string pointer
	MOV A,M! INX H		;Get a character, point to next
	ORA A!	RZ		;If zero (NULL), then terminate
	MOV	C,A
	JMP	PRINT1		;Loop until "NULL" encountered in string
;
;	Drive error messages altered to indicate the drive (unit) in question.
;
WRTPMSG:DB	'Write Error - Unprotect Disk, then Re-'
NRDYM1:	DB	'Load Drive '
NRDYM2:	DB	'x:',CR,LF,0
;
    if DISK2
D2$MSG:	DB	'DISK2 Unit #'
D2$MSGX: DB	'x Not Ready',0
    endif
;
    if DISK3
D3MSG:	DB	'DISK3 Error #'
D3MSG2:	DB	'x',0
    endif
;
	PAGE
;************************************************
;*	INITIALIZED VARIABLES / CONSTANTS	*
;************************************************
    if I8088
;
;	Initial values here are set up for start of XMBOOT (if activated).
;
; 01000H  = Point to place to save CP/M block (destination segment).
; CCP/16  = Point to start of CCP (source segment).
; 1600H/2 = Word length of CP/M as move count (to save to high mem).
;
; DESTINATION ADDRESS (Segment and Index register words):
DI88:	DW	0000h	;Destination Index
ES88:	DW	1000h	;Extended Segment
;
; SOURCE ADDRESS (Segment and Index register words):
SI88:	DW	0000h	;Source Index
DS88:	DW	CCP/16	;Data Segment
;
; LENGTH (words -- 2 bytes each):
CX88:	DW	1600h/2	;Amount to move (Words)
    endif
;
;	BIOS blocking / deblocking flags.
;
HSTACT:	DB	0	;Host active flag
HSTWRT:	DB	0	;Host written (must follow host active flag)
RDFLAG:	DB	0	;Read flag
;
;	Floppy Disk commands followed by data storage bytes.
;
DSEKC:	DB	FD$SEEK,0,0	;Seek track command
DSEKL:	EQU	$-DSEKC
;
DSTS:	DB	FD$DSTS,0	;Get drive status command
DSTSL:	EQU	$-DSTS
;
RECAL:	DB	FD$RECA,0	;Recalibrate (seek to home track) command
LRECAL:	EQU	$-RECAL
;
DRID:	DB	FD$DRID,0	;Read disk ID command to get sector size
DRIDL:	EQU	$-DRID
;
;	Disk 2 track location storage (not retained by controller).
;
    if DISK2
HD2CYL:	DB	-1,-1,-1,-1	;Show all 4 possible drives in unknown starting
    endif			;positions (force home operation).
	PAGE
;****************************************
;*	WARM AND COLD BOOT ROUTINES	*
;****************************************
;
;	BOOT CP/M FROM SPECIFIED DISK:
;
; The CBOOT entry point gets control from the cold start loader and
; is responsible for the basic system initialization.  This includes
; outputting a sign-on message and initializing the following page
; zero locations:
;
;    0,1,2: Set to the warmstart jump vector.
;        3: Set to the initial IOBYTE value.
;        4: Default and logged on drive.
;    5,6,7: Set to a jump to BDOS.
;
;Exit:	Register C must contain the selected drive, which is
;	zero to select the "A:" drive.  The exit address is to
;	the CCP routine.
;
;	The WBOOT entry point gets control when a warm start occurs,
; a ^C from the console, a jump to BDOS (function 0), or a jump
; to location zero.  The WBOOT routine reads the CCP and BDOS 
; from the appropriate disk sectors.  WBOOT must also re-initialize
; locations 0,1,2 and 5,6,7.  The WBOOT routine exits with the "C"
; register set to the appropriate drive selection value.  The exit
; address is to the CCP.
;
WBOOT:	LXI	SP,100h		;Temp stack for boot
	LXI	H,CCP+3		;The "+3" allows warm boot and go to
	PUSH	H		;CP/M with no buffer execution
;
;	CCP can be loaded with name of program to execute if vector
; to just "CCP".  This is used at end of cold boot to load any extra
; routines to the CBIOS (such as interrupt handlers), and may be a
; SUBMIT file which does many things.
;
    if XMBOOT
;	This routine requires that you have ram at the base of the next
; memory segment - 010000h.  It must be at least 8K in size to accomodate both
; the CCP and BDOS.  If you have memory above 64K, this is a good use for it -
; it considerably speeds up the warm boot process and makes more CBIOS ram
; available for bitmaps and such.  It also permits you to run after initial
; boot with "systemless" diskettes (even allows you to use a single side,
; single density diskette where the warm boot disk would normally reside).
;
;NOTE:	Having M-Drive active is not necessary, if both are used then
;	the M-drive size is reduced by only 8K.
;
	LXI	H,0
	SHLD	SI88		;Set both index pointers to 0
	SHLD	DI88
	MVI	H,10h		;Point to saved CP/M block
	SHLD	DS88		;and use as source (Start of 2nd memory bank)
	LXI	H,CCP/16	;Point to start of CCP
	SHLD	ES88		;and use as destination address
	LXI	H,1600h/2	;Word length of CP/M
	SHLD	CX88		;as counter
	IN	SWAPP		;Perform block move using 8088
    else
	CALL	LOADCPM		;Boot CP/M from floppy or hard disk
    endif
GOCPM:	LXI	B,80h		;Set default data transfer address
	CALL	SETDMA
	MVI	A,0C3h		;Store jumps in low memory
	STA	0		;CBIOS vector
	STA	5		;BDOS vector
	LXI	H,BIOS+3	;Warm boot vector address
	SHLD	1
	LXI	H,X$BDOS	;BDOS vector address
	SHLD	6
	LDA	CDISK		;Get active warm boot disk #
	MOV	C,A		;Put in "C" for CP/M
	RET
;
    if not XMBOOT	;If re-boot is always from a disk (of some type) --
	BOOTCPM		;Boot CP/M from disk, exit only if load sucessful.
    endif
;
	PAGE
;************************************************
;*	CONSOLE,READER,PUNCH,LIST ROUTINES	*
;************************************************
;
;	CONSOLE STATUS INPUT ROUTINE:
;
;Exit:	A = 0 (zero), means no character currently ready to read.
;	A = FFh (255), means character currently ready to read.
; IOBYTE selects device to use as follows:
; 0 = TTY:,	1 = CRT:,	2 = BAT:,	3 = UC1:
;    USER 6	    xxx		    xxx		   USER 7
; -----	If CRT, secondary select done using IOCNTL byte:
; 0 = USER 0,	1 = SysSup 1,	2 = IF1P0,	3 = Custom.
; -----	If BAT, secondary select done using READER of IOBYTE:
; 0 = USER 0	1 = USER 1,	2 = USER 2,	3 = USER 3
;
CONST:	LDA	IOBYTE		;Get I/O Byte (0=TTY,1=CRT,2=BAT,3=UC1)
	ANI	3		;Select console bits
	JNZ	CONST1		;If not TTY, check for others
;
; T T Y -- Used by all six logical device vectors.
TTYST:	MVI	A,6		;TTY is always Interfacer 3,4 USER 6
IF3STS:	OUT	IF3UX		;Select mux
	IN	IF3US		;Get TTY status
IFXST:	ANI	UDAV		;Mask data available
XCRTST:	RZ
	ORI	0FFh		;Show ready
	RET
;
CONST1:	DCR	A		;See if CRT selected
	JNZ	CONST2		;Check for UC1 or BATCH if not
;
; C R T -- Video Display Terminal.
CRTST:	LDA	IOCNTL		;Get I/O control select byte
	ANI	00$0000$11b	;Check on CRT select bits
	JZ	IF3STS		;If zero, CRT is Interfacer 3,4 USER 0
	DCR	A		;See if System Support I UART
	JNZ	CRTST2		;Try devices 2,3 if not
	IN	SS1US		;Get console status
	JMP	IFXST		;If I/O control was a "1", then use SS1 board
;
CRTST2:	DCR	A		;See if UART 0 of IF 1,2
	JNZ	CRTST3		;Use device 3 if not
	IN	IF1US0		;switch selects Interfacer I,II port at 00h
	JMP	IFXST		;All CompuPro input status masks are identical
;
; Custom CRT routine, initially set for Interfacer I,II UART 1.
CRTST3:	IN	IF1US1		;BUILD YOUR OWN CUSTOM CRT DRIVER HERE
	ANI	UDAV		;Select status bit(s)
	XRI	0		;Optional compliment of data
	JMP	XCRTST		;Universal return from status check
;
; More checks on regular console devices.
CONST2:	DCR	A		;See if UC1 selected
;
; U C 1 -- Optional user console device.
UC1ST:	MVI	A,7		;Get optional user 7 console status
	JNZ	IF3STS		;Complete status mask if not BATCH selected
;
; B A T -- BATCH Mode (use READER select bits to chose USER 0-3).
	LDA	IOBYTE
	RRC!	RRC		;Put READER select bits in lower 2 bits
	ANI	3		;To select low user #
	JMP	IF3STS		;And get IF3 status
;
;****************************************
;*	CONSOLE DATA INPUT ROUTINE:	*
;****************************************
;
; Read the next character into the A register, clearing
; the high order bit.  If no character currently ready to
; read then wait for a character to arrive before returning.
;
; IOBYTE selects device to use as follows:
; 0 = TTY:,	1 = CRT:,	2 = BAT:,	3 = UC1:
;    USER 6	    xxx		    xxx		   USER 7
;-----	If CRT, secondary select done using IOCNTL byte:
; 0 = USER 0,	1 = SysSup 1,	2 = IF1P0,	3 = Custom.
;-----	If BAT, secondary select done using READER of IOBYTE:
; 0 = USER 0	1 = USER 1,	2 = USER 2,	3 = USER 3
;
;Exit:	A = character read from terminal.
;
CONIN:	LDA	IOBYTE		;Get I/O Byte (0=TTY,1=CRT,2=BAT,3=UC1)
	ANI	3		;Select console bits
	JNZ	CONIN1		;If not TTY, check for others
;
; T T Y -- Used by all six logical device vecotrs.
TTYIN:	MVI	A,6		;TTY is always Interfacer 3,4 USER 6
IF3IN:	OUT	IF3UX		;Select mux
IF3IN2:	IN	IF3US		;Get TTY status
	ANI	UDAV		;Mask data available
	JZ	IF3IN2		;Loop until ready
	IN	IF3UD		;Get data
	ANI	7Fh		;Strip out parity
	RET
;
CONIN1:	DCR	A		;See if CRT
	JNZ	CONIN2		;Try UC1 or BATCH if not
;
; C R T -- Video Display Terminal.
CRTIN:	LDA	IOCNTL		;Get I/O control select byte
	ANI	00$0000$11b	;Check on CRT select bits
	JZ	IF3IN		;If zero, CRT is Interfacer 3,4 USER 0
	DCR	A		;See if System Support I UART
	JNZ	CRTIN2		;Try devices 2,3 if not
CRTIN1:	IN	SS1US		;Get console status
	ANI	UDAV
	JZ	CRTIN1		;Loop if data not available
	IN	SS1UD		;Get data
	ANI	7Fh		;Strip out parity
	RET
;
CRTIN2:	DCR	A		;See if Interfacer I,II UART 0
	JNZ	CRTIN3		;Use alternate CRT device 3 if not
CRTIN2X:IN	IF1US0		;switch selects Interfacer I,II
	ANI	UDAV
	JZ	CRTIN2X		;Loop if data not available
	IN	IF1UD0		;Get data
	ANI	7Fh		;Strip out parity
	RET
;
; Custom CRT routine, initially set for Interfacer I,II UART 1.
CRTIN3:	IN	IF1US1		;BUILD YOUR OWN CUSTOM CRT INPUT ROUTINE
	ANI	UDAV		;Strip out console status bits
	XRI	0		;Flip status bits as necessary
	JZ	CRTIN3		;Loop if data not available
	IN	IF1UD1		;Get data
	ANI	7Fh		;Strip out parity
	RET
;
; Check for other console devices.
CONIN2:	DCR	A		;See if UC1 selected
;
; U C 1 -- Optional user console device.
UC1CI:	MVI	A,7		;Get optional user 7 console status
	JNZ	IF3IN		;Complete data input if not BATCH selected
;
; B A T -- BATCH Mode (use READER select bits to chose USER 0-3).
	LDA	IOBYTE
	RRC!	RRC		;Put READER select bits in lower 2 bits
	ANI	3		;To select low user #
	JMP	IF3IN		;And get IF3 data input
;
;********************************************************
;*	READER LOGICAL DEVICE DATA INPUT ROUTINE:	*
;********************************************************
;
; Read the next character from the currently assigned
; reader device into the A register, no parity bit is stripped.
;
; IOBYTE selects device to use as follows:
; 0 = TTY:,	1 = PTP:,	2 = UP1:,	3 = UP2:
;    USER 6	   USER 1	   USER 2	   USER 3
;
;Exit:	A = character read from the reader device.
;
READER:	LDA	IOBYTE		;Get I/O BYTE
	RRC!	RRC		;Move select to lower two bits
	ANI	3		;Mask select bits (0=TTY,1=PTR,2=UR1,3=UR2)
	JNZ	IF3RI		;Else get USER 1-3 as reader inputs
	MVI	A,6		;TTY is USER 6
IF3RI:	OUT	IF3UX		;Select mux
IF3RI2:	IN	IF3US		;Get TTY status
	ANI	UDAV		;Mask data available
	JZ	IF3RI2		;Loop until ready
	IN	IF3UD		;Get data
	RET
;
;********************************************************
;*	SEND CHARACTER TO PUNCH OUTPUT LOGICAL DEVICE:	*
;********************************************************
;
; Send a character (8 bits) to the selected punch device.
;
; IOBYTE selects device to use as follows:
; 0 = TTY:,	1 = PTP:,	2 = UP1:,	3 = UP2:
;    USER 6	   USER 1	   USER 2	   USER 3
;
;Entry:	C = ASCII character or byte to output.
;
PUNCH:	LDA	IOBYTE		;Get I/O BYTE
	ANI	00$11$00$00b	;Mask out punch device select bits
	JZ	TTYOUT		;Use TTY if zero
PUNCH2:	RRC! RRC! RRC! RRC	;Move select to lower two bits
	ANI	3		;Mask select bits
	JMP	IF3OUT		;Select USER for Interfacer 3,4
;
;****************************************
;*	CONSOLE DATA OUTPUT ROUTINE:	*
;****************************************
;
; Send a character to the console.  If the console is not ready
; to output a character, wait until it is, then do transmission.
;
; IOBYTE selects device to use as follows:
; 0 = TTY:,	1 = CRT:,	2 = BAT:,	3 = UC1:
;    USER 6	    xxx		    xxx		   USER 7
;-----	If CRT, secondary select done using IOCNTL byte:
; 0 = USER 0,	1 = SysSup 1,	2 = IF1-P0,	3 = Custom.
;-----	If BAT, secondary select done using PUNCH of IOBYTE:
; 0 = USER 0	1 = USER 1,	2 = USER 2,	3 = USER 3
;
;Entry:	C = ASCII character to output to console.
;
CONOUT:	LDA	IOBYTE		;Get I/O Byte (0=TTY,1=CRT,2=BAT,3=UC1)
	ANI	3		;Mask select bits
	JNZ	CONOUT1		;If not TTY, check for others
;
; T T Y -- Used by all six logical device vectors.
TTYOUT:	MVI	A,6		;TTY is always Interfacer 3,4 USER 6
IF3OUT:	OUT	IF3UX		;Select mux
IF3OUT2:IN	IF3US		;Get TTY status
	ANI  	IF3TMSK		;Mask TBE, DSR bits
	XRI  	IF3FMSK		;Flip status of both
	JNZ	IF3OUT2		;Loop until ready
	MOV	A,C
	OUT	IF3UD		;Xmit data
	RET
;
CONOUT1:DCR	A		;See if CRT
	JNZ	CONOUT2		;Try UC1 or BATCH if not
;
; C R T -- Video Display Terminal.
CRTOUT:	LDA	IOCNTL		;Get I/O control select byte
	ANI	00$0000$11b	;Check on CRT select bits
	JZ	IF3OUT		;If zero, CRT is Interfacer 3,4 USER 0
	DCR	A		;See if System Support I UART
	JNZ	CRTOUT2		;Try devices 2,3 if not
CRTOUT1:IN	SS1US		;Get console status
	XRI	SS1FMSK		;Flip TBE status bit
	ANI	SS1TMSK		;Mask data xmit flags
	JNZ	CRTOUT1		;Loop if xmit buffer not empty
	MOV	A,C		;Get char to xmit
	OUT	SS1UD		;Send it on it's way
	RET
;
CRTOUT2:DCR	A		;See if device 3 selected
	JNZ	CRTOUT3		;Use custom CRT routine if so
CRTO2LP:IN	IF1US0		;Switch selects Interfacer I,II
	ANI	IF1TMSK		;Mask data xmit flags
	XRI	IF1FMSK		;Flip TBE status bit
	JNZ	CRTO2LP		;Loop if xmit buffer not empty
	MOV	A,C		;Get char to xmit
	OUT	IF1UD0		;Send it on it's way
	RET
;
; Custom CRT routine, initially set for Interfacer I,II UART 1.
CRTOUT3:IN	IF1US1		;BUILD YOUR OWN CUSTOM CRT OUTPUT ROUTINE
	ANI	IF1TMSK		;Mask data xmit flags
	XRI	IF1FMSK		;Flip TBE status bit
	JNZ	CRTOUT3		;Loop if xmit buffer not empty
	MOV	A,C		;Get char to xmit
	OUT	IF1UD1		;Send it on it's way
	RET
;
; Check for other console devices.
CONOUT2:DCR	A		;See if UC1 selected
;
; U C 1 -- Optional user console device.
UC1CO:	MVI	A,7		;Get USER 7 console status if UC1 selected
	JNZ	IF3OUT		;Complete data input if so
;
; B A T -- BATCH Mode (use PUNCH select bits to chose USER 0-3).
	LDA	IOBYTE		;Get I/O byte value again for PUNCH select
	JMP	PUNCH2		;And use their value to select the USER number
;
;********************************************************
;*	OUTPUT CHARACTER TO LIST LOGICAL DEVICE:	*
;********************************************************
;
; Send a character to the list device.  If the list device is not
; ready to receive a character wait until the device is ready.
;
; IOBYTE selects device to use as follows:
; 0 = TTY:,	1 = CRT:,	2 = LPT:,	3 = UL1:
;    USER 6	    xxx		    xxx		   USER 5.
;-----	If CRT, secondary select done using IOCNTL byte:
; 0 = USER 0,	1 = SysSup 1,	2 = IF1-P0,	3 = Custom.
;-----	If LPT, secondary select done using IOCNTL byte:
; 0 = USER 4	1 = IF1-P1,	2,3 = Custom.
;
;Entry:	C = ASCII character to be output.
;
LISTOUT:LDA	IOBYTE		;Get IOBYTE status
	RLC!	RLC		;Move select to lower two bits
	ANI	3		;Mask out select bits
	JZ	TTYOUT		;Use TTY if zero
	DCR	A		;See if CRT output
	JZ	CRTOUT		;Use it if so
	DCR	A		;See if line printer
	JNZ	UL1OUT		;User list device if not
;
; L P T -- Line printer list output.
	LDA	IOCNTL		;Get I/O control switch byte
	ANI	11$0000$00b	;Mask out LPT control bits
	JM	LPTCUST		;If custom, use routine
	JZ	LPTUSR4		;If control value was zero, USER 4
;
LPTIF1:	IN	IF1US1		;Get status
	ANI	IF1TMSK		;Mask ready bits
	XRI	IF1FMSK		;Flip TBE bit
	JNZ	LPTIF1		;Loop until ready
	MOV	A,C		;Get char
	OUT	IF1UD1		;Xmit it
	RET
;
LPTUSR4:MVI	A,4		;USER 4 as list device if so
	OUT	IF3UX		;Select mux in Interfacer 3,4
	LDA	IOCNTL		;Get I/O control byte
	ADD A!	ADD A		;Shift left 2 bits to mask USER 4 routine
	JMP	LSTSPCL		;Check for special list protocol
;
; U L 1 -- User device list output.
UL1OUT:	MVI	A,5		;USER 5 is special list device
	OUT	IF3UX		;Select mux in Interfacer 3,4
	LDA	IOCNTL		;Get I/O control byte
LSTSPCL:ANI	00$11$00$00b	;Mask out USER 5 output routine select bits
	JZ	IF3OUT2		;Normal transmit if zero
	ADD A!	ADD A		;Shift left 2 bits
	JM	ETX$ACK		;Use ETX/ACK protocol if higher bit set
;
; XON/XOFF (DC1/DC3) software protocol in use.
DC1$DC3:IN	IF3US		;Get status
	ANI	UDAV
	JZ	IF3OUT2		;List if no char ready on input
	IN	IF3UD		;Get character
	ANI	7FH		;Strip out parity bit
	CPI	XOFF		;^S? (XOFF)
	JNZ	IF3OUT2		;Continue if not
DC1X2:	IN	IF3US		;Get status
	ANI	UDAV
	JZ	DC1X2		;Wait for data from printer
	IN	IF3UD		;Get it
	ANI	7FH
	CPI	XON		;^Q? (XON)
	JNZ	DC1X2		;Loop until found
	JMP	IF3OUT2		;Xmit data using usual Interfacer 3,4 output
;
; --- Footnote #4. ---
;
; ---  ETX/ACK software protocol in use.
ETX$ACK:MOV	A,C		;Get character to transmit
	CPI	LF		;See if line feed (always last character if in
				;an ESCAPE sequence)
	JNZ	IF3OUT2		;Transmit data if not
	CALL	IF3OUT2		;Transmit Line Feed and return for more
	MVI	C,ETX		;Show end of line (End of Transmission, ETX)
	CALL	IF3OUT2		;Transmit and return to wait for ACK
ACKX2:	IN	IF3US		;Get status
	ANI	UDAV
	JZ	ACKX2		;Wait for data from printer
	IN	IF3UD		;Get it
	ANI	7FH		;Strip out parity bit
	CPI	ACK		;See if ACKnowledge of buffer empty
	JNZ	ACKX2		;Loop until found
	RET
;
; Custom list routine for LPT selection.
LPTCUST:IN	IF1US2		;Get status
	ANI	IF1TMSK		;Mask ready bits
	XRI	IF1FMSK		;Flip TBE bit
	JNZ	LPTCUST		;Loop until ready
	MOV	A,C		;Get char
	OUT	IF1UD2		;Xmit it
	RET
;
;********************************************************
;*	LIST DEVICE OUTPUT READY STATUS ROUTINE:	*
;********************************************************
;
; Return the ready status for the assigned list device.
;
; IOBYTE selects device to use as follows:
; 0 = TTY:,	1 = CRT:,	2 = LPT:,	3 = UL1:
;    USER 6	    xxx		    xxx		   USER 5.
;-----	If CRT, secondary select done using IOCNTL byte:
; 0 = USER 0,	1 = SysSup 1,	2 = IF1-P0,	3 = Custom.
;-----	If LPT, secondary select done using IOCNTL byte:
; 0 = USER 4	1 = IF1-P1,	2,3 = Custom.
;
;Exit:	A = 0 (zero), list device is not ready.
;	A = FFh (255), list device is ready.
;
LISTST:	LDA	IOBYTE		;Get IOBYTE status
	RLC			;if LPT or UL1, carry set
	JC	LPTLOS		;See which if either selected
	RLC			;if CRT, carry is set, else TTY
	MVI	A,6		;TTY select USER 6 on Interfacer 3,4
	JNC	IF3LOS
;
; C R T -- List status.
CRTLOS:	LDA	IOCNTL		;Get I/O control select byte
	ANI	00$0000$11b	;Check on CRT select bits
	JZ	IF3LOS		;If zero, CRT is Interfacer 3,4 USER 0
	DCR	A		;See if System Support I UART
	JNZ	CRTLOS2		;Try devices 2,3 if not
	IN	SS1US		;Get System Support I console status
	XRI	SS1FMSK		;Flip selected status values
	ANI	SS1TMSK		;Mask ready bit(s) (zero bit set if ready)
	DCR	A		;Make previous zero into 0FFh
	RNZ			;If ready
	XRA	A		;Show not ready
	RET
;
CRTLOS2:DCR	A		;See if Interfacer I or II, UART 0
	MVI	A,0FFh		;Show ready on custom routine if not
	RNZ			;Use custom routine if not
	IN	IF1US0		;alternate if Interfacer I or II selected
	JMP	IF1LOS		;Use common Interfacer I output status
;
LPTLOS:	RLC			;carry set if UL1, reset for LPT
	MVI	A,5		;USER 5 for UL1 on Interfacer 3,4
	JC	IF3LOS		; user list device selected
;
; L P T -- Line printer list status.
	LDA	IOCNTL		;Get I/O control switch byte
	ANI	11$0000$00b	;Mask out LPT control bits
	JM	CRTLOS2		;If custom, show ready
	MVI	A,4		;USER 4 of Interfacer 3,4
	JZ	IF3LOS		;if control value was zero
;
	IN	IF1US1		;Get alternate status port
IF1LOS:	XRI	IF1FMSK		;Flip TBE bit
	ANI	IF1TMSK		;Mask ready bits
	RZ			;Done if not ready
	ORI	0FFh		;Show ready
	RET
;
; U L 1 -- User device list status.
IF3LOS:	OUT	IF3UX		;Select mux in Interfacer 3,4
	IN	IF3US		;Get status
	XRI  	IF3TMSK		;Flip status of both
	ANI  	IF3FMSK		;Mask TBE, DSR bits (zero bit set if ready)
	DCR A!	RNZ		;Make previous zero into 0FFh, if ready
	XRA	A		;Show not ready
	RET
;
	PAGE
REUSE	SET	$	;Nominal Start of re-usable space
;************************************************
;*	COLD BOOT INITIALIZATION ROUTINES	*
;************************************************
;
    if XMBOOT		;If memory disk used for "re-boot", then re-use space
	BOOTCPM		;Boot CP/M from disk, exit only if load sucessful.
    endif
;
CBOOT:	LXI	SP,100h		;Temp stack for cold boot
;
; "Size" the M-Drive/H by counting active boards in use.
    if (HMDRIVE and HMSIZER)
	LXI	H,-((512-4)/2)	;Init the disk size (blocks 2K)
	LXI	B,30F8h		;"B" = "0", "C" = board count-1
HMDTEST:MOV A,B!  STA HM$MSG	;Put ASCII board count in Sign-on
	INR	B		;Bump count for next loop
	LXI	D,(512-4)/2	;Size of each board in 2K blocks
	DAD	D		;Add in more blocks for new board
	MOV A,C!  ADI 8		;Bump sector count by 8 (1 board)
	STA DPBHMD!  MOV C,A	;Save sector count in Disk Parameter Block
	CPI	64		;See if maximum count reached
	JZ	HMDZEND		;Done if so
	PUSH	H		;Save the current disk size in blocks
	CALL	HMDTRD		;Read a sector into the directory buffer
	XRA	A		;Clear command (for write)
	CALL	HMDTWR		;Write it back out again
	CALL	HMDTRD		;Read back, should now report no errors
	POP	H		;Recover current disk size
	JZ	HMDTEST		;Test for next board if no errors
	MOV A,C!  CPI 0		;See if no boards present
	JZ	HMDZERO		;If no boards zero dskmsk
	CPI 	32		;See if 2 megabytes or more
	JNC	HMD4K		;Set DSM only if so, rest of DPB is set
	CPI	8		;See if only 512K
	JNZ	HMD1 		;If not 512k, don't change EXM
	XRA A! STA DPBHMD+4	;Set EXM to 0 if only one board
HMD1:	XCHG			;Save DSM in "D,E"
	LXI	H,128-1 
 	SHLD	DPBHMD+7	;Set directory count to 128
	MVI 	A,0C0H 
	STA	DPBHMD+9	;Set number of reserved blocks to match
	XCHG			;Put DSM back in "H,L"
HMD4K:	DCX H!	SHLD DPBHMD+5	;Save corrected disk size allocation
	JMP	HMDZEND		;Skip no m-drive section
HMDZERO:
	XRA 	A		;Want 0 in the dskmsk table
	if XMDRIVE		;If extended memory drive present 
	   if (DRVH eq 0)	; then M-DRIVE/H is H:, if nothing else there
	STA DSKTBL+('H'-'A')*2 !STA DSKTBL+('H'-'A')*2+1
           else			;If H: is used, it is N:
	STA DSKTBL+('N'-'A')*2 !STA DSKTBL+('N'-'A')*2+1
	   endif
        else			;If no other mdrive, M: is used
	STA DSKTBL+('M'-'A')*2 !STA DSKTBL+('M'-'A')*2+1
	endif
HMDZEND:			;Was done if all 8 boards present
    endif
;
; Print the corrected sign-on message to the default console.
	LXI	H,SIGNON	;Show outside world that we're alive
	CALL	PRINT		;Output Banner
; Set up 8088 for moving data.
    if I8088
	MVI	A,0EAh		;8088 jump instruction
	STA	400h		;Place jump at 400h
	LXI	H,LOOP88	;to block move routine
	SHLD	401h
	LXI	H,0
	SHLD	403h
	IN	SWAPP		;Execute jump to LOP88
    else
	LXI	H,0
    endif
	SHLD	LOADVEC		;Make cold boot a warm boot, showing no overlay
;
    if DISK3
	CALL	D3INIT		;Initialize the DISK3 controller
    endif			;(before a boot if done there)
;
; Perform the cold boot from disk operation to bring in CP/M.
	CALL	LOADCPM		;Get CP/M into memory
;
; If "extended memory" drive is used for warm boots, save CP/M image there.
    if XMBOOT
	IN	SWAPP		;Perform block move using 8088 to save CP/M
    endif			;in high memory (locations preset)
;
; Load up the sector relocation tables for the hard disk systems.
    if DISK2		;Hard disk unit active (#0)
	LXI	H,RELTBL0	;Point to relocation table storage
	MVI	C,D2UNIT0	;Get Unit #0 ID
	CALL	D2INIT		;Read in table to initialize memory
      if DISK2X		;Second unit active
	LXI	H,RELTBL1	;Point to relocation table storage
	MVI	C,D2UNIT1	;Get Unit #1 ID
	CALL	D2INIT		;Read in table to initialize memory
      endif
      if DISK2Y		;Third unit active
	LXI	H,RELTBL2	;Point to relocation table storage
	MVI	C,D2UNIT2	;Get Unit #2 ID
	CALL	D2INIT		;Read in table to initialize memory
      endif
      if DISK2Z		;Fourth unit active
	LXI	H,RELTBL3	;Point to relocation table storage
	MVI	C,D2UNIT3	;Get Unit #3 ID
	CALL	D2INIT		;Read in table to initialize memory
      endif
    endif
;
; "Size" the extended memory drive which uses main memory and the 8088.
    if XMDRIVE
	LXI	H,TEMPBF	;Read the first word into temporary
	LXI	D,0		;Use base of each memory block (4K): D,E=0
	LXI	B,0100h		;1 word xfer ("B") and compare with 0 ("C")
	MVI	A,16		;Look for beginning page (start at 16*4K)
LOOK:	CALL	LOOK1		;Get byte at first location and put "C" byte
	CALL	LOOK1		;Put original back and get back stored value
	INR	A		;Point to next 4K block of memory
	JZ	MEMMAX		;Done if at maximum (leave very top 4K alone)
	INR C!	DCR C		;See if sent byte still 0 (mem bank exists)
	JZ	LOOK		;If this page exists look for another
MEMMAX:	    if XMBOOT
	SUI	19		;Adjust page number to include boot area
	    else
	SUI	17		;Adjust page number
	    endif
	MOV	E,A		;Save sectors/track in D,E (# of tracks fixed)
	LXI	H,DPBMEM	;Point to M-drive DPB
	MOV	M,E!	INX H	;Set the # of sectors/track
	MOV	M,D!	INX H
	XCHG!	CPI 65		;Check for different sized mdrives
	JNC	LMDRIVE		;Most of DPB is correct if large drive (>448K)
	XCHG!	DCR M!	INX H	;Change block size to 1K (BSH)
	MOV	A,M!	ORA A	;And mask to 1K (BLM), clear carry for high bit
	RAR!	MOV M,A		;Shift right 1 bit in mask, save again
	XCHG!	MVI A,63	;Change size of directory count to 64 if small
	STA	DPBMEM+7
	DAD	H		;Sectors * 2 for 1K blocks
LMDRIVE:DAD	H		;* 2 (2K blocks per (16K/4) 4K "sector")
	DCX	H		;less 1 for DSM (disk size in blocks)
	SHLD	DPBMEM+5	;Save it
    endif
;
;	If the length of a command line (in characters) is placed at location
; CCP+7, and the command line itself is placed starting at location CCP+8,
; CP/M will execute this command line after a boot (cold or warm) and execution
; is transferred to CCP+0.  If execution is transferred to CCP+3, this command
; line is ignored.  Only one line (as would be seen on screen) is allowed, but
; it may be a "SUBMIT STARTUP" or whatever to perform a sequence of operations.
;
;	This command line is best installed using 2nd generation SYSGENing by
; using DDT to alter the actual CCP (which, after a SYSGEN, exists at location
; 1600h for CompuPro systems).  BE CAREFUL.  There are some quirks that won't
; allow some commands to execute properly (and since Digital Research didn't
; document all of this, no help can expected from them or CompuPro).
;
	XRA	A		;Always set the default disk to drive A: on
	STA	CDISK		;cold boot entry to CP/M (or SUBMIT won't work)
	CALL	GOCPM		;Set up vectors in Page 0
	JMP	CCP		;Go to CP/M and execute anything in CCP buffer
;
;----- cold boot subroutines -----
;
;	DISK2 sector relocation table initialization routine.
;
;Entry:	H,L = Base address of unit's relocation table,
;	C = Unit select byte (by specifying the outer logical drive).
;
    if DISK2
D2INIT:	PUSH	H		;Save DMA specification
	CALL	SELDSK		;Set the current active drive specification
	POP	B		;Recover DMA for setup
	CALL	SETDMA		;Save DMA address of table
	MVI  C,(D2$SCNT-1) * 8	;Get relocation table from last sector on drive
	CALL	SETSEC		;Set for first active record (8 / 1K sector)
	CALL	HOME		;Restore pointers and register storage
	JMP	READ		; and read sector at Cylinder 0, Head 0
    endif
;
; Initialize the DISK3 controller.
    if DISK3
D3INIT:	LXI	H,D3CIOPB	;Start of normal CIOPB
	SHLD	D3DIOPB+D3$LINK	;Put link in default IOPB
	SHLD	D3CIOPB+D3$LINK	;Put link in normal IOPB
	XRA	A		;Clear A
	STA	D3DIOPB+D3$LNK3 ;Set default dma address
	STA	D3CIOPB+D3$LNK3	;
	STA	D3CIOPB+D3$DMA3	;Set extended address of DMA to 0
	MVI M,D3$NOOP !	INX H	;First command is NOOP,Point to status 
	MOV M,A ! INX H 	;Clear status byte, point at drive
	MOV M,A ! DCX H		;Set drive to 0, point back at status
	MVI	A,D3RESET	;Reset DISK3 controller (Select controller #1)
	OUT	D3$PORT
	MVI	A,D3$ATTN
	OUT	D3$PORT		;Send attention to controller
	LXI	B,0FFFH		;Wait only this many times incase no DISK3 
D3INIT3:MOV A,M ! ORA A		;Wait until status non-zero
	JNZ	D3INIT4		;Exit timeout loop if status non-zero
	DCX	B		;Count this wait loop
	MOV A,B ! ORA C		;See if zero reached	
	JNZ	D3INIT3		;Try again if not timed out 
	LXI	H,NOD3		;If timed out ...
	JMP	PRINT		; ....print message and return
;	
D3INIT4:MVI	C,NDISK3-1	;Get number of active drives 
D3INIT1:PUSH	B		;Save current drive
	MOV	A,C		;Get current drive number 
	STA	D3$DRVS+D3CIOPB	;Set current drive number
	MVI	A,D3$HOME	;Home drive first thing
	CALL	D3EXEC		;Execute home command 
	LXI	H,100H		;Read specify sector into TPA
	SHLD	D3$DMA+D3CIOPB	;Set as dma address in DISK3 iopb
	LXI	H,D3CIOPB+3	;Get start of DISK3 arguments
	XRA A			;Clear A
	MVI M,1 ! INX H		;Set as read command
	MOV M,A ! INX H		;Want to start reading at sector 0
	MOV M,A ! INX H		;High byte of sector
	MOV M,A ! INX H		;Want to start reading at track 0
	MOV M,A ! INX H		;High byte of track
	MVI M,2 ! INX H		;Want to read 2 sectors
	MOV M,A ! INX H		;High byte of sector count
	MVI 	A,D3$XFER	;Do DISK3 read/write sector command
	CALL	D3EXEC		;Execute command
	MVI	C,8		;Now compare 1st 8 bytes against signon message
	LXI	H,SIGNON	;Get start of "CompuPro" in signon
	LXI	D,100H		;Get begining of 8 flag bytes
SGNCMP:	LDAX	D		;Get first byte of what is on disk
	CMP	M		;See if it matches signon
	JNZ	NOFMT		;If no match, disk is not formatted right
	INX D ! INX H		;Move both pointers up
	DCR	C		;Count this byte
	JNZ	SGNCMP		;Do 8 bytes
	LXI	H,100H+16	;Point to specify block
	SHLD	D3$DMA+D3CIOPB	;Put address of specify in iopb
	MVI	A,D3$SPEC	;DISK3 specify command
	CALL	D3EXEC		;Tell disk3 to perform specify 
	LXI	H,100H+1024	;Point to start of bad map
	SHLD	D3$DMA+D3CIOPB	;Put address of badmap in iopb
	MVI	A,D3$MAP	;Set badmap command
	CALL	D3EXEC		;Execute command to set the relocation map
D3INIT2:POP	B
	DCR	C		;Count this drive 
	JP	D3INIT1		;Do for all drives
	LXI	H,D3CIOPB+3	;Point at ARG1
	MVI M,0     ! INX H	;Use mode 0
	MVI M,MRTRY ! INX H	;Set number of retries
	MVI M,4     ! INX H	;Max of 4 drives on one disk3
	MVI	A,D3$GLBL	;Disk3 set globals command 
	JMP	D3EXEC		;Execute globals command and return with status
;
NOFMT:	POP B ! PUSH B		;Recover/save drive number
	MOV A,C ! ADI '0'	;Add ascii bias to drive number
	STA	FMTDRV		;Set ascii drive letter in message
	LXI	H,FMTMSG	;Show not formatted message
	CALL	PRINT
	JMP	D3INIT2		;Go do any remaining drives
; 
FMTMSG:	DB	'Hard disk unit '
FMTDRV:	DB	'X not formatted properly.',CR,LF,NULL
NOD3:	DB	'No DISK3.',CR,LF,NULL
    endif
;
;	Routine to get a word from high memory, exchange the first byte with
; the contents of register "C", and return the word to high memory.  "C" is
; usually 00h on first call to this routine, and its storage in high memory
; is checked by the second call.  If storage exists at this high block, the
; second call will return with "C" still containing a 0; if not the S-100
; (IEEE 696) bus will return with 0FFh, indicating where the top of active
; memory is.
;
    if XMDRIVE
LOOK1:	PUSH	PSW
	CALL	OUTOF		;Read word from external
	XCHG			;Exchange source, dest (fix)
	MOV	A,M		;Get first byte from external
	MOV	M,C		;Set up byte to send back to external
	MOV	C,A		;Put recovered byte in "C"
	POP PSW! PUSH PSW	;Recover outside extended address
	CALL	INTO		;Send "fixed" word back to external
	POP	PSW		;Recover outside extended address
	RET
    endif
;
; Similar routine to size the number of boards belonging to an M-Drive/H.
    if (HMDRIVE and HMSIZER)
HMDTRD:	MVI	A,FD$RDAT	;Floppy "read data" command works for M-Drive
HMDTWR:	STA	CIOPB		;Store command for Read/Write process
	LXI	H,DIRBUF	;Use the directory buffer area for storage
	PUSH	B		;Save sector, message value
	MOV	A,C		;Sector to test
	LXI	B,4		;Actual track number for parity
	LXI	D,4*128		;"Translated" track number 4 (directory)
	CALL	HMD$FNL		;Perform Read/Write operation
	POP	B		;Recover sector and the ASCII board number
	RET			;Return with parity error status
    endif
;
	PAGE
; Sign on message -- must be terminated by a "NULL" byte.
SIGNON:	DB	'CompuPro Systems '
	DB	MSIZE/10+'0',MSIZE mod 10 + '0'
	DB	'K CP/M '
	DB	VERS/10+'0','.',VERS mod 10 + '0'
	DB	CBIOSV	;Cbios version letter
    if I8088
	DB	'x'	;Show 8088 code active
    endif
    if Z80
	db	'Z'	;Show Z80 code active	
    endif	
    if XMDRIVE
	DB	'M'	;Show M-DRIVE using extended memory and 8088 included
    endif
	DB	CR,LF	;New line for other specifications
    if FLOPPY5
	DB	'5" Disk1'	;Show 5 1/4 inch drives included
      if (HMDRIVE or DISK2 or DISK3)
	DB	', '	;Separator
      endif
    endif
    if HMDRIVE
HM$MSG:	DB	HM$NUM+'0',' M-Drive/H' ;Show active M-DRIVE/H boards included
      if (DISK2 or DISK3)
	DB	', '	;Separator
      endif
    endif
    if DISK2
	DB	' Disk2/'	;Hard disk (Disk 2) separator ID
      if D2M10
	DB	'M10'	;Show Hard Disk with 10 Mbytes included
      endif
      if D2M20
	DB	'M20'	;Show Hard Disk with 20 Mbytes included
      endif
      if D2F20B
	DB	'F20'	;Show Hard Disk with 20 Mbytes included
      endif
      if D2M26
	DB	'M26'	;Show Hard Disk with 26 Mbytes included
      endif
      if D2F40B
	DB	'F40'	;Show Hard Disk with 40 Mbytes included
      endif
      if DISK3
	DB	', '	;Separator
      endif
    endif
    if DISK3
	DB	' Disk3/'	;Hard disk (Disk 3) separator ID
      if D3ST506
	DB	'M5'	;Show Hard Disk with  5 Mbytes included
      endif
      if D3Q540
	DB	'Q540'	;Show hard Disk with 40 Mbytes included
      endif	
    endif
	DB	NULL	;End of sign-on message
;
ENDCODE	EQU  $	;End of active code
;--------------------------------
    if (BOOTSEC * 128) lt (ENDCODE - BIOS) ;If disk storage area insufficeint
--:ERROR:--	Assembly time BOOT SPACE SHORTAGE
SPACE$OVER EQU (ENDCODE - BIOS)-(BOOTSEC * 128)
    else
SPACE$LEFT EQU (BOOTSEC * 128)-(ENDCODE - BIOS)	;Amount of boot space left
    endif
;
;********************************
;*	CP/M DISK WORK SPACE:	*
;********************************
	ORG  REUSE  ;Space recovered from code used only at cold boot time
;
; Allocation vectors (reserved space).
	IRPC	?Y,ABCDEFGHIJKLMNOP
ALV$&?Y	DS	ALVZ&?Y		;;Specific drive allocation vector size
	ENDM
;
; Checksum vectors (reserved space for disk change identification).
	IRPC	?Z,ABCDEFGHIJKLMNOP
CSV$&?Z	DS	CSVZ&?Z		;;Specific drive checksum vector size
	ENDM
;
;-------------------------------
    if DISK2			;Disk 2 controller storage containing
RELTBL0	DS	D2$SCNT * 3	;Sector relocation tables for respective units
      if DISK2X
RELTBL1	DS	D2$SCNT * 3	;Unit 1
      endif
      if DISK2Y
RELTBL2	DS	D2$SCNT * 3	;Unit 2
      endif
      if DISK2Z
RELTBL3	DS	D2$SCNT * 3	;Unit 3
      endif
    endif
ENDWORK	EQU	$	;End of workspace below block buffers
;-------------------------------
	ORG	0FB00h	;Place directory, host buffer near final block of RAM
;
    if (ENDWORK gt $) ;Overflow of storage space requirement
--:ERROR:--	Assembly time STORAGE SPACE SHORTAGE
NEED$SPACE EQU	(ENDWORK - $)
    else
SPARE$RAM  EQU	($ - ENDWORK)
    endif
;
HSTBUF	DS	HSTSIZ	;Host buffer (sector blocking/deblocking)
DIRBUF	DS	128	;Directory buffer
;
; Disk access information.
; This area is organized into the following groups:
;	  1) Sector number.
;	2,3) Track number.
;	  4) Disk drive.
;	  5) Drive type.
; Each of these groups has three cells for the current disk request,
; ACTual disk transfer, and active host disk.
;
SAVSEC	DS	2	;Special storage for record in use
NUMSEC	DS	1	;Number of sectors
;
;	User (desired, seek to) Data Buffer Address, Parameters.
;
DMAADR	DS	2	;Lower 16 bits (least, middle)
DMAADE	DS	1	;Extended address
;
SEKSEC	DS	1	;Current sector request
SEKGPL	DS	1	;Current sector gap length
SEKTRK	DS	2	;Current track request
SEKDSK	DS	1	;Current disk number request
SEKTYP	DS	1	;Current disk's type
;
;	(Active) Physical Data Buffer Address, Parameters.
;
BUFADR	DS	2	;Lower 16 bits (least, middle)
BUFADE	DS	1	;Extended address
;
ACTSEC	DS	1	;Actual transfer operation
ACTGPL	DS	1	;Actual sector gap length used
ACTTRK	DS	2	;Actual transfer operation
ACTDSK	DS	1	;Actual transfer operation
ACTTYP	DS	1	;Actual disk's type
;
;	Host Disk Physical Parameters.
;
HSTSEC	DS	1	;Active host disk
HSTGPL	DS	1	;Host sector gap length
HSTTRK	DS	2	;Active host disk
HSTDSK	DS	1	;Active host disk
HSTTYP	DS	1	;Active disk's type
;
;	Floppy Disk "Command Input/Output Parameter Block" for controller ops.
;
CIOPL	EQU	9
CIOPB	DS	CIOPL	;Disk command buffer
TEMPBF	DS	8	;Result status cells
;-------------------------------
	if DISK3
;		Command, Status, Drive, Read/Write flag
D3CIOPB:DS	4
	DS	2		;Sector
	DS	2		;Track
	DS	2		;Number of sectors to xfer
	DS	2		;Data DMA address
	DS	1		;In current bank
	DS	2		;Link returns to start of this IOPB
	DS	1		;In current bank as well
	endif
;
	ORG	ENDCODE	;Show end of booted code address to user
	END		;Das Ende
