; LAST UPDATE:          04 FEB 2001
; REASON FOR UPDATE:    Remove references to DISK2, HMDRIVE, etc.
;                       Simplify code where applicable.
; 05 FEB 85, Ver 2.2Q   Removed HMX1BIOS and HMXIO overlay.  Unified the BIOS.
;                       Created loader w/CCP overlay similar to CP/M-86.    jrs
; 04 FEB 85, Ver 2.2P   Added DISK1A driver code and 5" floppy disk boot    jrs
; 20 JUN 84, Ver 2.2Na  Q520 DPB & fix ST506 DPB                            rrs
; 02 DEC 83, Ver 2.2N   Fixed sizing bug for 4 M-DRIVE/H's                  llo
; 10 OCT 82, Ver 2.2M   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 relocatio 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
; 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: HMXBIOS.ASM    -- Customized BIOS for CP/M-80 version 2.2,
;               developed for use with CompuPro Systems Components.
;
;       ==========================      Copyright 1983, CompuPro Corporation.
;       ||                      ||
;       ||     HMXBIOS.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     'Q'     ;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
;
; ********************
;
; ---   A general note about the rewrite of this BIOS.  The initial reason for
; the rewrite was to clarify (ie. comment) some of the original code as well as
; changes and additions that had been made in later releases.  In the process,
; much "deadweight code", or code which never took effect was discovered.
;       Ironically, the bulk of this code came from the examples given by
; the Digital Research blocking/deblocking algorythms found in the standard
; CP/M 2.2 (aka CP/M-80) manuals themselves.  The result you will find here
; does not conform with those outlines, but instead utilizes the fact that the
; largest physical sector permitted is 1024 bytes, the same value as the
; minimum logical block size.  Under these conditions, a disk read or write
; involving blocking/deblocking is triggered according to the physical size
; only, and no accounting of the logical blocking is required.  This technique
; actually will function better, in a more clearly documented manner than the
; "skeletal outline".  The overall result has been a noticable (20-100%) speed
; increase with absolutely no loss of compatibility or functionality as
; specified in the CP/M 2.2 documentation.
;
; ---   ADDITIONAL INFORMATION ABOUT DIFFERENCES IN THE COMPUPRO CBIOS  ---
;
;       1000h is the offset for CompuPro CP/M rev level 2.2J MOVCPM utility,
; but this will vary with the letter version of CP/M that you purchased.
; This BIOS offset was selected to comply with the "J" release, but you must
; move the offset for the "K" release, and others may be different.  Digital
; Research manuals show an offset of 0600h for their nominal 20K BIOS, which
; means that for the "J" version CompuPro system a "MOVCPM 64 *" will actually
; generate a "true" CP/M 61.5K system.  This is no real problem, because the
; CompuPro boot loads a CBIOS of 22 (or with special BOOT, 24) 128 byte
; sectors, which is 2.75 or 3K bytes (length 0B00h or 0C00h), both of which
; are much larger than would be permitted with a "true" 64K system.  CP/M can
; actually be generated by Digital Research to run on any page boundary
; (increments of 256 bytes), but once delivered it can only be "moved" with
; MOVCPM by 1Kbyte amounts.  Far better to have room in the CBIOS for expansion
; than follow Digital Research too literally.  This information is included
; for reference purposes only.                                          aep
;
;
    if INTRACT                  ;If interrupts are active
;
; System Support I Priority Interrupt Controller masks for interrupts:
MMASK   EQU     0111$1111b      ;Master initial mask (Slave at I7 enabled)
;               7654 3210       ;Interrupt selection bits (enabled if "0")
SMASK   EQU     0111$1111b      ;Slave initial mask (Uart recv enabled)
; -- 8085 internal interrupt mask register --
;                     765.5     ;Interrupt selection bits
M8085   EQU     0000$1111b      ;Mask for RST 5.5, 6.5, and 7.5
;
; System Support I as CRT (interrupt driven).
CRTSS   EQU     SS1US           ;System support 1 status
CRTDAV  EQU     SS1DAV          ;Data available mask
CRTTMSK EQU     SS1TMSK         ;Xmit ready mask
CRTFMSK EQU     SS1FMSK         ;Xmit buffer flip bits
CRTDATA EQU     SS1UD           ;Data port
CRT$DC  EQU     FALSE           ;CRT uses XON/XOFF protocol
CRT$DSR EQU     TRUE            ;CRT uses hardware DSR protocol
CRTILEN EQU     48              ;Length of input buffer
CRTOLEN EQU     128             ;Length of output buffer
    else        ;Interrupts are to be deactivated
;
MASTER  EQU     0               ;Master PIC code base is zeroed
SLAVE   EQU     0               ;Slave PIC code base is zeroed
MMASK   EQU     1111$1111b      ;Master interrupt mask has all disabled
SMASK   EQU     1111$1111b      ;Slave interrupt mask has all disabled
M8085   EQU     0000$1111b      ;Mask out RST 5.5, 6.5, and 7.5 (inactive)
;
; System Support I as CRT (interrupt driven).
CRTSS   EQU     SS1US           ;System support 1 status
CRTDAV  EQU     SS1DAV          ;Data available mask
CRTTMSK EQU     SS1TMSK         ;Xmit ready mask
CRTFMSK EQU     SS1FMSK         ;Xmit buffer flip bits
CRTDATA EQU     SS1UD           ;Data port
CRT$DC  EQU     FALSE           ;CRT uses XON/XOFF protocol
CRT$DSR EQU     TRUE            ;CRT uses hardware DSR protocol
CRTILEN EQU     48              ;Length of input buffer
CRTOLEN EQU     128             ;Length of output buffer
    endif
; 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)
;
; *******************
;
;       I/O device initialization is performed upon cold boot only, and some
; of the routines for this are located at the end of the BIOS.  The bulk of
; the UARTs, interrupt controllers, timer/counters, etc. are initialized by
; a program called the Initial Program Loader (IPL for short).  This program
; also performs the task of loading the CBIOS and initializing high RAM for
; the 8088 to function.  By inspecting the CP/M file titled "HMFBOOT.ASM", you
; will see some of the reasoning behind placing the initialization routines
; there.  The program has quite a few "absolute location" requirements, and
; this made it easy to assign fixed locations to the important I/O initial
; settings.  You will also see that it is quite easy alter or "poke" additional
; I/O port initialization sequences into the allotted memory area.  If you
; opt to change the source program, be extremely careful not to inadvertantly
; alter the absolute location assignments -- this will nearly always cause
; the program to become non-functional and the system will play dead.
;
;       More on the BIOS -- Since the cold boot routine is utilized only once,
; it is placed at the upper end of RAM.  This permits the RAM used by these
; initialization routines to be "recycled" for use by CP/M as a portion of the
; general workspace.  Since the cold boot routine is executed prior to any
; CP/M BDOS calls, there is no conflict in performing this reclamation.
;
;       For individual logical to physical device assignments (I/O redirection
; performed usually thru the STAT utility), see the individual logical device
; vectors.  These are located between the Warm Boot and Cold Boot routines,
; again to permit them to be overlayed by other routines as needed.  This will
; usually take the form of input/output interrupt driven routines, which are
; generally larger than the normal CBIOS boot track would permit on a cold
; boot.
;
;****************************************************************
;*      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
;
;****************************************************************
;*      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
;
    if FLOPPY8
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
;
    endif
    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
  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 DISK3    ;Disk 3 hard disk controller active.
      if D3ST506        ;Seagate  5 Mbyte drive (ST506)
  MAKEDPB       D3N1,   53h,    4,9,8,    2,149,  4096,  1024,0 ;First 5M
      endif
      if D3M3425        ;Miniscribe 20 Mbyte drive (MS3425)
  MAKEDPB       D3N1,   53h, 4,9,8,    5,175, 4096, 1024,0   ;FIRST 5M
  MAKEDPB       D3N2,   53h, 4,9,8,  175,425, 4096, 1024,0   ;Second 5M
  MAKEDPB       D3N3,   53h, 4,9,8,  425,615, 4096, 1024,0   ;Third 10M+
  ;MAKEDPB       D3N4,   53h, 4,9,8,  415,615, 4096, 1024,0   ;Last
      endif
      if D3ST251
  MAKEDPB       D3N1,   53h, 6,9,8,    6,141, 4096, 1024,0   ;First 7+M
  MAKEDPB       D3N2,   53h, 6,9,8,  141,276, 4096, 1024,0   ;Second 7+M
  MAKEDPB       D3N3,   53h, 6,9,8,  276,411, 4096, 1024,0   ;Third 7+M
  MAKEDPB       D3N4,   53h, 6,9,8,  411,546, 4096, 1024,0   ;Fourth 7+M
  MAKEDPB       D3N5,   53h, 6,9,8,  546,681, 4096, 1024,0   ;Fifth 7+M
  MAKEDPB       D3N6,   53h, 6,9,8,  681,820, 4096, 1024,0   ;Sixth 7+M
      endif
    endif
;
;************************************************************************
;*      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 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
          if DISK1
  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
          if DISK1A
  MAKEDPH  E, 02h, XLT5D3, FD5  ;Drive G: 5 inch floppy
  MAKEDPH  F, 03h, XLT5D3, FD5  ;Drive H: 5 inch floppy
          endif
        endif                                           ;=3
;
      else              ;Booting from 5 1/4 inch disks  ;=2
;
        if DISK1
  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
        endif
        if DISK1A
  MAKEDPH  A, 02h, XLT5D3, FD5  ;Drive A: 5 inch floppy
  MAKEDPH  B, 03h, XLT5D3, FD5  ;Drive B: 5 inch floppy
        endif
;
        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 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 D3M3425
  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, 5M
  MAKEDPH  C, 00h, 00, D3N3     ;Drive G: 5 inch hard disk, select #0, 10M
 ; MAKEDPH  D, 00h, 00, D3N4     ;Drive 
        if DISK3X
  MAKEDPH  D, 01h, 00, D3N1     ;Drive G: 5 inch hard disk, select #0, 5M
  MAKEDPH  E, 01h, 00, D3N2     ;Drive G: 5 inch hard disk, select #0, 5M
  MAKEDPH  F, 01h, 00, D3N3     ;Drive G: 5 inch hard disk, select #0, 10M
 ; MAKEDPH  H, 01h, 00, D3N4     ;Drive 
         endif
        endif
        if D3ST251
  MAKEDPH  A, 00h, 00, D3N1     ;Drive G: etc.
  MAKEDPH  B, 00h, 00, D3N2
  MAKEDPH  C, 00h, 00, D3N3
  MAKEDPH  D, 00h, 00, D3N4
  MAKEDPH  E, 00h, 00, D3N5
  MAKEDPH  F, 00h, 00, D3N6
         if DISK3X
  MAKEDPH  G, 01h, 00, D3N1
  MAKEDPH  H, 01h, 00, D3N2
  MAKEDPH  M, 01h, 00, D3N3
  MAKEDPH  N, 01h, 00, D3N4
  MAKEDPH  O, 01h, 00, D3N5
  MAKEDPH  P, 01h, 00, D3N6
         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 DISK1
          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
        if DISK1A
          if ((DRVK eq 0) and (DRVL eq 0))              ;=3
  MAKEDPH  K, 02h, XLT5D3, FD5  ;Drive K: 5 1/4 inch floppy
  MAKEDPH  L, 03h, XLT5D3, FD5  ;Drive L: 5 1/4 inch floppy
          else                                          ;=3
            if ((DRVN eq 0) and (DRVO eq 0))            ;=4
  MAKEDPH  N, 02h, XLT5D3, FD5  ;Drive N: 5 1/4 inch floppy
  MAKEDPH  O, 03h, XLT5D3, FD5  ;Drive O: 5 1/4 inch floppy
            endif                                       ;=4
          endif                                         ;=3
        endif
      endif                                             ;=2
    endif                                               ;=1
;
;----------------------------------------
;
;       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
;
    if FLOPPY8
        LXI     B,X8TABLE       ;Show format parameter table base
    endif
    if (FLOPPY5 and not FLOPPY8)
        LXI     B,X5TABLE       ;Show format parmaeter table base
    endif
    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 or set data rate clock
    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
    if FLOPPY8
        LXI     D,DPB8TBL+1     ;Get base of DPB's to get correct one
    endif
    if (FLOPPY5 and not FLOPPY8)
        LXI     D,DPB5TBL+1     ;Get base of DPB's to get correct one
    endif
    if (FLOPPY5 and FLOPPY8)
        CPI     FD5TYPE         ;See if 5 1/4 inch disk type
        JC      FIX8DPB         ;Use that format table if so
        LXI     D,DPB5TBL+1     ;Get, save base of DPB's to get correct one
    endif
FIX8DPB: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 6! 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
TREAD1: LXI     H,RECAL         ;Home to track 0
        MVI     B,LRECAL        ;No status bytes for command
        CALL    MOVETO          ;Process command, (Always reports success)
    if DISK1A
        JZ      TREAD3
        LDA     TEMPBF
        MOV     B,A
        ANI     FDC$SKE
        JZ      TRDX
        MOV     A,B
        ANI     FDC$CHK
        JZ      TRDX
        LXI     H,RECAL
        MVI     B,LRECAL
        CALL    MOVETO
        RNZ
TREAD3: LDA     SEKTYP
        CPI     FD5TYPE
    endif
        MVI     A,2             ;Seek to track two (only done if recal worked)
    if DISK1A
        JC      TREAD4
        MVI     A,4
    endif
TREAD4: 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
TRDX:   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
;
;----------------------------------------
;
;       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 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 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.
;
; ***********************
;
;---    As a side note, for those of you who never got a chance to work
; with one of the earliest (and possibly the slowest) operating systems
; implemented on a micro, the expression "CIOPB" used here comes from the Intel
; ISIS software.  It stands for "Command Input/Output Parameter Block", which
; itself is a derivative of the method used by the larger mainframes for
; communicating with their I/O channel controllers.
;       Since all of the versions of the CP/M operating system thru at least
; version 2.0 were developed using Intel MDS systems, ISIS and PL/M initially,
; there are many artifacts in CP/M that reflect that evolutionary process.
; The most obvious holdover from the MDS system is the IOBYTE, which is
; implemented in CP/M in exactly the same manner defined by the MDS monitor.
; (In the very early days of micros, there were no operating systems--just
; "monitor" programs that provided the barest necessities for making the thing
; behave vaguely like a computer).  The choice of retaining the flavor of
; the older Intel disk controllers by naming the variable here "CIOPB" came
; originally from work done at SORCIM, but I thought you might be interested
; in some of its related history.                                       aep
;
;
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"
        XCHG                    ;Track in "D,E" and pointer in "H,L"
    if 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.
;
  if DISK1
GTREADY:MVI     A,0F0h          ;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
  endif
  if DISK1A
    if (FLOPPY5 or MOTOR8)
GTREADY:
    endif
    if (MOTOR5 or MOTOR8)
        MVI     A,0F0H
        OUT     FD8PORT + FDON
    endif
    if not MOTOR8
        LDA     ACTTYP
        CPI     FD5TYPE
        JC      GTRDY1
    endif
MAKRDY:
    if not READY5
        CALL    SOFTRDY
        MOV     A,B
        ORA     C
        JZ      NOTRDY
    endif
    if READY5
      if not (FLOPPY5 or MOTOR8)
GTREADY:
      endif
    endif
GTRDY1: MVI     E,10
GTRDY1A:PUSH    D
        CALL    GTDSTS
        POP     D
        RZ
        DCR     E
        JNZ     GTRDY1A
    if not READY5
      if (MOTOR5 or MOTOR8)
;       MVI     A,0F0H
;       OUT     FD8PORT + FDON
        LDA     ACTTYP
        CPI     FD5TYPE
       if MOTOR5
        JNC     MAKRDY
       endif
       if MOTOR8
        JC      MAKRDY
       endif
      endif
    endif
;
NOTRDY: LXI     H,NRDYM1
        CALL    PRINT
GTRDY2:
    if not READY5
      if (MOTOR5 or MOTOR8)
        CALL    SOFTRDY
        MOV     A,B
        ORA     C
        JZ      GTRDY3
      endif
    endif
        CALL    GTDSTS
        RZ
GTRDY3: CALL    CIS
        JZ      GTRDY2
        RET
;
;       SOFTRDY -- Get drive ready status by samplint the INDEX pulse status
;       bit directly associated with the desired drive.  5-1/4 inch drives
;       often don't have a normal READY line, but it can be simulated by
;       testing for two consecutive index pulses from the selected drive.
;       This is also necessary for utilizing motor on/off control with 8-inch
;       drives, since their ready line will remain unchanged if the motor is
;       stopped with the diskette remaining in the drive.  This routine
;       verifies both that a diskette exists in the selected drive, and that
;       it is rotating at speed.
;
;Exit:  BC = 0 if the selected drive "timed out" without an index pulse
;         appearing within approximately one second.
;
    if not READY5
SOFTRDY:MVI     A,0F0H
        OUT     FD8PORT + FDON
        LDA     ACTDSK
        ORI     FDXUS
        OUT     FD8PORT + FDCS
        LXI     B,4000H
SFTRDY1:MVI     D,10
SFTRDY2:XTHL
        XTHL
        DCR     D
        JNZ     SFTRDY2
        IN      FD8PORT + INTS
        ANI     FDINDEX
        JNZ     SFTRDY3
        DCX     B
        MOV     A,B
        ORA     C
        JNZ     SFTRDY1
SFTRDY3:MOV     A,B
        ORA     C
        JZ      SFTRDYX
SFTRDY4:MVI     D,25
SFTRDY5:XTHL
        XTHL
        DCR     D
        JNZ     SFTRDY5
        IN      FD8PORT + INTS
        ANI     FDINDEX
        JZ      SFTRDY6
        DCX     B
        MOV     A,B
        ORA     C
        JNZ     SFTRDY4
SFTRDY6:MOV     A,B
        ORA     C
        JZ      SFTRDYX
SFTRDY7:IN      FD8PORT + INTS
        ANI     FDINDEX
        JZ      SFTRDY7
SFTRDYX:PUSH    B
        LDA     FIXMASK
        OUT     FD8PORT + FDCS
        LXI     B,1
        CALL    FIXWAIT
        POP     B
        RET
;
    endif
;       FIXWAIT -- delay approximately one millisecond
FIXWAIT:MVI     A,(CPUSPD*1000)/26
FIXDLY: INX     B
        DCX     B
        DCR     A
        JNZ     FIXDLY
        DCX     B
        MOV     A,B
        ORA     C
        JNZ     FIXWAIT
        RET
  endif
;
;       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:
    if DISK1
        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
    if DISK1A
        PUSH    B
        PUSH    D
        PUSH    H
        ANI     FD5TYPE
        MOV     B,A
        LDA     FIXTYPE
        CMP     B
        MOV     A,B
        STA     FIXTYPE
        JZ      FIX58X
      if MOTOR5 or MOTOR8
        MVI     A,0F0H
        OUT     FD8PORT + FDON
      endif
        LDA     ACTDSK
        ANI     3
        RLC
        MOV     C,A
        MVI     B,0
        LXI     H,SPECTBL
        DAD     B
        MOV     A,M
        INX     H
        MOV     H,M
        MOV     L,A
        MOV     A,M
        INX     H
        STA     FIXMASK
        OUT     FD8PORT + FDCS
        LXI     B,1
        CALL    FIXWAIT
        LXI     B,(3*256)+0
        CALL    EXEC1
        LXI     B,240
        CALL    FIXWAIT
FIX58X: POP     H
        POP     D
        POP     B
        RET
    endif
  endif
;
;****************************************************************
;*      FINAL PROCESSING FOR HARD DISK DRIVES (DISK3)           *
;****************************************************************
;
;       Routines for driving the DISK3 controller.
;
HARDFNL:
    if DISK3
        CPI     D3$TYPE         ;See if DISK3 driver
    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         ;Loop for status test if not
        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
;
;       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
;
;       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 DISK1A
FIXTYPE:DB      -1
FIXMASK:DB      0
;
SPECTBL:DW      SPEC8
        DW      SPEC8
        DW      SPEC5
        DW      SPEC5
;
SPEC8:  DB      0
        DB      FD$SPEC
        DB      (SRT8 shl 4) + HUT8
        DB      HDLT8 shl 1
;
SPEC5:  DB      FD5SL+FDF2S
        DB      FD$SPEC
        DB      (SRT5 shl 4) + HUT5
        DB      HDLT5 shl 1
    endif
;
    if DISK3
D3MSG:  DB      'DISK3 Error #'
D3MSG2: DB      'x',0
    endif
;
;************************************************
;*      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
;
    if INTRACT
; Use interrupt driven SS1 UART.
        LDA     CRTICNT         ;See if anything in buffer
        ORA A!  RZ              ;Non-zero if something there, return if nothing
        MVI     A,0FFh          ;Show character ready status
        RET
    else
        IN      CRTSS           ;Get console status
        JMP     IFXST           ;Mask data available and return with status
    endif
;
CRTST2: DCR     A               ;See if Interfacer I or II port 0 as CRT
        JNZ     CRTST3          ;Use custom crt driver if not
        IN      IF1US0          ;Interfacer I,II UART 0 status
        JMP     IFXST           ;Mask data available and return with status
;
; 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)
        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
;
    if INTRACT
; Interrupt driven console input.
CRTCHK: LDA CRTNULL!    ORA A   ;See if output null control on
        JNZ     CRTIN0          ;See if character ready if not
        DCR A!  STA CRTNULL     ;Turn off for next pass
CRTSTW: LDA     CRTXDIF         ;See if any characters left in xmit buffer
        ORA     A               ;(no characters to xmit if so)
        JNZ     CRTSTW          ;Loop until output buffer empty
CRTIN0: LXI     H,CRTICNT       ;See if anything in buffer
        MOV A,M!  ORA A         ;Non-zero if something there
        JZ      CRTIN0          ;Loop until character ready
        DI                      ;Hold off interrupts to get end character
        MOV     C,M             ;Get length left in "C"
        DCR     M               ;Bump down 1 for next, point at buffer - 1
        ADD L!  MOV L,A         ;Point to last character in buffer position
        MOV A,H!  ACI 0!  MOV H,A
        MOV     A,M             ;Get char at end of buffer
        EI                      ;Re-enable interrupts
CRTIN1: DCR     C               ;Bump counter
        RZ                      ;Done if at start of buffer
        DCX     H               ;Back up 1 at a time until start of buffer
        MOV     B,M             ;Get current character
        MOV     M,A             ;Put previous in its place
        MOV     A,B             ;Move current to previous
        JMP     CRTIN1          ;Loop until we have char at start of buffer
    else
CRTCHK: IN      CRTSS           ;Get console crt status
        ANI     UDAV            ;Mask out data available bit
        JZ      CRTCHK          ;Wait if not ready
        IN      CRTDATA         ;Get console crt data
        ANI     7Fh             ;Mask out upper bit (parity)
        RET
    endif
;
CRTIN2: DCR     A               ;See if Interfacer I or II, port 0 as CRT
        JNZ     CRTIN3          ;Use device 3 if not
CRTIN2X:IN      IF1US0          ;Interfacer I,II UART 0 status
        ANI     UDAV            ;Strip out console status bits
        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
;
;-------------------------------
;
;       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
;
    if INTRACT
;
; Interrupt driven console output.
SS1CRT: LXI     H,CRTXDIF       ;Point to aggragate difference of load, xmit
        MOV A,M!  CPI CRTOLEN   ;See if room for another character in buffer
        JNC     SS1CRT          ;Loop until transmitter catches up if not
;
        DI                      ;Disable interrupts while we fix pointers
        INR     M               ;Show one character added to buffer
        LXI     H,CRTOCNT       ;Point to load character position counter
        DCR     M               ;Back down one position in buffer
        JP      SS1CRT0         ;Proceed if not past bottom of buffer
        MVI     M,CRTOLEN-1     ;Place pointer/counter at rear of buffer
SS1CRT0:MOV E,M!   MVI D,0      ;Place offset to character in "D,E"
        LXI     H,CRTOBUF
        DAD     D               ;Point to character position in buffer
        MOV     M,C             ;Get character loaded into buffer
        LDA CRTNULL!    ORA A   ;Get null control status byte, see if active
        JZ      SS1CRT2         ;Skip status storage if so
        MOV A,C!  STA CRTNULL   ;Mark null control with current character
SS1CRT2:
      if CRT$DC
        LDA     CRTXCTL         ;See if transmit is software enabled
        CPI     XOFF            ;is in the xmit suspend state
        JZ      SS1CRT3         ;Don't turn on transmitter if so
      endif
        IN      SS1SP1          ;Get slave interrupt mask
        ANI     1011$1111b      ;Enable CRT xmit bit so
        OUT     SS1SP1          ;interrupts are enabled for transmission
SS1CRT3:EI                      ;Enable interrupts again
        RET
    else
CRTOUT1:IN      CRTSS           ;Get console crt status
        XRI     CRTFMSK         ;Flip selected status bits
        ANI     CRTTMSK         ;Mask transmit enable bits
        JNZ     CRTOUT1         ;Wait if not ready
        MOV     A,C             ;Get character to transmit
        OUT     CRTDATA         ;Sent to console crt
        RET
    endif
;
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
        JZ      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
        JZ      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
;
;-------------------------------
;
;       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 = 0FFh (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
;
    if INTRACT
; Interrupt driven console output.
        LDA     CRTXCTL         ;See if output buffering is suspended
        SUI     XOFF            ;Transmission stopped if in "XOFF" mode
        RZ                      ;Show not ready if so
        LDA     CRTXDIF         ;Get the total active characters in the buffer
        SUI     CRTOLEN-1       ;See if all full
        RZ                      ;Show not ready if so
        ORI     0FFh            ;Show ready if any room
        RET
    else
        IN      CRTSS           ;Get console status
        ANI     CRTTMSK         ;Mask transmit enable bits
        XRI     CRTFMSK         ;Flip selected transmit status bits
        DCR     A               ;Make zero into 0FFh if was ready
        RNZ                     ;Return with status if so
        XRA     A               ;Show not ready
        RET
    endif
;
CRTLOS2:DCR     A               ;See if Interfacer I or II, UART 0
        MVI     A,0FFh          ;Use custom routine if not
        RNZ                     ;Show always ready
        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: ANI     IF1TMSK         ;Mask ready bits
        XRI     IF1FMSK         ;Flip TBE bit
        RZ                      ;Done if ready
        ORI     0FFh            ;Show not ready
        RET
;
; U L 1 -- User device list status.
IF3LOS: OUT     IF3UX           ;Select mux in Interfacer 3,4
        IN      IF3US           ;Get status
        ANI     IF3TMSK         ;Mask TBE, DSR bits
        XRI     IF3FMSK         ;Flip status of both
        RZ                      ;If ready
        ORI     0FFh            ;Show not ready
        RET
;
;-------------------------------
;
;       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
;
; 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
;
    if INTRACT
; Master priority interrupt controller vectors.
;
        ORG     ($ AND 0FFE0h) + 32 ;Must be on 32 byte boundary for interrupts
; Interrupt interval of 4 bytes
MASTER:
  PUSH PSW! JMP MASTERI ;VI0 (Slave CPU Service Request)- Restore interrupts
  PUSH PSW! JMP MASTERI ;VI1 (Input Character Ready)    - Restore interrupts
  PUSH PSW! JMP MASTERI ;VI2 (Output Character Ready)   - Restore interrupts
  PUSH PSW! JMP MASTERI ;VI3 (Disk 1 Service Request)   - Restore interrupts
  PUSH PSW! JMP MASTERI ;VI4 (Disk 2, Disk 3, Disk 4)   - Restore interrupts
  PUSH PSW! JMP MASTERI ;VI5 (MPX-1, Character I/O )    - Restore interrupts
  PUSH PSW! JMP MASTERI ;VI6 (MPX-1, Disk I/O Service)  - Restore interrupts
  PUSH PSW! JMP MASTERI ;Slave interrupt (inactive code response)
;
; Slave priority interrupt controller vectors.
;
SLAVE:
  PUSH PSW! JMP SLAVEI  ;VI7 (Systick Master Interval)  - Restore interrupts
  PUSH PSW! JMP SLAVEI  ;Timer 0 (General Time Base)    - Restore interrupts
  PUSH PSW! JMP SLAVEI  ;Timer 1 (Real Time Clock Server)-Restore interrupts
  PUSH PSW! JMP SLAVEI  ;Timer 2 (Job Activity Timeout) - Restore interrupts
  PUSH PSW! JMP SLAVEI  ;Math chip service request      - Restore interrupts
  PUSH PSW! JMP SLAVEI  ;Math chip end of process       - Restore interrupts
  PUSH PSW! JMP CRTXMIT ;Master CRT RS232 transmit byte request
  PUSH PSW              ;Master CRT RS232 recieve byte request
;
        SHLD    HL$HOLD         ;Save incoming "H,L"
        XCHG!   SHLD DE$HOLD    ; and "D,E"
        IN      CRTDATA         ;Get data to reset source of interrupt
        ANI     7FH             ;Strip out parity bit
        STA     CRTICHR         ;Save character in storage
    if CRT$DC                   ;If CRT uses Device Control (XON/XOFF) protocol
        CPI     XON             ;See if restore transmission command
        JZ      CRTQX
        CPI     XOFF            ;See if turn off transmission command
        JNZ     CRT2RD          ;Put character in buffer if neither
        STA     CRTXCTL         ;Save control in storage
        JMP     CRT3RD          ;Finish interrupt
;
; Re-enable output transmission if incoming was "XON".
CRTQX:  STA     CRTXCTL         ;Save control in storage
        LDA     CRTXDIF         ;Get output buffer character count
        ORA     A               ;See if any characters to transmit
        JZ      CRT3RD          ;Skip xmit enable if not, finish interrupt
        IN      SS1SP1          ;Get slave interrupt mask
        ANI     1011$1111b      ;Set CRT xmit bit so we can re-enter
        OUT     SS1SP1          ;when transmit interrupt occurs
        JMP     CRT3RD          ;Finish interrupt
    endif
;
CRT2RD: LXI     H,CRTICNT       ;Get CRT input counter
        MOV     A,M
        CPI     CRTILEN         ;See if at end of buffer
        JNC     CRT3RD          ;Done with interrupt if no room for char
        INR M!  INX H           ;Bump by one for next, point to start of buffer
        ADD L!  MOV L,A         ;Point to current buffer position
        MOV A,H!  ACI 0!  MOV H,A
        LDA     CRTICHR         ;Get character again
        MOV     M,A             ;Put character in buffer
CRT3RD: MVI     A,SEOI+7        ;Specific end of interrupt 7
        JMP     SLV2EOI         ;Finish with interrupt
;
;       Interrupt driven CRT transmission routine.
;
CRTXMIT:SHLD    HL$HOLD         ;Save entering "H,L"
        XCHG!   SHLD DE$HOLD    ; and "D,E"
    if CRT$DSR
CRTWAIT:IN      SS1US           ;Get System Support I uart status
        ANI     SS1DSR          ;See if the Data Set Ready bit is active
        JZ      CRTWAIT         ;Loop until it is (hardware handshake line)
    endif
    if CRT$DC
        LDA     CRTXCTL         ;Get transmission control byte
        CPI     XOFF            ;See if "on hold" status set
        JZ      CRTXOFF         ;Turn off transmission interrupts if so
    endif
        LXI     H,CRTXCNT       ;Point to xmit position counter
        DCR     M               ;Back down one position in buffer
        JP      CRTXNXT         ;Proceed if not past bottom of buffer
        MVI     M,CRTOLEN-1     ;Place pounter/counter at rear of buffer
CRTXNXT:MOV E,M!   MVI D,0      ;Place offset to character in "D,E"
        INX     H               ;Point to aggragate difference of load, xmit
        DCR     M               ;Show one character removed from buffer
                                ;The Zero flag set here is not altered below
CRTX1C: INX H!  DAD D           ;Point to character position in buffer
        MOV     A,M             ;Get character
        OUT     CRTDATA         ;Transmit it
        JNZ     CRTXM3          ;Transmit normally if not the last (zero test)
CRTXOFF:IN      SS1SP1          ;Get slave interrupt mask
        ORI     0100$0000b      ;Set CRT xmit bit so no transmission
        OUT     SS1SP1          ;when interrupts are enabled in this routine
CRTXM3: MVI     A,SEOI+6        ;Specific end of interrupt 6
SLV2EOI:LHLD DE$HOLD!   XCHG    ;Recover entering "D,E"
        LHLD HL$HOLD            ;Recover entering "H,L"
SLVXEOI:OUT     SS1SP0          ;End of interrupt to slave
;
; General Master return from interrupt (only "A" saved, non-specific source).
MASTERI:MVI     A,EOI           ;Non-specific End Of Interrupt
        OUT     SS1MP0          ;To Master PIC
        POP     PSW             ;Recover entering "A", Status
        EI                      ;Enable interrupts
        RET                     ;After return
;
; General Slave return from interrupt (only "A" saved, non-specific source).
SLAVEI: MVI     A,EOI           ;Non-specific End Of Interrupt
        JMP     SLVXEOI         ;To Slave PIC
    endif
;-------------------------------
;
;       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
;
;-------------------------------
;

;************************************************
;*      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
;
    if INTRACT
;
HL$HOLD:DW      0       ;Storage for entering "H,L" registers during interrupts
DE$HOLD:DW      0       ;Storage for entering "D,E" registers during interrupts
;
; CRT console input buffering control
CRTICHR:DB      0       ;Temp storage for input character
CRTICNT:DB      0       ;Initially no chars in buffer
CRTIBUF:DS      CRTILEN ;Leave room for fair amount of type ahead
;
; CRT console output buffering control
CRTNULL DB      0FFh    ;Null character output buffering control
CRTXCTL DB      XON     ;CRT output control initially on
CRTOCNT DB  CRTOLEN - 1 ;Output load counter/pointer init at rear of buffer
CRTXCNT DB  CRTOLEN - 1 ;Output xmitted character pointer, init at rear
CRTXDIF DB      0       ;Output buffer character difference counter
CRTOBUF DS      CRTOLEN ;Large enough for 1 line with some controls
    endif

;****************************************
;*      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
; 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.
;
        CALL    LOADCPM         ;Boot CP/M from floppy or hard disk
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
;
        BOOTCPM         ;Boot CP/M from disk, exit only if load sucessful.
;
REUSE:  SET     $       ;Start of re-usable space
;************************************************
;*      COLD BOOT INITIALIZATION ROUTINES       *
;************************************************
;
CBOOT:  LXI     SP,100h         ;Temp stack for cold boot
;
;       All runtime code is now ready, so set up interrupt controllers and
; initialize remaining UARTS and other devices not done by the boot loader,
; "HMXxBOOT.COM" or possibly in the BIOS.
;
IOINIT: LXI     H,INISEQ        ;Point to initialization sequence string
IOINIT1:MOV A,M!  INX H         ;Get the port to xmit to, point to next value
        STA     INIPORT+1       ;Store in code sequence for transmission
        INR     A               ;See if highest address port
        JZ      INITCPU         ;Do CPU specific initialization if so
        MOV A,M!  INX H         ;Get value to xmit in "A", point to next value
INIPORT:OUT     0               ;Send data to correct port
        JMP     IOINIT1         ;Loop until all ports initialized
;
INITCPU:                        ;Initialize CPU specific interrupt controls
    if INTRACT                  ;if they are to be active
; Mask internal interrupts (5.5, 6.5, 7.5) if 8085.
      if I8085
        LXI     D,0FBC9h        ;EI, RET opcodes
        LXI     H,(4*2+1)*4     ;TRAP (RST 4.5)
        MOV     M,D             ;Put in enable interrupt opcode
        INX     H
        MOV     M,E             ;And return opcode
          if (not M8085) and 001b
        LXI     H,(5*2+1)*4     ;RST 5.5
        MOV     M,D             ;Put in enable interrupt opcode
        INX     H
        MOV     M,E             ;And return opcode
          endif
          if (not M8085) and 010b
        LXI     H,(6*2+1)*4     ;RST 6.5
        MOV     M,D             ;Put in enable interrupt opcode
        INX     H
        MOV     M,E             ;And return opcode
          endif
          if (not M8085) and 100b
        LXI     H,(7*2+1)*4     ;RST 7.5
        MOV     M,D             ;Put in enable interrupt opcode
        INX     H
        MOV     M,E             ;And return opcode
          endif
        MVI     A,M8085         ;Get mask for these internal interrupts
        DB      30h             ;(SIM, set interrupt mask opcode)
      endif
        EI                      ;Turn on interrupts in processor
        NOP                     ;Process any while our stack is active
    endif
;
; Print the corrected sign-on message to the default console.
        LXI     H,SIGNONA       ;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
    endif
    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
;
; Load up the sector relocation tables for the hard disk systems.
;
;
;       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   0
        LXI     H,LDRCODE       ;Show position of externally loaded code
        SHLD    LOADVEC         ;as cold boot vector (cold boot used only once)
        JMP     CCP             ;Go to CP/M and execute anything in CCP buffer
;
;----- cold boot subroutines -----
;
; 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 dma address to 0
        MVI M,D3$NOOP ! INX H   ;First command is NOOP, point to status
        MOV M,A ! INX H         ;Clear status byte, point to drive
        MOV M,A ! DCX H         ;Set drive to 0, point back to status
        MVI     A,D3RESET       ;Reset DISK3 controller (Select controller #1)
        OUT     D3$PORT
        MVI     A,D3$ATTN
        OUT     D3$PORT         ;Send attention to controller
;
        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 controller.',CR,LF,NULL
    endif
;
;****************************************************************
;*      INPUT/OUTPUT DEVICE INITIALIZATION SEQUENCE TABLE       *
;****************************************************************
INISEQ: ;Port,  Value to transmit sequence until Port = 0FFh.
;
; Interfacer 3,4 UART and interrupt initialization.
    if INTERFACER3
   DB   IF3UX,  0       ;Select Uart 0
   DB   IF3UM,11101110b ;Async, 16x, 8 bits, no parity, 2 stop
   DB   IF3UM,01111110b ; 9600 baud
   DB   IF3UC,00100111b ;Trans. on, DTR low, rec. on, no break/ reset, RTS low
;
   DB   IF3UX,  1       ;Select Uart 1
   DB   IF3UM,11101110b ;Async, 16x, 8 bits, no parity, 2 stop
   DB   IF3UM,01111110b ; 9600 baud
   DB   IF3UC,00100111b ;Trans. on, DTR low, rec. on, no break/ reset, RTS low
;
   DB   IF3UX,  2       ;Select Uart 2
   DB   IF3UM,11101110b ;Async, 16x, 8 bits, no parity, 2 stop
   DB   IF3UM,01111110b ;9600 baud
   DB   IF3UC,00100111b ;Trans. on, DTR low, rec. on, no break/ reset, RTS low
;
   DB   IF3UX,  3       ;Select Uart 3
   DB   IF3UM,11101110b ;Async, 16x, 8 bits, no parity, 2 stop
   DB   IF3UM,01111110b ; 9600 baud
   DB   IF3UC,00100111b ;Trans. on, DTR low, rec. on, no break/ reset, RTS low
    endif
;
    if INTERFACER3 or INTERFACER4
   DB   IF3UX,  4       ;Select Uart 4
   DB   IF3UM,11101110b ;Async, 16x, 8 bits, no parity, 2 stop
   DB   IF3UM,01111110b ; 9600 baud
   DB   IF3UC,00100111b ;Trans. on, DTR low, rec. on, no break/ reset, RTS low
;
   DB   IF3UX,  5       ;Select Uart 5
   DB   IF3UM,11101110b ;Async, 16x, 8 bits, no parity, 2 stop
   DB   IF3UM,01111110b ; 9600 baud
   DB   IF3UC,00100111b ;Trans. on, DTR low, rec. on, no break/ reset, RTS low
;
   DB   IF3UX,  6       ;Select Uart 6
   DB   IF3UM,01001010b ;Async, 16x, 8 bits,  no parity, 1 stop
   DB   IF3UM,01110111b ; 1200 baud
   DB   IF3UC,00100111b ;Trans. on, DTR low, rec. on, no break/ reset, RTS low
;
   DB   IF3UX,  7       ;Select Uart 7
   DB   IF3UM,11101110b ;Async, 16x, 8 bits, no parity, 2 stop
   DB   IF3UM,01111110b ; 9600 baud
   DB   IF3UC,00100111b ;Trans. on, DTR low, rec. on, no break/ reset, RTS low
;
   DB   IF3IR,00000000b ;Interrupt on data received control port (disabled)
   DB   IF3IT,00000000b ;Interrupt on xmit ready control port (disabled)
    endif
;
    if (INTERFACER3 and INTERFACER4)
   DB   IF3UX,  8+0     ;Select Uart 0 (USER 8)
   DB   IF3UM,11101110b ;Async, 16x, 8 bits, no parity, 2 stop
   DB   IF3UM,01111110b ; 9600 baud
   DB   IF3UC,00100111b ;Trans. on, DTR low, rec. on, no break/ reset, RTS low
;
   DB   IF3UX,  8+1     ;Select Uart 1 (USER 9)
   DB   IF3UM,11101110b ;Async, 16x, 8 bits, no parity, 2 stop
   DB   IF3UM,01110101b ;  300 baud
   DB   IF3UC,00100111b ;Trans. on, DTR low, rec. on, no break/ reset, RTS low
;
   DB   IF3UX,  8+2     ;Select Uart 2 (USER 10)
   DB   IF3UM,11101110b ;Async, 16x, 8 bits, no parity, 2 stop
   DB   IF3UM,01111110b ; 9600 baud
   DB   IF3UC,00100111b ;Trans. on, DTR low, rec. on, no break/ reset, RTS low
;
   DB   IF3UX,  8+3     ;Select Uart 3 (USER 11)
   DB   IF3UM,01011110b ;Async, 16x, 8 bits, no parity, 2 stop
   DB   IF3UM,01111110b ; 9600 baud
   DB   IF3UC,00100111b ;Trans. on, DTR low, rec. on, no break/ reset, RTS low
;
   DB   IF3IR,00000000b ;Interrupt on data received control port (disabled)
   DB   IF3IT,00000000b ;Interrupt on xmit ready control port (disabled)
    endif
;
; System Support I UART initialization.
    if SYSUP1
   DB   SS1UM,11101110b ;Async, 16x, 8 bits, no parity, 2 stop
   DB   SS1UM,01111110b ;9600 baud
   DB   SS1UC,00100111b ;Xmit on, DTR low, rec. on, no break, run, RTS low
;
; System Support I timer initialization.
   DB   SS1TC,00$11$011$0b      ;Timer 0, 16 bit load, square wave, binary
   DB   SS1T0,low TIMEBASE      ;Get divisor for time base for other 2 timers
   DB   SS1T0,high TIMEBASE     ;Set up timer 0
;
   DB   SS1TC,01$11$000$0b      ;Timer 1, 16 bit, interrupt on count, binary
   DB   SS1T1,low TIME1         ;Get divisor for time base for other 2 timers
   DB   SS1T1,high TIME1        ;Set up timer 1 (Real Time Clock tick)
;
   DB   SS1TC,10$11$000$0b      ;Timer 2, 16 bit, interrupt on count, binary
   DB   SS1T2,low TIME2         ;Set interrupt interval in units of time base
   DB   SS1T2,high TIME2
;
; Initialize Slave 8259A Interrupt Controller.
   DB   SS1SP0,(low SLAVE and 111$00000b) + 000$11101b  ;Make sure only 3
   ; address bits used, level trigger, 4 byte inverval, cascade mode    -- ICW1
   DB   SS1SP1,high SLAVE       ;High address of Slave interrupt base   -- ICW2
   DB   SS1SP1,00000$111b       ;Slave ID is interrupt 7                -- ICW3
   DB   SS1SP1,000$00000b       ;Non-buffered, normal EOI, 8085         -- ICW4
   DB   SS1SP1,SMASK            ;Slave PIC initial Active Interrupt Mask - OCW1
   DB   SS1SP0,EOI              ;Clear controller of pending interrupts -- OCW2
   DB   SS1SP0,SETPRI+6         ;Set CRT transmit to lowest priority    -- OCW2
   DB   SS1SP0,READISR          ;Set to read interrupt service register -- OCW3
   DB   SS1SP0,SMMOFF           ;Special mask mode off                  -- OCW3
;
; Initialize Master 8259A Interrupt Controller.
   DB   SS1MP0,(low MASTER and 111$00000b) + 000$11101b ;Make sure only 3
   ; address bits used, level trigger, 4 byte inverval, cascade mode    -- ICW1
   DB   SS1MP1,high MASTER      ;High address of Master interrupt base  -- ICW2
   DB   SS1MP1,1000$0000b       ;Only interrupt 7 is a slave            -- ICW3
   DB   SS1MP1,000$10000b       ;Full nest, non-buffered, norm EOI, 8085 - ICW4
   DB   SS1MP1,MMASK            ;Master initial Active Interrupt Mask   -- OCW1
   DB   SS1MP0,EOI              ;Clear controller of pending interrupts -- OCW2
   DB   SS1MP0,READISR          ;Set to read interrupt service register -- OCW3
   DB   SS1MP0,SMMOFF           ;Special mask mode off                  -- OCW3
    endif
   DB   0FFh            ;End of I/O port initialization string
;
; Sign on message -- must be terminated by a "NULL" byte.
SIGNONA:DB      LF,LF
SIGNON: DB      'CompuPro '
        DB      MSIZE/10+'0',MSIZE mod 10 + '0'
        DB      'K CP/M '
        DB      VERS/10+'0','.',VERS mod 10 + '0',CBIOSV,' ('
    if I8085
        DB      '85'    ;Show 8085 code active
    endif
    if I8085 and I8088
        DB      '/'
    endif
    if I8088
        DB      '88'    ;Show 8088 code active
    endif
    if Z80
        DB      'Z80'   ;Show Z80 code active
    endif
        DB      ')',CR,LF,'Copyright (c) 1985 VIASYN Corporation',CR,LF,LF
    if DISK3
        DB      'Disk3 with '        ;Hard disk (Disk 3) separator ID
      if D3ST506
        DB      'M5 as A:'      ;Show Hard Disk with  5 Mbytes included
      endif
      if D3M3425
        DB      'Miniscribe M3425 20 meg hard drive as A: B: C:'      
      endif
        DB      CR,LF,LF
    endif
    if not DISK3
      if not (FLOPPY5 and BOOT5X)
        DB      '8" DISK1'
        if DISK1A
        DB      'A'
        endif
        DB      ' as A: B:'
        if FPY8X4
        DB      ' C: D:'
        endif
        DB      CR,LF
        if FLOPPY5
        DB      '5" DISK1'
          if DISK1A
        DB      'A'
          endif
        DB      ' as E: F:'
          if FPY5X4
        DB      ' G: H:'
          endif
        DB      CR,LF
        endif
      else
        DB      '5" DISK1'
        if DISK1A
        DB      'A'
        endif
        DB      ' as A: B:'
        if (DISK1 and FPY5X4)
        DB      ' C: D:'
        endif
        DB      CR,LF
        if FLOPPY8
        DB      '8" DISK1'
          if DISK1A
        DB      'A'
          endif
        DB      ' as E: F:'
          if (DISK1 and FPY8X4)
        DB      ' G: H:'
          endif
        DB      CR,LF
        endif
      endif
    else
      if FLOPPY8
        DB      '8" DISK1'
        if DISK1A
        DB      'A'
        endif
        DB      ' as '
        if ((DRVN ne 0) and (DRVO ne 0))
        DB      'N: O:'
        else
        DB      'I: J:'
          if (DISK1 and FPY8X4)
        DB      'K: L:'
          endif
        endif
        DB      CR,LF
      endif
      if FLOPPY5
        DB      '5" DISK1'
        if DISK1A
        DB      'A'
        endif
        DB      ' as '
        if ((DRVN ne 0) and (DRVO ne 0))
        DB      'N: O:'
        else
        DB      'K: L:'
        endif
        DB      CR,LF
      endif
    endif
        DB      LF
    if INTRACT
        DB      'Interrupts active'
    endif
;
        DB      CR,LF,NULL      ;End of sign-on message
;
ENDCODE EQU  $  ;End of active code
;--------------------------------
;********************************
;*      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
;
;-------------------------------
ENDWORK EQU     $       ;End of workspace below block buffers
;-------------------------------
; Place directory, host buffer near final block of RAM.
        ORG  0 - (HSTSIZ + 256) ;Fix on   boundary
;
LDRCODE EQU   $-XLOADZ  ;Base address of externally loaded additional code to
                        ;add to the BIOS.  Placed here to attain invariance
                        ;if the associated BIOS is altered in size.
;
    if ENDWORK gt LDRCODE ;Overflow of storage space requirement
--:ERROR:--     Assembly time STORAGE SPACE SHORTAGE
NEED$SPACE EQU  (ENDWORK - LDRCODE)
    else
SPARE$RAM EQU   (LDRCODE - 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               ;Initial Specification Parameters
        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
