;============================================================
;               BANKED & TRACK BUFFERING BIOS
;          for the Cromemco Z-1, System III, etc.
;============================================================
;Supporting the following hardware:
;  Cromemco ZPU Z80 CPU
;  Cromemco 16FDC floppy disk controller
;  Cromemco 64FDC floppy disk controller
;  Cromemco 64KZ 64K DRAM
;  Cromemco 64KZ-II 64K DRAM
;  Cromemco 16KZ 16K DRAM (as track buffer)
;  Cromemco TUART serial/parallel board
;  Cromemco PRI printer interface (dot matrix printer only)
;  Generic serial terminal
;  Wyse WY-30 and WY-50 terminal
;
;This BIOS detects and self-configures to support any
;combination of up to 4 drives of the following types:
;(BPS = Bytes Per Sector, SPT = Sectors Per Track.)
;
; 8" disks, stepper or voicecoil actuator:
;   (Voicecoil support if 16FDC and PERSCI=TRUE)
; 360 RPM
; 1- or 2-sided
;   Single-Density    Double-Density
;   250 bits/sec      500 bits/sec
;   BPS SPT Tracks    BPS SPT Tracks
;   128  26   77      256  26   77  
;   256  25   77      512  15   77
;   512   8   77     1024   8   77 
;
; 5.25" 'minidisks'
; 300 RPM
; 1- or 2-sided
;                                         Quad-Density
;   Single-Density     Double-Density     (if QDHD=TRUE)
;   125 bits/sec       250 bits/sec       250 bits/sec
;   BPS SPT Tracks     BPS SPT Tracks     BPS SPT Tracks
;   128  18   40       256  18   40       256  18   80
;   256  10   40       512  10   40       512  10   80
;   512   5   40      1024   5   40      1024   5   80
;
; 5.25"'High-Density" (HD)
; (if QDHD=TRUE)
; 360 RPM
; Double-density, 2-sided only
; 500 bits/sec
;   BPS SPT Tracks
;   256  26   80
;   512  15   80
;  1024   8   80
;
; 3.5" (PC standard)
; 300 RPM
; Double-density, 2-sided only
; 500 bits/sec
;   BPS SPT Tracks
;   256  31   80
;   512  18   80
;  1024  10   80
;
;See the "Assembly Options" section below to select hardware.
;
;This BIOS uses Z-80 instructions, meaning it will not work
;with an 8080 CPU. It is written to assemble with Digital
;Research's ASM. Z-80 instructions are generated the hard
;way, using DS and DW pseudo-operations. The relative branch
;instructions need to be checked manually after assembly:
;the relative address must be less than 80h for forward
;branches, and more than 7Fh for backward branches.
;
;This BIOS can read and write single-density disks, but does
;not fit on a single-density disk's system tracks.  (The
;standard number of system tracks is 2 on an 8" disk and 3 on
;a minidisk (except HD disks). Using a non-standard number of
;system tracks would make reading disks from other machines
;impossible. However, a single-density disks may have all its
;system tracks except track 0 formatted as double-density.
;Note, however, that PerSci 277 drives cannot read double-
;density even near the outside diameter (i.e. track 1).
;
;This BIOS cannot read or write double-density 500 kbits/sec
;disks (8", HD 5.25", 3.5") if the CPU is set for 2 MHz.
;A warning is generated during initial boot if a 2 MHz CPU
;is detected.
;
;Some versions of Cromemco's RDOS-II will only boot from
;fast-seeking (PerSci) drives if the controller is set up for
;the higher data rate (DCMAXI=1) so these version of RDOS-II
;cannot boot from 3.5" drives or from HD 5.25" drives.
;However, these versions of RDOS-II can boot from 40-track
;minidisk drives from and 80-track Quad-Density disk drives.
;============================================================
;---------------------------
;This BIOS'S revision number
;---------------------------
BIOSVER	equ	0113h	;high byte = major revision number
			;low byte=2-digit minor version
;----------------
;Revision History
;----------------
; 1.13 12 November 2021  M. Eberhard
;   Speed up sidedness detection for 1-sided disks in drives
;   that respond to SIDESEL. Tidy up comments a bit.
; 1.12 21 October 2021  M. Eberhard
;   Only ID a drive the first time it's seen since boot.
;   This means a separate DPB per drive,  and remembering
;   which disks have been seen since boot. Fix SECTRAN so
;   that it does not need to know which drive. Make mini-
;   disk sector interleaving optional. Split QDHD into
;   two options: QDRIVE and HDRIVE. Lots of squeezing so
;   that all drive types can be supported simultaneously,
;   so long as VERBOS=FALSE. This version is pretty close
;   to my CCS MBIOS version 5.22.
; 1.11 17 October 2021  M. Eberhard
;    Fix cold boot/warm boot logic. Squeeze code a bit.
; 1.10 13 October 2021  M. Eberhard
;   Restore BUFDSK on error in SELDSK. Report CPU speed in
;   initial boot. Fix PRI output bug.  Add VERBOS option.
;   Squeeze code a bit.
; 1.09 26 May 2021  M. Eberhard
;   FIRST RELEASED VERSION
;   Add support for 64FDC (mostly just comments changed)
; 1.08 9 May 2021  M. Eberhard
;   Rename to be CroBIOS. Delay after selecting, for PerSci
;   299 drives. Improve comments.
; 1.07 7 May 2021  M. Eberhard
;   Add QDHD option to allow removal of support for the QD
;   and HD 5.25" disks, and therefore the removal of the
;   last-track test during initial SELDSK.
; 1.06 6 May 2021  M. Eberhard
;   Look for last track to determine number of tracks, when
;   figuring out drive type in DRIVID. Remove MODRDR option.
;   More comment cleanup.
; 1.05 6 May 2021  M. Eberhard
;   Tidy up some comments. Tighten up a few routines, rename
;   subroutine RESTOR to be DRIVID and SEEK to be SEEKID.
;   Only seek past system tracks in DRIVID when necessary.
; 1.04 5 May 2021  M. Eberhard
;   Always transfer disk data to/from CP/M via SECBUF, since
;   the track buffer bank overlays the entire 1st 32K of RAM.
;   Only do 2:1 interleave for track writes for minidisks.
; 1.03 4 May 2021  M. Eberhard
;   Remove CPU-speed dependency on trim erase delay, to save
;   code space, since additional delay doesn't really matter.
;   Read/write tracks with a 2:1 interleave, so that crappy
;   5.25" drives that have 800 uS internal trim-erase times
;   won't slip a rev for every sector written. (I hate this.)
; 1.02 3 May 2021  M. Eberhard
;   Determine CPU speed by timing the Index pulse, and use
;   this to adjust the trim-erase delay. Tighten up read
;   and write sector loops using INI and OUTI instructions.
; 1.01 30 April 2021  M. Eberhard
;   Mask 16FDC interrupts in WBOOT. Don't look at 80-track
;   DIP switch if voicecoil drive detected. Roll in bug fix
;   CCS MBIOS 5.13. Leave motors on when deselecting drives.
;   MAke RPM measurement auto-detect 2/4 MHz CPU
; 1.00 12 April 2021  M. Eberhard
;   Ported from CCS MBIOS rev 5.12
;============================================================
;Switch and Jumper Settings for System Boards
;
;The memory board settings assign bank 0 to main memory,
;and bank 1 to the buffer memory. (Banks 6 & 7 are currently
;reserved for the ByteSaver.)
;
;---------------------
;Cromemco ZPU Z-80 CPU
;---------------------
; 4-position DIP switch
;     Position   Setting
;        1         OFF
;        2         OFF
;        3         ON
;        4         ON
; CPU speed Toggle-Switch position:
;    Right (4 MHz position)
;
; Wait State Jumpers (factory default):
;    M1-0
;    0-W
; JUMP ENABLE Jumper:
;    connected (factory default with PCB trace)
; Address Mirror Jumper:
;    AM-ON (factory default with PCB trace)
; Refresh Enable ( RFSH ENAB) Jumper:
;    not connected (factory default)
;
;-------------------------------------
;Cromemco 16FDC Floppy Disk Controller
;-------------------------------------
;  DIP Switch
;  Position           Setting
;     1                 OFF
;     2                 ON
;     3     ON=auto-boot, OFF=RDOS at reset
;     4                 OFF
;     5                 OFF
;     6                 OFF
;     7                 OFF
;     8                 OFF
;
;-------------------------------------
;Cromemco 64FDC Floppy Disk Controller
;-------------------------------------
;   Jumper            Setting
;     1               No Jumper                  
;     2               Jumpered
;     3     ON=auto-boot, OFF=RDOS at reset
;     4               No Jumper
;  DIP Switch
;  Position           Setting
;     1                 OFF
;     2                 OFF
;     3                 OFF
;     4                 OFF
;     5                 OFF
;
;----------------------
;Cromemco 64KZ 64K DRAM
;----------------------
; SW1 (Address/Control)
;         MAIN MEMORY             BUFFER MEMORY
;     Position     Setting     Position     Setting
;        1           OFF          1           OFF
;        2           ON           2           ON
;        3           ON           3           OFF
;        4           OFF          4           OFF
;        5           ON           5           ON
;        6           OFF          6           OFF
;        7           ON           7           ON
;        8           OFF          8           OFF
; SW2 (Bank Select A)
;         MAIN MEMORY             BUFFER MEMORY
;     Position     Setting     Position     Setting
;        1           ON           1           OFF
;        2           OFF          2           On
;        3           ON           3           OFF
;        4           ON           4           OFF
;        5           ON           5           OFF
;        6           ON           6           OFF
;        7           ON           7           OFF
;        8           ON           8           OFF
; SW3 (Bank Select B)
;         MAIN MEMORY             BUFFER MEMORY
;     Position     Setting     Position     Setting
;        1           ON           1           OFF
;        2           ON           2           OFF
;        3           ON           3           OFF
;        4           ON           4           OFF
;        5           ON           5           OFF
;        6           ON           6           OFF
;        7           OFF          7           OFF
;        8           OFF          8           OFF
; MEMDSBL Jumper:
;   connected (factory default with PCB trace)
;
;-------------------------
;Cromemco 64KZ-II 64K DRAM
;-------------------------
; SW1 (Bank Select A)
;         MAIN MEMORY             BUFFER MEMORY
;     Position     Setting     Position     Setting
;        1           ON           1           OFF
;        2           OFF          2           ON
;        3           ON           3           OFF
;        4           ON           4           OFF
;        5           ON           5           OFF
;        6           ON           6           OFF
;        7           ON           7           OFF
;        8           ON           8           OFF
; SW2 (Bank Select B)
;         MAIN MEMORY             BUFFER MEMORY
;     Position     Setting     Position     Setting
;        1           ON           1           OFF
;        2           ON           2           OFF
;        3           ON           3           OFF
;        4           ON           4           OFF
;        5           ON           5           OFF
;        6           ON           6           OFF
;        7           OFF          7           OFF
;        8           OFF          8           OFF
; SW3 (Address/Control)
;         MAIN MEMORY             BUFFER MEMORY
;     Position     Setting     Position     Setting
;        1           OFF          1           OFF
;        2           OFF          2           OFF
;        3           ON           3           OFF
;        4           OFF          4           OFF
;        5           OFF          5           OFF
;        6           OFF          6           OFF
;        7           ON           7           ON
;        8           OFF          8           OFF
; IC 7 (Custom Bank Select Address PROM)
;   Factory default (jumpers with PCB traces):
;     No PROM installed
;     Jumpers
;       1-16
;       2-15
;       3-14
;       4-13
;       5-12
;       6-11
;       7-10
;       8-9
;
;-----------------------------------------
;Cromemco 16KZ 16K DRAM (as buffer memory)
;-----------------------------------------
; 4-position DIP switch
;     Position   Setting
;        1         OFF
;        2         OFF
;        3         OFF
;        4         OFF
; 8-position DIP switch
;     Position   Setting
;        1         OFF
;        2         OFF
;        3         OFF
;        4         OFF
;        5         OFF
;        6         OFF
;        7         ON
;        8         OFF
;
;------------------------------------
;Cromemco TU-ART 2 serial, 2 parallel
;------------------------------------
;  DIP Switch
;  Position      Setting
;     1            OFF
;     2            OFF
;     3            ON
;     4            OFF
;     5            ON
;     6            ON
;     7            ON
;     8            ON
;     9            ON
;    10            OFF
;============================================================
;NOTES
;
;Most 3.5" drives are hardwired to be drive B, so the drive
;select signal from the 16FDC or 64FDC must be routed to the
;disk drive's "Drive Select 2" pin with an adapter.
;
;Fast-seeking voicecoil drives, such as those made by PerSci,
;are automatically detected and supported when PERSCI=TRUE.
;You can have a combination of normal (Shugart-type) drives
;and fast-seeking drives in the same interface with an
;appropriate interface adapter (documented elsewhere).
;
;The 16FDC and 64FDC do not connect the 2-Sided signal from
;the 50-pin disk drive connector to anything. The only way to
;determine whether a disk is 1-sided or two-sided is to try
;reading from side 1. If the drive itself is a 1-sided drive
;(such as an SA800 or SA400L), then side 0 will be read. But
;if it is a 1-sided disk in a 2-sided drive, then the drive
;may respond by deasserting its Ready signal or possibly by
;reading garbage from the unformatted side of a 1-sided disk.
;This process is inherently slow because the WD1793 chip will
;retry for 6 disk revolutions on a read failure. For this
;reason, the first time reading a 1-sided disk after a cold
;or warm boot will be pretty slow, more than a second.
;(subsequent accesses will be fast.)
;
;The 16FDC and 64FDC also do not connect the Ready signal
;from the 34-pin drive connector to anything. The WD1793's
;Ready input is true if the 50-pin interface's Ready signal
;is true, or if the 16FDC/64FDC is set up for a minidisk
;(MAXI=FALSE). This means that the 34-pin interface can only
;be used for minidisks and Quad Density disks (125K bits/sec
;single-density, 250K bits/sec double density).
;
;Therefore, High Density 5.25" drives and 3.5" drives (which
;transfer data at the Maxi data rates) must be connected via
;an adapter to the 50-pin connector, and not to the 34-pin
;connector. If such a drive does not generate a Ready signal,
;then the adapter must also generate this signal.
;
;On 8", 3.5", and HD 5.25" disks, tracks 0 and 1 are system
;tracks. On minidisk and QD 5.25" disks (MAXI=FALSE), tracks
;0 through 2 are system tracks. System tracks may be
;formatted differently than the rest of the disk, as noted
;above. Track 0 is always formatted as 128 BPS, single
;density, while the other system tracks are 256 BPS,
;double-density. Side 1 of the system tracks is not used by
;CP/M or for the boot image. Note that the PerSci 270 and 277
;single-sided drives cannot reliably read a double-density
;track, so such drives cannot be drive A. (Shugart SA800
;drives, for example, have no trouble reading double-density
;tracks near the disk's outside diameter.)
;
;Disk Identification Strategy
;
;PRMTBL contains drive- and disk-unique parameters for each
;of the supported disk drives. All entries are initialized 
;for 5.25" disks, with an "uninitialized"  bit set to
;indicate that the drive has not yet been seen since CP/M
;was loaded from disk, and another to indicate that the
;drive has not been seen since the last WBOOT or CBOOT.
;(The kind of drive - 3.5", 5.25", 8" etc - won't change,
;but the kind if disk - density, sidedness, bytes/sector -
;may change between calls to WBOOT.)
;
;Detecting the *drive* type:
;
;When a drive is seen for the first time by a call to SELDSK,
;the new drive bit is cleared for that drive, and the index 
;pulse rate is measured to determine whether it is a 300 RPM
;or a 360 RPM disk. If a 300 RPM drive is detected, then
;track 3 (past the system tracks) is sampled to determine
;the data rate. If a 500 BPS DD track is detected, then the
;drive is assumed to be a 3.5" drive; otherwise it is either
;a 40-track 5.25" drive or an 80-track "quad-density" 5.25"
;drive. Finally, when necessary, the last track is tested to
;distinguish between a 40-track 5.25" drive and an 80-track
;quad-density 5.25" drive, and also between a 77-track 8"
;drive and an 80-track "high density" 5.25"drive. This
;information allows the BIOS to determine the drive and disk
;type from the above list.
;
;If the drive is an 8" drive and PERSCI is TRUE, then DRIVID
;will also check to see if it is a fast-seeking (voicecoil)
;drive by looking at the SEEK COMPLETE signal from the drive.
;
;Detecting the *disk* type:
;
;Disk density, sidedness and bytes/sector (BPS) are updated
;by SELDSK the first time a drive is specified since WBOOT
;or CBOOT, because the user may have changed the disk, and
;the new disk may have different sidedness, density, and/or
;BPS than the previous disk.
;
;40-traCK 5.25" and 8" drives are assumed to be slow-stepping
;until a 2-sided disk is seen in the drive - at which point
;the step rate for that drive in PRMTBL is (permanently)
;updated to the faster 2-sided drive drive's step rate.
;
;The drive type, density, BPS, sidedness, and seek-type
;information is used to fill in the PRMTBL entry, and to
;generate dynamically the Disk Prameter Block (DPB) for the
;selected drive. Some of these are determined by DRIVID,
;others (density, sector size) are determined later during
;SELDSK (because the disk itself may have been changed).
;
;The drive identification takes a fair bit of code.
;However, this code only executes once for each drive, the
;first time that drive is seleced after CP/M cold boot.
;
;Track Buffer Strategy
;
;This BIOS only transfers data to and from the disk one whole
;track at a time, via a 10K-byte track buffer: big enough for
;the largest track possible (on an 3.5" disk). Calls to READ
;and WRITE only transfer data from/to the calling source
;to/from the track buffer.
;
;The BANKED assembly option puts the track buffer in RAM bank
;1, while RAM bank 0 is the main memory. READ and WRITE calls
;transfer data through an intermediate 128-byte buffer that 
;is located in main memory, in a segment that is still
;enabled when RAM bank 1 is selected.
;
;The track buffer is loaded from the disk on any call to
;READ or WRITE (after the buffer is flushed), if the track
;in the buffer is different (disk, track, side) than the one
;specified by the most recent calls to SELDSK, SETTRK and
;SETSEC or if the buffer is marked as invalid (TBSTAT=0).
;
;The track buffer is flushed (via a call to BFLUSH) under the
;following circumstances:
; 1) Any call to READ or WRITE referencing a different track
;    than the one in the buffer
; 2) Any call to CONIN
; 3) Any call to SELDSK that selects a drive that has never
;    been accessed since the last boot.
;
;A flush causes the track buffer's contents to be written to
;the disk only if it is marked as dirty (TBSTAT=0FFh). The
;TBSTAT flag is set to 0FFh on every call to WRITE, and
;cleared whenever the buffer is flushed.
;
;When the buffer is filled from disk, TBSTAT is set to 1 to
;indicate valid data.
;
;For maximum speed, disk track transfers to and from disk
;start with the next sector after the one found by the call
;to IDREAD within SEEKID.
;
;All of the BIOS's variables (including all buffering tables
;and variables) are kept in the BIOS's RAM, so that they are
;retained even if an application program wipes out page-zero
;RAM. (Warm and cold boot entry to the BIOS restore CP/M's
;page-zero data and jump tables.)
;
;RAM Banks
;
;After reset, the RDOS-II EPROM (which is on the Cromemco
;disk controller, and contains the boot loader as well as a
;monitor program) occupies 4K of the memory space between
;addresses C000h and CFFFh. This ROM is disabled (and DRAM is
;enabled) upon the first write to the bank select port (port
;40h). Since this CP/M image is entirely above CFFFh, the
;boot loader is not required to disable the RDOS-II ROM
;before it loads CP/M. However, this BIOS does disable
;RDOS-II during its initialization.
;
;If the BANKED option is true, then this BIOS uses the bank
;select system to locate the 10K track buffer out of main
;memory, in its own private memory bank. Cromemco supports 8
;banks of memory, selected by writing to port 40h, where
;each bit of the written byte enables one of the banks. Bank
;0 (selected by writing 01h to port 40h) is the main memory,
;and bank 1 (selected by writing 02h to port 40h) is the
;track buffer bank.
;
;For the BANKED option, Cromemco RAM boards (both main and
;the track buffer memory) should be set up such that only the
;32K of RAM between addresses 0000 and 7FFFh is affected by
;the bank select register. For the rest of the memory space,
;the RAM on the main memory board should be enabled. The 10K
;track buffer is located at address 1000h. The remaining
;memory in the track buffer bank is unused. This implies that
;the track buffer bank requires only a 16 K-byte memory board
;(such as a 16KZ), though you can use a larger memory board,
;if set up appropriately. (See above.)
;============================================================
;Assembly Options
;============================================================
FALSE	equ	0
TRUE	equ	not FALSE
;-------------------------------------------------
;Memory Size
;Specify the total amount of system RAM, in Kbytes
;-------------------------------------------------
RAMSIZ	equ	64
;-----------------------------------------------------
;Option to put the track buffer in its own memory bank
;(See above for RAM board configuration.)
;-----------------------------------------------------
BANKED	equ	TRUE
;---------------------------------
;Option to support 3.5" drives
;DRIV35=FALSE saves 74 (4Ah) bytes
;---------------------------------
DRIV35	equ	TRUE
;------------------------------------------------
;Option to support 80-track 'High Density' drives
;(on the 50-pin interface)
;HDRIVE=FALSE saves 35 (23h) bytes
;------------------------------------------------
HDRIVE	equ	TRUE
;------------------------------------------------
;Option to support 80-track 'Quad Density' drives
;(on the 34-pin interface)
;QDRIVE=FALSE saves 32 (20h) bytes
;------------------------------------------------
QDRIVE equ	TRUE
;------------------------------------------------------
;Option to support PerSci (voicecoil) drives
;(Setting PERSCI=FALSE just saves a little code space.)
;Note: The 64FDC does not support PerSci drives.
;PERSCI=FALSE saves 33 (21h) bytes
;------------------------------------------------------
PERSCI	equ	TRUE
;-------------------------------------------------------
;Option to write alternate sectors when writing to a low
;data rate "minidisk". (This is necessary for some older
;5.25" drives because they take too long to turn around
;from writing data to reading the next sector header.)
;INTRLV=FALSE saves 40 (28h) bytes
;-------------------------------------------------------
INTRLV	equ	TRUE
;----------------------------------------------------
;Option to choose more or less verbose error messages
;VERBOS=FALSE saves 14 (0Eh) bytes
;----------------------------------------------------
VERBOS	equ	FALSE
;------------------------------------------
;Debug option includes PRINTA & PAHEX, etc.
;It also minimizes BIOS error messages, to
;make room for the DEBUG code.
;------------------------------------------
DEBUG	equ	FALSE
;-------------------------------------------------
;Optional Wyse WY-50/WY-30 Terminal Support
;This option enables initializing the terminal and
;writing to its banner line during initial boot
;-------------------------------------------------
WY50 equ TRUE
;-----------------------------
;I/O port installation options
;-----------------------------	
;UC1: selection. Exactly one of these must be true
C1TAS	equ	TRUE	;TU-ART serial channel A
C1TBS	equ	FALSE	;TU-ART serial channel B
C1NONE	equ	FALSE	;no user console 1

;LPT: selection. Exactly one of these must be true
LPTAS	equ	FALSE	;TU-ART serial channel A
LPTBS	equ	FALSE	;TU-ART serial channel B
LPTAP	equ	TRUE	;TU-ART parallel channel A
LPPRI	equ	FALSE	;PRI dot matrix interface
LPNONE	equ	FALSE	;no line printer

;UL1: selection. Exactly one of these must be true
L1TAS	equ	FALSE	;TU-ART serial channel A
L1TBS	equ	FALSE	;TU-ART serial channel B
L1TBP	equ	TRUE	;TU-ART parallel channel B
L1NONE	equ	FALSE	;no user line printer 1

;UR1: selection. Exactly one of these must be true
R1TAS	equ	TRUE	;TU-ART serial channel A
R1NONE	equ	FALSE	;no user reader 1

;UR2: selection. Exactly one of these must be true
R2TBS	equ	TRUE	;TU-ART serial channel B
R2NONE	equ	FALSE	;no user reader 1

;UP1: selection. Exactly one of these must be true
P1TAS	equ	TRUE	;TU-ART serial channel A
P1NONE	equ	FALSE	;no user punch 1

;UP2: selection. Exactly one of these must be true
P2TBS	equ	TRUE	;TU-ART serial channel B
P2NONE	equ	FALSE	;no user punch 1

;============================================================
;Equates
;============================================================
TUARTA equ C1TAS or LPTAS or R1TAS or P1TAS or LPTAP
TUARTB equ C1TBS or LPTBS or R2TBS or P2TBS or L1TBP
PRI equ LPPRI

 if BANKED
MSIZE	equ	RAMSIZ-1;CP/M standard memory size in Kbytes
 endif ;BANKED

 if not BANKED and not DRIV35
MSIZE	equ	RAMSIZ-9
 endif ;not BANKED and not DRIV35

 if not BANKED and DRIV35
MSIZE	equ	RAMSIZ-11
 endif ;not BANKED and DRIV35

;----------------
;General Settings
;----------------
RATRYS	equ	4		;Max 'read address' retries
RWTRYS	equ	8		;Max r/w sector retries
				;(the WD1793 tries 3 times)
SKTRYS	equ	4		;Max Seek retries
MAXDRV	equ	3		;drives A-D (0-3) allowed
IOINIT	equ	80h+20h+08h	;CON:=TTY:, LST:=LPT:
				;PUN:=UP1:, RDR:=UR1:
;----------------------------------
;CP/M Component Addresses and Sizes
;----------------------------------
CCPSIZ	equ	2048		  ;CCP size in bytes
BDOSIZ	equ	3584		  ;BDOS size in bytes

BIAS	equ	(MSIZE-20)*1024	  ;20K standard system offset 
CCP	equ	3400h+BIAS 	  ;base of CCP
BDOS	equ	CCP+CCPSIZ	  ;Base of BDOS
BIOS	equ	CCP+CCPSIZ+BDOSIZ ;base of BIOS
;-----------------
;CP/M Entry Points
;-----------------
CCPCLR	equ	CCP+3		;warm start, clear cmd buffer
BDOSE	equ	BDOS+6		;Main BDOS Entry Point
;------------------------------
;CP/M Page 0 Reserved Locations
;------------------------------
WBOOTV	equ	0		;CP/M warm boot entry
IOBYTE	equ	3		;Intel IOBYTE location
CDISK	equ	4
BDOSV	equ	5		;vector to BDOS

;Restart/interrupt vectors

RSTV1	equ	08h
RSTV2	equ	10h
RSTV3	equ	18h
RSTV4	equ	20h
RSTV5	equ	28h
RSTV6	equ	30h
RSTV7	equ	38h

BSCRCH	equ	40h		;16 bytes BIOS scratch RAM

;CP/M Buffers 

TBUF	equ	80h		;temp buffer used by CP/M
TPA	equ	100h		;transient program area
;---------------------------
;BDOS functions used in BIOS
;---------------------------
CPMVER	equ	0Ch		;returns version in hl
;-----------------------
;Memory Buffer constants
;-----------------------
BBANK	equ	40h		;Bank select port

 if BANKED
OSBANK	equ	00000001b	;Main memory bank
TBBANK	equ	00000010b	;track buffer's 32K bank

TRKBUF	equ	1000h		;track buf addr in TBBANK
 endif ;BANKED

SECSIZ	equ	128		;logical sector size
				;(must be 128)
TBSIZE	equ	10*1024		;max track buffer size
				;(3.5" disk)
;------------
;Disk Equates
;------------
TPD525	equ	40		;tracks per small 5.25" disk
TPD8	equ	77		;Tracks per 8" disk

;Boot-track sectors/track

BSPT5	equ	18	;128 bps SD & 256 bps DD minidisk SPT
BSPT8	equ	26	;128 bps SD & 256 bps DD 8" disk SPT

;----------------------------------------------
;16FDC and 64FDC floppy disk controller equates
;----------------------------------------------
;Port Addresses

USTAT	equ	00h		;UART status port
UBAUD	equ	00h		;UART Baud rate
UDATA	equ	01h		;UART data port
UCMD	equ	02h		;UART command
INTADR	equ	03h		;Interrupt address
INTMSK	equ	03h		;Interrupt mask

ADCMD	equ	04h		;Auxiliary disk command
ASTAT	equ	04h		;Auxiliary disk status

TIMER1	equ	05h		;Initialize timer 1
TIMER2	equ	06h		;Initialize timer 2
TIMER3	equ	07h		;Initialize timer 3
TIMER4	equ	08h		;Initialize timer 4
TIMER5	equ	09h		;Initialize timer 5

DSTAT	equ	30h		;disk status port
DCMMD	equ	30h		;disk command port
DTRCK	equ	31h		;track port
DSCTR	equ	32h		;sector port
DDATA	equ	33h		;data port
DFLAG	equ	34h		;disk flag port
DCTRL	equ	34h		;disk control port

;WD1793 Commands

RSTRCM	equ	00000000b	;(I) restore
SEEKCM	equ	00010000b	;(I) seek
STEPCM	equ	00100000b	;(I) step
STPICM	equ	01000000b	;(I) step-in
STPOCM	equ	01100000b	;(I) step-out
RSECCM	equ	10000000b	;(II) read sector
WSECCM	equ	10100000b	;(II) write sector
RADRCM	equ	11000000b	;(III) read address
RTRKCM	equ	11100000b	;(III) read track
WTRKCM	equ	11110000b	;(III) write track
FINTCM	equ	11010000b	;(IV) force interrupt

;WD1793 Command Options (1st letter matches WD1793 data sheet)

RS03MS	equ	00000000b	;(I) 3 mS step rate
RS06MS	equ	00000001b	;(I) 6 mS step rate
RS10MS	equ	00000010b	;(I) 10 mS step rate
RS15MS	equ	00000011b	;(I) 15 mS step rate
VERTRK	equ	00000100b	;(I) verify on destination track
HDLOAD	equ	00001000b	;(I) head load
UPDTTR	equ	00010000b	;(I) update track reg
F1SCMP	equ	00000010b	;(II)enable side compare
F2SCS1	equ	00001000b	;(II)side compare, side 1
MULTCM	equ	00010000b	;(II) read/write multiple
ED15MS	equ	00000100b	;(II & III) settling delay

;WD1793 Step Rates (note: low data rate "minidisk" mode
;halves the clock rate):
;
;  Reg.    Low         High
;  value  Data Rate  Data Rate
; RS03MS    6 mS        3 MS
; RS06MS   12 mS        6 MS
; RS10MS   20 mS       10 MS
; RS15MS   30 mS       15 MS

STEP35	equ	RS03MS		;3.5" drive step rate
STEP5	equ	RS10MS		;SA400L Minidisk rate=20 mS
				;(SA450 is the same...)
STEP5D	equ	RS03MS		;2-sided minidisk rate=6 mS
STEP5Q	equ	RS03MS		;Quad-density 5.25" rate=6 mS
STEP5H	equ	RS03MS		;5.25" HD 5.25" rate=3 mS
STEP8	equ	RS10MS		;SA800 8" 1-sided rate=8 mS
STEP8D	equ	RS03MS		;SA851 8" 2-sided rate=3 mS

;Composite WD1793 Commands used in this program

CCRDSEC	equ	RSECCM+F1SCMP	;read sector, verify side
CCWRSEC	equ	WSECCM+F1SCMP	;write sector, verify side
CCRDADD	equ	RADRCM+ED15MS	;read addr, settling delay

;Disk Control Register (DCTRL) output bits

DCDS1	equ	00000001b	;drive select 1
DCDS2	equ	00000010b	;drive select 2
DCDS3	equ	00000100b	;drive select 3
DCDS4	equ	00001000b	;drive select 4
DCMAXI	equ	00010000b	;0=Minidisk, 1=8"/3.5" disk
DCMDMO	equ	00100000b	;Motor on
DCDDEN	equ	01000000b	;double density enable
DCAUTOW	equ	10000000b	;autowait

;Auxiliary Disk Command (ADCMD) output bits
;(All bits are active-low)

ADSIDE0	equ	00000010b	;0=side 1, 1=side 0
ADCTRL0	equ	00000100b	;-Control Out (test)
ADRSTOR	equ	00001000b	;-Restore (PerSci)(16FDC)
ADFSEKN	equ	00010000b	;-Fast Seek (PerSci)(16FDC)
ADDSO	equ	00100000b	;-Drive Select Override (test)
ADEJECT	equ	01000000b	;-Remote eject (PerSci)(16FDC)

;Composite ADCMD values used to select disk side

SIDE0	equ	0FFh		 ;Needs to be <>0
SIDE1	equ	0FFh xor ADSIDE0 ;Needs to be <>0

;WD1793 Disk Status (DSTAT) after a command

SBUSY	equ	00000001b	;(All) busy
SCRCERR	equ	00001000b	;(All) CRC error
SWRPROT	equ	01000000b	;(All) write protect
SNOTRDY	equ	10000000b	;(All) drive not ready
SINDEX	equ	00000010b	;(I) index
STRACK0	equ	00000100b	;(I) track 0
SSEEKER	equ	00010000b	;(I) seek error
SHEADLD	equ	00100000b	;(I) head loaded
SDATRQ	equ	00000010b	;(II & III) data request
SLOSTD	equ	00000100b	;(II & III) lost data error
SRNFER	equ	00010000b	;(II & III) record not found
SRCTYF	equ	00100000b	;(II & III) record type fault
SWFALT	equ	00100000b	;(II & III) Write fault

;Disk Flag Register (DFLAG) input bits

DEOJ	equ	00000001b	;WD1793 INTRQ signal, active high
DAWTO	equ	00000010b	;Autowait Timeout
DMOTO	equ	00000100b	;Motor Timeout
DMOTON	equ	00001000b	;Motor On
DINHIN	equ	00010000b	;-Inhibit Init
DFSHLD	equ	00100000b	;WD1793 HLD, active high
DFAUTOB	equ	01000000b	;autoboot jumper, 0=autoboot
DFSDRQ	equ	10000000b	;WD1793 DRQ signal, 1=ready

;Auxiliary Status Register (ASTAT) input bits

BSW8	equ	00000001b	;Switch 8
BSW7	equ	00000010b	;Switch 7
BSW6	equ	00000100b	;Switch 6
BSW5	equ	00001000b	;Switch 5
BSIP	equ	01000000b	;(PerSci & 16FDC)
				;Seek in Progress
				;(Reads as '1' on 64FDC)
BRTC	equ	10000000b	;512 m,Sec reat-time clock

;Equates for TMS5501-based serial
;port on the 16FDC and 64FDC disk controllers

;USTAT bits

RXRDY	equ	01000000b	;Rx data ready
TXMTY	equ	10000000b	;Tx buffer empty

;UCMD bits

HIBAUD	equ	00010000b	;enable high baud
URESET	equ	00000001b	;Reset UART

;Modem status register bits

RTSSTA	equ	00010000b	;RTS bit
DTRSTA	equ	00100000b	;DTR bit

;UBAUD bits (The console baud rate is
;actually set by RDOS before booting.)
;
; Low Baud Rate  High Baud Rate  Stop Bits UBAUD
;     110                            2      01h
;     150                            1      82h
;     300                            1      84h
;    1200                            1      88h
;    2400           19200            1      90h
;    4800           38400            1     0A0h
;    9600           76800            1     0C0h

 if TUARTA or TUARTB
;--------------
;TU-ART Equates
;--------------
TUBASA	equ	20H	;Base of TU-ART serial port A
TUBASB	equ	80H	;Base of TU-ART serial port B

;Register offsets from base address

TUSTAT	equ	00h	;Status input port
			;(code asumes 0)
TUFE	equ	  01h	  ;framing error
TURBF	equ	  40h	  ;Rx-buf full bit
TUTBA	equ	  80h	  ;Tx-buf available bit
TUTBAB	equ	  7	  ;TUTBA bit

TUBAUD	equ	00h	;baud rate output port

TUDATA	equ	01h	;Data I/O port

TUCTRL	equ	02h	;Control output port
TURST	equ	  01h	  ;Reset UART
TURS7	equ	  04h	  ;Parallel input bit 7
			  ;..is interrupt
TUHBD	equ	  10h	  ;high baud rate

TUINTE	equ	03h	;Int enable I/O port

TUPARP	equ	04h	;Parallel port
TOPDA	equ	  80h	  ;Parallel port data available

;TU-ART Baud Rates
;High byte: control register value
;Low byte:  baud register value
;(See TUART manual)
;
; Baud Rate  Stop Bits xBAUD
;     110        2     0001h
;     150        1     0082h
;     300        1     0084h
;    1200        1     0088h
;    2400        1     0090h
;    4800        1     00A0h
;    9600        1     00C0h
;   19200        1     1090h
;   38400        1     10A0h
;   76800        1     10C0h

TABAUD	equ	10A0h	;TU-ART channel A: 38400
TBBAUD	equ	00C0h	;TU-ART channel B: 9600

 endif ;TUARTA or TUARTB
;-----------
;PRI Equates
;-----------
 if PRI
 PRBASE	equ	50h	;Base of PRI ports
 PRDATA	equ	04h	;offset to data port
 PRSTAT	equ	03h	;Offset to status
 PRBUSY	equ	20h	;printer busy bit

 endif ;PRI
;----------------------------
;Some Z80 instruction opcodes
;----------------------------
JR	equ	18h
JRNZ	equ	20h
JRZ	equ	28h
JRNC	equ	30h
JRC	equ	38h
DJNZ	equ	10h

;2-byte instructions have bytes reversed for dw

BIT0A	equ	047CBh		;BIT 0,a
BIT1A	equ	04FCBh		;BIT 1,a
BIT2A	equ	057CBh		;BIT 2,a
BIT3A	equ	05FCBh		;BIT 3,a
BIT4A	equ	067CBh		;BIT 4,a
BIT5A	equ	06FCBh		;BIT 5,a
BIT6A	equ	077CBh		;BIT 6,a
BIT7A	equ	07FCBh		;BIT 7,a

BIT1B	equ	048CBh		;BIT 1,b
BIT5B	equ	068CBh		;BIT 5,b
BIT6B	equ	070CBh		;BIT 6,b
BIT7B	equ	078CBh		;BIT 7,b

BIT2C	equ	051CBh		;BIT 2,c
BIT3C	equ	059CBh		;BIT 3,c
BIT4C	equ	061CBh		;BIT 4,c
BIT5C	equ	069CBh		;BIT 5,c	
BIT6C	equ	071CBh		;BIT 6,c
	
BIT0D	equ	042CBh		;BIT 0,d
BIT1D	equ	04ACBh		;BIT 1,d
BIT2D	equ	052CBh		;BIT 2,d
BIT3D	equ	05ACBh		;BIT 3,d
BIT4D	equ	062CBh		;BIT 4,d
BIT5D	equ	06ACBh		;BIT 5,d
BIT6D	equ	072CBh		;BIT 6,d
BIT7D	equ	07ACBh		;BIT 7,d

BIT0M	equ	046CBh		;BIT 0,(hl)
BIT1M	equ	04ECBh		;BIT 1,(hl)
BIT2M	equ	056CBh		;BIT 2,(hl)
BIT3M	equ	05ECBh		;BIT 3,(hl)
BIT4M	equ	066CBh		;BIT 4,(hl)
BIT5M	equ	06ECBh		;BIT 5,(hl)
BIT6M	equ	076CBh		;BIT 6,(hl)
BIT7M	equ	07ECBh		;BIT 7,(hl)

SET3B	equ	0D8CBh		;SET 3,b
SET4M	equ	0E6CBh		;SET 4,(hl)
SET6M	equ	0F6CBh		;SET 6,(hl)
SET7M	equ	0FECBh		;SET 7,(hl)

RES4B	equ	0A0CBh		;RES 4,b
RES7E	equ	0BBCBh		;RES 7,e
RES4M	equ	0A6CBh		;RES 4,(HL)
RES5M	equ	0AECBh		;RES 5,(hl)
RES6M	equ	0B6CBh		;RES 6,(hl)
RES7M	equ	0BECBh		;RES 7,(hl)

LDIR	equ	0B0EDh
CPIR	equ	0B1EDh
ZCPI	equ	0A1EDh		;Z80 CPI
CPD	equ	0A9EDh

OUTCA	equ	079EDh		;OUT (c),a
OUTCB	equ	041EDh		;OUT (c),b
OUTCH	equ	061EDh		;OUT c,h
OUTCL	equ	069EDh		;OUT c,l

INI	equ	0A2EDh
OUTI	equ	0A3EDh
INIR	equ	0B2EDh
OTIR	equ	0B3EDh
LDDE	equ	05BEDh		;LD de,(nn)
SBCD	equ	043EDh		;LD (nn),bc
SDED	equ	053EDh		;LD (nn),de
RLCB	equ	000CBh		;RLC b
RLCC	equ	001CBh		;RLC c
;--------------
;ASCII equates
;--------------
CTRLC	equ	3
BELL	equ	7
CR	equ	0Dh
LF	equ	0Ah
ESC	EQU	1BH
DQUOT	equ     22H          ;double-quote character
SQUOT	equ	27H	     ;single-quote character
BQUOT	equ     60H          ;back-quote character

;============================================================
;Page 0 Variables
;in the 1979 CP/M 2.2 Alteration Guide (page 23), RAM from
;40h through 4Fh is defined as available for use by the BIOS.
;50h through 5Bh is defined as "not currently used -
;reserved". All of page 0 RAM gets cleared during cold-boot.
;
;Note that this page of RAM is not visible when the track
;buffer bank is switched on.
;
;This BIOS uses Page 0 RAM only for temporary variables,
;so that a transient program can overwrite them without
;harm, even if it makes BIOS calls to perform I/O activity.
;Warm Boot will repair the jump vectors in Page 0, so a
;program that wipes out Page 0 can still return to CP/M via
;Boot or Warm Boot.
;============================================================
	org BSCRCH

;Temporary Variables

SCOUNT:	ds	1	;Seek retry counter
RCOUNT:	ds	1	;R/W retry counter

 if (50h-$)/256
 ERROR RAM variables exceed available space
 endif ;(50h-$)/256
;==============================================
;Fixed-location jump vectors for BIOS functions
;See CP/M documentation for details
;==============================================
	ORG	BIOS

	jmp	BOOT		;cold Start
WBOOTE:	jmp	WBOOT		;warm start
	jmp	CONST		;console input status
	jmp	CONIN		;console character in
	jmp	CONOUT		;console character out
	jmp	LIST		;list character out
	jmp	PUNCH		;punch character out
	jmp	READER		;reader character in
	jmp	HOME		;move head to track 0
	jmp	SELDSK		;select disk
	jmp	SETTRK		;set track number
	jmp	SETSEC		;set sector number
	jmp	SETDMA		;set DMA address
	jmp	READ		;read disk
	jmp	WRITE		;write disk
	jmp	LISTST		;return list status
	jmp	SECTRAN		;sector translate

;============================================
;Table Page
;The following tables are all in one 256-byte
;RAM page, for easy access.
;=============================================
TPAGE	equ	$/256

;===Table==================================================
;Parameter table for drive- and disk-unique constants
;These entries get updated by DRIVID when a drive is first
;selected, and by IDREAD when sidedness, SD/DD, and BPS
;for the inserted disk become known.
;3 bytes for each drive:
; Byte 0: step rate etc.
;   bits 0:1=step rate per WD1793
;   bit 2 = PRMQD: 1 if this drive is 5.25" quad-density
;   bit 3 = PRMHD: 1 if this drive is 5.25" high-density
;   bit 4 = PRM35: 1 if this is a 3.5" drive
;   bit 5 = PRMVC: 1 if this drive is a voicecoil drive
;   bit 6 = PRMDRV: 1 if this drive has never been restored
;   bit 7 = PRMDSK: 1 if this disk hasn't been seen since boot
; Byte 1: DCTRL value
;   bits 0:3: drive select bits
;   bit 4: DCMAXI = (8"or 3.5")/-5.25"
;   bit 5: DCMDMO (Motor on)
;   bit 6: DCDDEN (Density)
;   bit 7=0 (auto-wait gets OR'd into this bit)
; Byte 2: Sector size
;   bits 0:2=sector size {0-3} means {128-1024}
;   bits 3:7=0 
;NOTE: The PTLU subroutine assumes that the
;entire PRMTBL is in a single 256-byte page.   
;==========================================================
;Byte 0 bits:
PRMQD	equ	04h	;set for quad density 5.25"
PRMHD	equ	08h	;set for HD 5.25" drive
PRM35	equ	10h	;set for 3.5" drive
PRMVC	equ	20h	;set for Voicecoil drive
PRMDRV	equ	40h	;set for a new drive
PRMDSK	equ	80h	;set for new disk

;Byte 2 bits:
SS128	equ	0	;128 BPS
SS256	equ	1	;256 BPS
SS512	equ	2	;512 BPS
SS1024	equ	3	;1024 BPS

PRMTBL:	db	STEP5+PRMDRV+PRMDSK		;drive 0
	db	DCDDEN+DCMDMO+DCDS1
	db	SS512

	db	STEP5+PRMDRV+PRMDSK		;drive 1
	db	DCDDEN+DCMDMO+DCDS2
	db	SS512

	db	STEP5+PRMDRV+PRMDSK		;drive 2
	db	DCDDEN+DCMDMO+DCDS3
	db	SS512

	db	STEP5+PRMDRV+PRMDSK		;drive 3
	db	DCDDEN+DCMDMO+DCDS4
	db	SS512
;===Table=============================================
;Select tables for all supported drive & disk types
;Each entry is a 1-byte page address within TPAGE.
; Byte 0: page address of sector skew table
; Byte 1: page address of disk param block seed (DPBS)
;=====================================================
SELTBL8:

;8" single-sided

 db T826 and 0FFh,DP8S0 and 0FFh	;8" 1SSD 128 BPS
 db T815 and 0FFh,DP8S1 and 0FFh	;8" 1SSD 256 BPS
 db T88 and 0FFh,DP8S2 and 0FFh		;8" 1SSD 512 BPS

;8" single- and double-sided

 db T826 and 0FFh,DP8D1 and 0FFh	;1SDD 256 BPS,
					;2SSD 128 BPS
 db T815 and 0FFh,DP8D2 and 0FFh 	;1SDD 512 BPS,
					;2SSD 256 BPS
 db T88 and 0FFh,DP8D3 and 0FFh 	;1SDD 1024 BPS,
					;2SSD 512 BPS

;8" double-sided

 db T826 and 0FFh,DP8D1A and 0FFh	;8" 2SDD 256 BPS
 db T815 and 0FFh,DP8D2A and 0FFh	;8" 2SDD 512 BPS
 db T88 and 0FFh,DP8D3A and 0FFh	;8" 2SDD 1024 BPS

SELTBL5:

;5.25" single-sided

 db T518 and 0FFh,DP5S0 and 0FFh	;5.25" 1SSD 128 BPS
 db T510 and 0FFh,DP5S1 and 0FFh	;5.25" 1SSD 256 BPS
 db T55 and 0FFh,DP5S2 and 0FFh		;5.25" 1SSD 512 BPS

;5.25" single- and double-sided

 db T518 and 0FFh,DP5D1 and 0FFh	;1SDD 256 BPS,
					;2SSD128 BPS
 db T510 and 0FFh,DP5D2 and 0FFh	;1SDD 512 BPS,
					;2SSD 256 BPS
 db T55 and 0FFh,DP5D3 and 0FFh		;1SDD 1024 BPS,
					;2SSD 512 BPS

;5.25" double-sided

 db T518 and 0FFh,DP5D1A and 0FFh	;5.25" 2SDD 256 BPS
 db T510 and 0FFh,DP5D2A and 0FFh	;5.25" 2SDD 512 BPS
 db T55 and 0FFh,DP5D3A and 0FFh	;5.25" 2SDD 1024 BPS

 if QDRIVE
SELTBLQ:	;(Only supporting 2SDD)
;5.25" quad-density double-sided

 db T518 and 0FFh,DP5Q1A and 0FFh	;5.25" 2SQD 256 BPS
 db T510 and 0FFh,DP5Q2A and 0FFh	;5.25" 2SQD 512 BPS
 db T55 and 0FFh,DP5Q3A and 0FFh	;5.25" 2SQD 1024 BPS
 endif ;QDRIVE

 if HDRIVE
SELTBLH:	;(Only supporting 2SDD)
;5.25" high-density double-sided

 db T826 and 0FFh,DP5H1A and 0FFh	;5.25" 2SHD 256 BPS
 db T815 and 0FFh,DP5H2A and 0FFh	;5.25" 2SHD 512 BPS
 db T88 and 0FFh, DP5H3A and 0FFh	;5.25" 2SHD 1024 BPS
 endif	;HDRIVE

 if DRIV35
SELTBL3:	;(Only supporting 2SDD)
;3.5" double sided

 db T330 and 0FFh,DP3D1A and 0FFh	;3.5" 2SSD 256 BPS
 db T318 and 0FFh,DP3D2A and 0FFh	;3.5" 2SSD 512 BPS
 db T310 and 0FFh,DP3D3A and 0FFh	;3.5" 2SSD 1024 BPS
 endif ;DRIV35

;==========================================================
;Skew Table
;Used by SECTRAN for interleaving, and by TVTLU to discover
;physical sectors per track
;  Byte 0 = PSPT( Physical sectors per track)
;  Byte 1 = INTLV (interleave Factor)
;NOTE: The SECTRAN algorithm assumes that the largest
;common factor between PSPT and INTLV is 2. SECTRAN will
;need to be modified if you choose an interleave factor
;where the largest common factor is larger than 2.
;==========================================================
SKWTBL:
;---Table-----------------------
;Skew table for HD and 8" drives
;-------------------------------
T826:	db	26,6	;128 BPS SD & 256 BPS DD
T815:	db	15,4	;256 BPS SD & 512 BPS DD
T88:	db	8,3	;512 BPS SD & 1024 BPS DD
;---Table------------------
;Skew table for 3.5" drives
;(overlaps 5.25" table)
;--------------------------
 if DRIV35
T330:	db	31,3	;3.5" 256 BPS DD 
T318:			;3.5" 512 BPS DD
 endif ;DRIV35
;---Table-------------------
;Skew table for 5.25" drives
;overlapping with 3.5" table
;---------------------------
T518:	db	18,4	;128 BPS SD & 256 BPS DD
T310:			;3.5" 1024 BPS DD
T510:	db	10,3	;256 BPS SD & 512 BPS DD
T55:	db	5,2	;512 BPS SD & 1024 BPS DD

;=========================================================
;Disk Parameter Block Seeds for all supported disk types,
;used to calculate the CP/M Disk Parameter Blocks (DPBs).
;(See CP/M documentation for details)
;Block sizes for each format come from CCS documentation
;except 3.5" drives (which CCS never supported)
;  Byte  Name   Function               Interpretation
; 0<1:0> BPB   Bytes/block            00:1024, 01:2048
;                                     10:4096, 11:8192
; 0<2>   DSMH  DSM high byte          1 means +256
; 0<3>   RTOF  Reserved Track Offset  0:2, 1:3
; 0<4>   CKSS  Check Vector Size      0:16, 1:32
; 0<5>   AL01  Allocation Vectors     0:128, 1:192
; 0<7:6> DENT  Directory Entries      01:64, 10:128
;   1    SPT   128-byte Sectors/track SPT
;   2    DSML  DSM low byte           DSM mod 256
;=========================================================

;---Table----------------------------------------
;Disk parameter block seeds for 77-track 8" disks
;(and 5.25" HD drives, using only 77 tracks)
;------------------------------------------------
;1SSD: 26 128-byte sectors, 1K CP/M blocks

DP8S0:	db	60h	;Byte 0 bits
	db	26	;SPT
	db	242	;DSML

;1SSD: 15 256-byte sectors, 2K CP/M blocks

DP8S1:	db	41h	;Byte 0 bits
	db	30	;SPT
	db	139	;DSML

;1SSD: 8 512-bytes sectors, 2K CP/M blocks

DP8S2:	db	41h	;Byte 0 bits
	db	32	;SPT
	db	149	;DSML

;1SDD: 26 256-byte sectors, 2K CP/M blocks
;2SSD: 2x26 128-byte sectors, 2K CP/M blocks

DP8D1:	db	41h	;Byte 0 bits
	db	52	;SPT
	db	242	;DSML

;1SDD: 15 512-byte sectors, 4K CP/M blocks
;2SSD: 2x15 256-byte sectors, 4K CP/M blocks

DP8D2:	db	45h	;Byte 0 bits
	db	60	;SPT
	db	24	;DSML

;1SDD: 8 1024-byte sectors, 2K CP/M blocks
;2SSD: 2x8 512-byte sectors, 2K CP/M blocks

DP8D3:	db	45h	;Byte 0 bits
	db	64	;SPT
	db	43	;DSML

;2SDD: 2X26 256-byte sectors, 4K CP/M blocks

DP8D1A:	db	92h	;Byte 0 bits
	db	104	;SPT
	db	242	;DSML

;2SDD: 2X15 512-byte sectors, 8K CP/M blocks

DP8D2A:	db	93h	;Byte 0 bits
	db	120	;SPT
	db	139	;DSML

;2SDD: 2 x 8 1024-byte sectors, 4K CP/M blocks

DP8D3A	db	96h	;Byte 0 bits
	db	128	;SPT
	db	43	;DSML

;---Table--------------------------------------------
;Disk parameter Block seeds for 5.25" 40-track disks:
;----------------------------------------------------
;1SSD: 18 128-byte sectors, 1K CP/M blocks

DP5S0:	db	68h	;Byte 0 bits
	db	18	;SPT
	db	82	;DSML

;1SSD: 10 256-byte sectors, 1K CP/M blocks
;1SSD: 5 512-byte sectors, 1K CP/M blocks

DP5S1:
DP5S2:	db	68h	;Byte 0 bits
	db	20	;SPT
	db	91	;DSML

;1SDD: 18 256-byte sectors, 2K CP/M blocks
;2S1D: 2x18 128-byte sectors, 2K CP/M blocks

DP5D1:	db	49h	;Byte 0 bits
	db	36	;SPT
	db	82	;DSML

;1SDD: 10 512-byte sectors
;1SDD: 5 1024-byte sectors

DP5D2:			;(The tables are the same.)

;2S1D: 2x10 256-byte sectors, 2K CP/M blocks
;2S1D: 2x5 1024-byte sectors, 2K CP/M blocks

DP5D3:	db	49h	;Byte 0 bits
	db	40	;SPT
	db	91	;DSML

;2SDD: 2x18 256-byte sectors, 2K CP/M blocks

DP5D1A:	db	49h	;Byte 0 bits
	db	72	;SPT
	db	165	;DSML

;2SDD: 2x10 512-byte sectors, 2K CP/M blocks

DP5D2A:			;(The tables are the same)

;2SDD: 2x5 1024-byte sectors, 2K CP/M blocks

DP5D3A:	db	49h	;Byte 0 bits
	db	80	;SPT
	db	184	;DSML

 if QDRIVE
;---Table----------------------------
;Disk parameter Block seeds for 5.25"
;80-track "Quad Density" disks:
;------------------------------------
;2SDD: 2x18 256-byte sectors, 2K CP/M Blocks

DP5Q1A:	db	4Dh	;Byte 0 bits
	db	72	;SPT
	db	89	;DSML

;2SDD: 2x18 512-byte sectors, 2K CP/M Blocks
;2SDD: 2x18 1024-byte sectors, 2K CP/M Blocks (same)

DP5Q2A:
DP5Q3A:	db	4Dh	;Byte 0 bits
	db	80	;SPT
	db	128	;DSML
 endif ;QDRIVE

 if HDRIVE
;---Table----------------------------
;Disk parameter Block seeds for 5.25"
;80-track "High Density" disks:
;------------------------------------
;2SDD: 2x18 256-byte sectors, 4K CP/M Blocks

DP5H1A:	db	92h	;Byte 0 bits
	db	104	;SPT
	db	252	;DSML

;2SDD: 2x18 512-byte sectors, 4K CP/M Blocks

DP5H2A:	db	96h	;Byte 0 bits
	db	120	;SPT
	db	35	;DSML

;2SDD: 2x18 1024-byte sectors, 4K CP/M Blocks

DP5H3A:	db	96h	;Byte 0 bits
	db	128	;SPT
	db	55	;DSML
 endif ;HDRIVE

 if DRIV35
;---Table-------------------------------------------
;Disk parameter Block seeds for 3.5" 80-track disks:
;---------------------------------------------------
;2SDD: 2x18 256-byte sectors, 4K CP/M blocks

DP3D1A:	db	96h	;Byte 0 bits
	db	124	;SPT
	db	45	;DSML

;2SDD: 2x18 512-byte sectors, 4K CP/M blocks

DP3D2A:	db	96h	;Byte 0 bits
	db	144	;SPT
	db	94	;DSML

;2SDD: 2x18 1024-byte sectors, 4K CP/M blocks

DP3D3A:	db	96h	;Byte 0 bits
	db	160	;SPT
	db	133	;DSML
 endif ;DRIV35	

PTEND:

;***************************************************
;Check that all the above tables fit in one 256-byte
;page.
;Note that ASM's math is limited to 16 bits.
;Note also that (despite ASM's documentation), the
;'if' pseudo-op only looks at the lsb of the result.
;***************************************************

 if PTEND/256 - TPAGE

ERROR: Table page crosses a page boundary

 endif ;PTEND/256 - TPAGE

;================================================
;Message strings used after cold boot
;Strings ending with msb set get 'error' appended.
;Strings ending with 0 just end.
;80h gets replaced with BUFDSK value in ASCII.
;The PMSGP subroutine assumes all of these
;messages are in a single 256-byte page.
;================================================

MPAGE:

 if DEBUG			;truncated messages to make room
IOMSG:	db	'I'+80h
RSTMSG:	db	'R'+80h
BOTMSG:	db	'B'+80h
WFMSG:	db	'W'+80h

WPMSG:	db	'W '			;Fall into DNRMSG
DNRMSG:	db	'N'+80h

MPEND:

ERRMSG:	db	' er'			;Fall into CRMSG
CRMSG:	db	CR,0			;LF gets appended

 endif ;DEBUG

 if VERBOS and not DEBUG
IOMSG:	db	'IOBYT','E'+80h
RSTMSG:	db	'RS','T'+80h
BOTMSG:	db	'Boo','t'+80h
WFMSG:	db	'Drive ',01h		;becomes drive letter
	db	' Writ','e'+80h

WPMSG:	db	'Write Protect- '	;Fall into DNRMSG

DNRMSG:	db	'Drive ',01h		;becomes drive letter
	db	' Not Read','y'+80h

MPEND	equ	$+1

ERRMSG:	db	BELL,' Error'		;Fall into CRMSG
CRMSG:	db	CR,0			;LF gets appended
 endif ; VERBOS and not DEBUG

 if not VERBOS and not DEBUG
IOMSG:	db	'IOBYT','E'+80h
RSTMSG:	db	'RS','T'+80h
BOTMSG:	db	'Boo','t'+80h
WFMSG:
DRVL1:	db	01h			;gets drive letter
	db	': writ','e'+80h

WPMSG:	db	'Write Prot '		;Fall into DNRMSG

DNRMSG:
DRVL2:	db	01h			;gets drive letter
	db	': Not Read','y'+80h

MPEND	equ	$+1

ERRMSG:	db	' Err'			;Fall into CRMSG
CRMSG:	db	CR,0			;LF gets appended
 endif ; not VERBOS and not DEBUG

;*** Assembly Check ********************************
;Check that all messages between MPAGE and MPEND
;are all in one 256-byte page.
;Note that ASM's math is limited to 16 bits.
;Note also that (despite ASM's documentation), the
;'if' pseudo-op only looks at the lsb of the result.
;***************************************************
 if (MPEND - MPAGE)/256

ERROR: Messages cross a page boundary

 endif ;(MPEND - MPAGE)/256

;***Error Reboot**************
;Too many retries loading CP/M
;*****************************
BFAIL:	mvi	l,BOTMSG and 0FFh ;'boot error'

;Fall into MWBOOT, where stack gets restored

;***Error Reboot**************************************
;Print message at hl, wait for user input, warm boot.
;If user typed ^C, force cold boot
;*****************************************************
MWBOOT:	call	PMSGP		;print message at (l)

	call	CONIN2		;wait for user input
				;..without flushing buffer

	mov	e,a		;user input
	call	PCRLF		;print CRLF after user input

	mov	a,e		;user input
	cpi	CTRLC		;^C forces cold boot
	db	JRNZ,WBOOT-$-1

;Fall into CBOOT

;***BIOS Function********************************************
;BIOS Function: cold boot (Restores stack)
;Reload BDOS (BDOS & CCP) from drive A without wiping out the
;BIOS. Set CP/M's drive to A. Do not flush the buffer.
;************************************************************
CBOOT:	xra	a
	sta	CDISK		;a=0: CP/M select drive A
	sta	TBSTAT		;nothing to flush in BFLUSH

;Fall into WBOOT

;***BIOS Function********************************************
;BIOS Function: warm boot (Restores stack)
;Reload BDOS (BDOS & CCP) from drive A without wiping out the
;BIOS. Note that the track buffer may still have valid data
;from previous operations before WBOOT was called.
;
;Note that system tracks on a 3.5" disk are treated the same
;as those on an 8" disk, meaning that not all sectors are
;used, and meaning that the 8" disk parameters are used
;during booting.
;On Entry:
;  TBSTAT = 0FFh if the buffer still needs to be flushed
;  CDISK is valid
;************************************************************
WBOOT:
 if BANKED
	mvi	a,OSBANK	;select main memory bank
	out	BBANK
 endif ;BANKED

	di			;no ints while booting
	lxi	sp,TPA		;page 0 local stack for boot

	call	BFLUSH		;flush anything in the buffer
				;returns a=0 on success
				;keep going on flush fail too

;log all 4 disks out
	lxi	h,PRMTBL
	mvi	b,4		;4 drives

LOLUP:	dw	SET7M		;set PRMDSK bit
	inx	h		;3 bytes/PRMTBL entry
	inx	h
	inx	h
	db	DJNZ,(LOLUP-$-1) xor 0FF00h

;Select drive A and have SELDSK re-ID that disk
;b=0 here
	mov	c,b		;0 for drive A
	call	SELDSK		;make DPB current
				;Z clear means error
	db	JRNZ,(BFAIL-$-1)XOR 0FF00h

;Track 0 is always SD, so set drive A DCTRL value to SD for
;faster loading. This bit will get fixed in IDREAD if the
;boot disk is otherwise a SD disk.
	lxi	h,PRMTBL+1	;drive A DCTRL value
	dw	RES6M		;clear DCDDEN for SD

;Sector 1 was the boot sector, so we start loading CP/M
;from sector 2.
;Loop to read CCP & BDOS one (SECSIZ-byte) logical sector
;at a time. Note that the BIOS is on a 256-byte boundary.
; b = track number
; c = next 0-based sector number
; de = DMA address
; hl = PRMTBL+1 (points to drive A DCTRL value)
	lxi	b,1		;b: start with track 0
				;c:start with sector 1
	lxi	d,CCP		;DMA address

WBTLUP:	mov	a,b		;set track
	sta	SEKTRK

WBSLUP:	dw	SDED,DMAADR	;set sector's DMA address

	push	h		;PRMTBL+1
	push	b
	call	SETSEC		;c=sector number
	call	READ		;returns next DMA addr in de
				;trashes hl
	pop	b
	pop	h		;PRMTBL+1
	db	JRNZ,(BFAIL-$-1)XOR 0FF00h

;Stop loading when we reach the BIOS (which follows the BDOS)
	mvi	a,BIOS/256-1	;filled to BIOS yet?
	cmp	d		;set carry if so
	db	JRC,BOOT0-$-1

;Next sector
	inr	c		;next 0-based logical sector

;Boot track 0 is always BSPT5 sectors per track for
;low data rate drives, and BSPT8 sectors per track for
;high data rate drives. (Subsequent tracks are double-
;density, and have 2x the sectors per track.)
	mvi	a,BSPT5		;low data rate "minidisk"?
	dw	BIT4M		;test DCMAXI
	db	JRZ,WB1-$-1
	mvi	a,BSPT8		;high data rate "maxi" SPT
WB1:
	dw	BIT6M		;double density?	
	db	JRZ,WB2-$-1
	rlc			;y: DD means 2x logical secs
WB2:				;a=SPT

	xra	c		;last sector on this track?
	db	JRNZ,(WBSLUP-$-1)XOR 0FF00h

;Next track. Subsequent system tracks are always 256 BPS
;DD, so set drive A DCTRL value to DD (for faster loading)
;  a=0
	dw	SET6M		;set DCDDEN for DD
	mov	c,a		;(a=0) start with sector 0
	inr	b		;..on the next track
	db	JR,(WBTLUP-$-1)XOR 0FF00h

;******************************************
;Common boot code for cold & warm boots
;Set up page 0 vectors and a few constants,
;get the current drive, and go to the CCP
;******************************************
BOOT0:	lxi	h,TBUF		;default DMA address is 80h
	shld	DMAADR

;Get c = current drive for entry into CCP
	mvi	l,CDISK		;h already 0
	mov	c,m		;select drive for CCP

;NOP all restart vectors and install RST error JMP
	mvi	l,RSTV1		;h=0 here
	mvi	b,RSTV7-RSTV1

CBLOOP:	mov	m,h		;h=0
	inr	l
	db	DJNZ,(CBLOOP-$-1)XOR 0FF00h	

;Install jump vectors
	mvi	a,JMP		;jump opcode

	mov	m,a		;last restart vector
	lxi	h,RSTERR	;other vectors are NOP'ed out
	shld	RSTV7+1

	sta	WBOOTV		;warm boot JMP
	lxi	h,WBOOTE
	shld	WBOOTV+1

	sta	BDOSV		;BDOS entry JMP
	lxi	h,BDOSE
	shld	BDOSV+1

;Jump to CCP with c=drive number to complete initialization
	jmp	CCPCLR		;go to CPM w/ no initial cmd

;***BIOS Function*****************************************
;Sector Translate
;Translate sector in c using the 'translate table address'
;in de. Note that the value in de is created by SELDSK
;when the disk is identified. SELDSK passes the info
;needed to perform the sector translation, though not
;actually a translation table address. (See below.)
;On Entry:
;  c=0-based logical sector number
;  d=BPS(0:3) for this disk
;  e=Skew table page address within TPAGE
;On Exit:
;  h=0
;  l=logical interleaved 0-based sector number
;    (low bits are the sub-sector, high-bits are the
;    0-based physical sector number. the number of bits
;    in the subsector depends on this disk's BPS.)
;    msb set for side 1, cleared for side 0
;Trashes psw,b,de,hl
;Stack Depth: 2 (System stack, not including call SECTRAN)
;*********************************************************
SECTRAN:

;---------------------------------------------------------
;Convert logical (SECSIZ-byte) 0-based sector number in c
;to a physical, 0-based sector number: divide c by number
;of SECSIZ-byte logical sectors sectors/physical sector
;  c=0-based logical sector number input to this routine
;  d=BPS(0:3)
;  e=Skew table page address within TPAGE
;  hl=skew table address
;---------------------------------------------------------
	inr	d		;BPS(1:4)
	mov	b,d
	push	b		;BPS(1:4) & logical sector

	mov	a,c		;desired sector from input

SECT1:	ora	a		;clear carry
	rar
	db	DJNZ,(SECT1-$-1)XOR 0FF00h

	ral			;went 1 too many
;-------------------------------------------------------
;Create the skew table address
;  a=un-interleaved 0-based physical sector
;  b=0
;  c=0-based logical sector number input to this routine
;  e=Skew table page address within TPAGE
;-------------------------------------------------------
	mov	l,e
	mvi	h,TPAGE
;------------------------------------------------------------
;Set b=0 for side 0, 80h for side 1, by comparing physical
;sector number to total number of sectors/track, as indicated
;by the first entry in the skew table. If side 1, then
;subtract SPT from a to get sector number for the side.
;  a=un-interleaved 0-based sector number
;  b=0
;  hl=skew table address
;  (hl)=physical SPT
;------------------------------------------------------------
	mov	d,b		;0	
	cmp	m		;1st table entry is SPT	
	db	JRC,SECT2-$-1	;side 1?
	mvi	d,80h		;flag for side 1
	sub	m		;sector without side
SECT2:
;-----------------------------------------------------------
;Compute interleaved physical sector number using specified
;skew table. NOTE: This algorithm assumes that the largest
;common factor between SPT and the interleave factor is 2.
;  a=un-interleaved 0-based physical sector without side
;  d=0 for side 0, 80h for side 1
;  hl=skew table address
;-----------------------------------------------------------
	mov	b,a		;physical sector (0-based)
	mov	e,m		;SPT
	inx	h		;(hl)=interleave factor

	xra	a		;start with 0
	inr	b		;for count down to 0

	db	CPI		;skip 1st add m
SECT3:	add	m		;interleave factor
	cmp	e
	db	JRNZ,SECT4-$-1	;common factor of 2?
	inr	a		;skip (does not affect cy)
SECT4:
	db	JRC,SECT5-$-1
	sub	e
SECT5:	db	DJNZ,(SECT3-$-1)xor 0FF00h

	pop	b		;BPS & Logical sector
;-------------------------------------------------------
;Compute sub-sector within sector, and shift sector
;to make room for subsector
;  a=interleaved sector number (0-based)
;  b=BPS(1:4)
;  c=logical sector
;  d=0 for side 0, 80h for side 1
;-------------------------------------------------------
	mov	l,a		;interleaved sector number
	xra	a		;initialize mask
	db	JR,SECT7-$-1	;skip into loop

;compute a=subsector mask and l=shifted sector number

SECT6:	dad	h		;offset sector number
	stc
	ral			;build subsector mask
SECT7:	db	DJNZ,(SECT6-$-1)XOR 0FF00h

	mov	h,b		;high byte of sector is 0
	ana	c		;a=subsector
;-------------------------------------------------------
;Combine physical sector, subsector, and side to
;make resulting translated sector number for return
;  a=subsector
;  c=0-based logical sector number input to this routine
;  d=0 for side 0, 80h for side 1
;  h=0
;  l=0-based interleaved physical sector number, shifted
;    to make room for subsector
;-------------------------------------------------------
	ora	l		;combine interleaved sector
	ora	d		;add on side select
	mov	l,a		;hl=0-based interleaved sect
				;with side as msb of l
	ret
;***BIOS Function********************************************
;Select Disk
;
;If we have never looked at a disk in the specified drive
;before, then we first figure oit what kind od drive it is
;(3.5", 5.25" minidisk, 5.25" QD or HD, 8", 8" voicecoil)
;
;If we've not seen a disk in this drive since the last WBOOT
;or CBOOT, then identify the disk in the disk to determine
;its density, sidedness, and BPS. Use these data to generate
;the correct DPB for this drive, for CP/M.
;
;On Entry
;  c=requested disk number (0-3)
;  TSTAT = 0FFh if the buffer is dirty
;  PRMTBL(c) bit 7=1 if we've never looked at this drive
;  PRMTBL(c) bit 6=1 if we haven't looked at a disk in this
;     drive since WBOOT or CBOOT
;Success Exit:
;  Z set
;  SEKDSK = requested disk
;  hl=address of correct DPH(SEKDSK)
;  If this is the first time we've seen this disk:
;     Buffer flushed
;     DPB(SEKDSK) calculated
;     BUFDSK=SEKDSK
;     IDSAVE has current values
;     Drive head is on the first non-system track    
;  If this is the first time this drive was selected:
;     This drive's PRMTBL entry is updated with the drive type
;     (size, voice-coil or not)
;Disk Failure Exit:
;  Z clear
;  hl=0
;Bad drive Number Error Exit:
;  hl=0
;  Z random...
;Trashes psw,b,de
;Stack Depth: 4 plus call
;************************************************************
SELDSK:	mov	a,c		;requested drive
	sta	SEKDSK		;remember request

	lxi	h,0		;error-return value

	cpi	MAXDRV+1	;valid drive number?
	rnc			;n: quit with hl=0

	mov	l,a		;hl=SEKDSK
	dad	h		;*2
	dad	h		;*4
	dad	h		;*8
	dad	h		;*16
	xchg			;de=SEKDSK*16

	call	PTLUA		;lookup SEKDSK (not BUFDSK)
	mov	b,m		;remember byte 0 value
	lxi	h,DPHBASE	;offset based on drive number
	dad	d		;hl=DPH address
;-----------------------------------------------------------
;If we've never seen a disk in this drive since the last
;wboot or cboot, then ID the disk and create the appropriate
;DPB. (We re-ID after every boot because the disk may have
;changed, changing density, sidedness, and/or BPS.)
;  b=PRMTBL(SEKDSK)
;  c=selected drive=SEKDSK
;  d=0
;  hl=DPH address
;-----------------------------------------------------------
	dw	BIT7B		;0 means logged in
	rz
;-------------------------------------------------
;Flush the buffer and take a look at the disk to
;determine its density, BPS and sidedness.
;  b=PRMTBL(SEKDSK)
;  c=selected drive=SEKDSK
;  d=0
;  hl=DPH address
;-------------------------------------------------
	mov	e,d		;de=0
	xchg			;DPH pointer to de
				;0 to hl

	call	BFLUSH		;flush buffer if needed
	rnz			;hl=0 if BFLUSH error

;The buffer is empty now (and TBSTAT=0), so it's
;safe to set BUFDSK=SEKDSK
	mov	a,c		;selected drive
	sta	BUFDSK		;for seeking

;----------------------------------------------------
;Use local stack from here forward. Note that BFLUSH
;uses the local stack, so we must not use the local
;stack in this subroutine until after BFLUSH (above)
;is done.
;  b=PRMTBL(BUFDSK) (since BUFDSK=SEKDSK now)
;  de=DPH address
;  hl=0
;----------------------------------------------------
 if PERSCI	
	mov	c,h		;0
 endif ;PERSCI

	dad	sp		;get incoming stack pointer
	lxi	sp,STACK	;use local stack
	push	h		;save incoming stack pointer

	push	d		;this drive's DPH address

;Select BUFDSK in case we don't call DRIVID
	call	PTLU		;DCTRL value & PRMTBL pointer
	out	DCTRL		;Select disk, motor on

 if PERSCI	
;Delay ~1 mS to give PerSci 299 time to become ready
SDSK0:	dcr	c
	db	JRNZ,(SDSK0-$-1) xor 0FF00h
 endif ;PERSCI

;---------------------------------------------------
;If we've never seen this drive, then ID it, to
;determine what kind of *drive* it is
;  b=PRMTBL(BUFDSK)
;---------------------------------------------------
	dw	BIT6B		;never seen this drive?
	cnz	DRIVID		;new drive: find its size
				;updates and  returns
	db	JRNZ,JSELER-$-1	;DRIVID error?
				;(hop skip there)
;------------------------------------------------------
;We know what kind of drive. Now determine what kind of
;disk is in that drive: Restore to track 0, then seek
;the 1st non-system track and ID that track to discover
;the drive's density and BPS.
;------------------------------------------------------
	mvi	c,RSTRCM+HDLOAD	;restore with head loaded
	call	T1DCMD		;Restore is a type I command
				;hl=PRMTBL(BUFDSK)

	mvi	a,SIDE0		;Seek side 0
	sta	BUFSID		;..for SEEKID
	
	mvi	a,2		;1st MAXI non-system track
	inx	h		;hl=PRMTBL(BUFDSK)+1
	dw	BIT4M		;test DCMAXI
	db	JRNZ,SDSK1-$-1	;2 sys tracks on most disks
	inr	a		;3 sys tracks on minidisks
SDSK1:	sta	BUFTRK		;target for SEEKID

	call	SEEKID		;could be a null seek
				;will retry if wrong track
				;hl=PRMTBL(BUFDSK)
	db	JRNZ,JSELER-$-1	;Seek error?
				;(hop skip there)

 if QDRIVE or HDRIVE or DRIV35
	mov	d,m
 endif ;QDRIVE or HDRIVE or DRIV35

	inx	h		;hl=PRMTBL(BUFDSK)+1
	mov	c,m		;c=DCTRL value from PRMTBL

	inx	h		;hl=PRMTBL(BUFDSK)+2
	mov	m,e		;BPS value goes here
;--------------------------------------------------
;If this is an 80-track drive (Quad-Density, High-
;Density, or 3.5" drive) then we know already that
;it is 2-sided double-density. Get the right SELTBL
;and go offset based on BPS only.
;  c=DCTRL value
;  d=value of PRMTBL(BUFDSK)
;  e= BPS(0:3) from IDRD0
;  hl=PRMTBL(BUFDSK)+2
;--------------------------------------------------
 if QDRIVE or HDRIVE or DRIV35
	mov	a,e			;BPS offset
 endif ;QDRIVE or HDRIVE or DRIV35

 if QDRIVE
	mvi	l,(SELTBLQ-2) and 0FFh ;with DD offset
	dw	BIT2D			;test PRMQD
	db	JRNZ,SDSK9-$-1
 endif ;QDRIVE

 if HDRIVE
	mvi	l,(SELTBLH-2) and 0FFh ;with DD offset
	dw	BIT3D			;test PRMHD
	db	JRNZ,SDSK9-$-1
 endif ;HDRIVE

 if DRIV35
	mvi	l,(SELTBL3-2) and 0FFh ;with DD offset
	dw	BIT4D			;test PRM35
	db	JRNZ,SDSK9-$-1
 endif ;DRIV35
;-----------------------------------------------------------
;Figure out sidedness: Look at side 1 to see if this is a
;2-sided disk. (Note that the 16FDC and 64FDC give no
;visibility to the 2-sided signal from 8" disks.)
;There are 4 possibilities: 
;1) This is a 1-sided disk in a 2-sided drive that signals
;   "not ready" when side 1 is selected for such a disk
;2) This is a 1-sided drive. In this case, "side 1" may be
;   readable (if the side signal is just ignored), but
;   IDSIDE will identify as side 0.
;3) This is a 1-sided disk in a 2-sided drive that reads
;   garbage from side 1, causing the IDREAD will fail. This
;   will work correctly, but will be slow, since the WD1793
;   will keep trying for 6 revolutions before giving up.
;4) this is a 2-sided disk in a 2-sided drive. In this case,
;the IDREAD will be successful, and IDSIDE will be 1.
;  e=BPS(0:3)
;  c=DCTRL value
;-----------------------------------------------------------

;test for case 1: 1-sided disk in a 2-sided drive that
;signals "not ready" if side 1 is selected. We already know
;that the drive is ready on side 0 from the above seek.
;The head is currently on the 1st non-system track here.
	mvi	a,SIDE1
	out	ADCMD		;select side 1

 if PERSCI
;Delay: PerSci 299 drives to need time to deassert ready
SDSK4:	dcr	a
	db	JRNZ,(SDSK4-$-1) xor 0FF00h
 endif ;PERSCI

	in	DSTAT		;disk status
	ral			;Drive not ready?
	db	JRC,SDSK5-$-1	;y: 1-sided disk

;test for cases 2-4
	mvi	a,1		;Don't retry
	call	IDRDRA		;Try to get an ID from side 1
				;e=BPS(0:3)
				;hl=PRMTBL(BUFDSK)
				;Z clear on error

	lda	IDSIDE		;side IDREAD found (0 or 1)
	db	JRZ,SDSK6-$-1	;Z for good read

;Error in IDREAD implies a 1-sided disk in a 2-sided drive.
;Reset SIDE and the select table for side 0.
SDSK5:	call	IDRD0		;reset to side 0
				;a=0 on success
				;e=BPS(0:3)
				;hl=PRMTBL(BUFDSK)
				;Z clear on real error
JSELER:	jnz	SDEXIT

SDSK6:	rrc			;compute sidedness offset
	ral			;tricky use of carry bit
	ral			;a=0 or 3
	mov	d,a		;d=0 or 3
;----------------------------------------------------
;If the disk is 2-sided, then set up for the fastest
;step rate.
;NOTE: We assume here that STEP8D=STEP5D=max rate
;  a=d=0 for 1-sided disk, 3 for 2-sided disk
;  c=DCTRL value
;  e=BPS(0:3)
;  hl=PRMTBL(BUFDSK)
;----------------------------------------------------
	ora	a		;2-sided?
	db	JRZ,SDSK7-$-1	;n: leave step rate alone

	mov	a,m
	ani	0FCh		;max step rate
	mov	m,a

SDSK7:	inx	h		;hl=PRMTBL(BUFDSK)+1
;-------------------------------------------------------
;The drive is either a 77-track 8" drive or a 40-track
;5.25" "minidisk" drive. Choose the correct select table
;base by looking at DCMAXI for this drive.
;  c=DCTRL value
;  d=0 for 1-sided disk, 3 for 2-sided disk
;  e=BPS(0:3)
;  hl=PRMTBL(BUFDSK)+1
;-------------------------------------------------------
	dw	BIT4M			;Z set if DCMAXI=0

	mvi	l,SELTBL8 and 0FFh	;Begin 8" entries
	db	JRNZ,SDSK8-$-1		;Z set for minidisk
	mvi	l,SELTBL5 and 0FFh	;Begin 5.25" entries
SDSK8:
;-------------------------------------------------------
;Compute table offset based on sector size and density.
;  c=DCTRL value
;  d=0 for 1-sided disk, 3 for 2-sided disk
;  e=(0:3) for sector size (from call to IDRD0)
;  hl=address of Select table for selected disk size
;    (SELTBL8 for 8" or SELTBL5 for 40-track 5.25").
;    Note that 1024 BPS is illegal for SD disks, and
;    128 BPS is illegal for DD disks. They will
;    therefore select bogus table entries.
;-------------------------------------------------------
	mov	a,c		;DCTRL value 
	ani	DCDDEN		;density is bit 6

	rlc
	rlc
	rlc			;now density's in bit 1

	add	e		;combine with sector size

; a = 0 for SD 128 BPS
; a = 1 for SD 256 bps   a = 3 for DD 256 BPS
; a = 2 for SD 512 bps   a = 4 for DD 512 BPS
;                        a = 5 for DD 1024 BPS

	add	d		;offset for sidedness
;-------------------------------------------
;compute select table address
;  a=offsets for BPS, density, and sidedness
;  e=BPS(0:3)
;  hl=address of base table
;-------------------------------------------
SDSK9:	add	a		;2 bytes per entry
	add	l		;base table
	mov	l,a		;(hl)=correct select table
;----------------------------------------------------------
;Put 'translate vector address' of this kind of disk's into
;this disk'S Disk Parameter Header (DPH). This 'address' is
;actually 2 bytes: the first byte is the page address
;within TPAGE of the correct skew table. The second byte is
;this disk's BPS(0:3)
;  top-of-stack=address of DPH for this drive type
;  e=BPS(0:3)
;  hl=address of select table for this drive
;----------------------------------------------------------
	pop	b		;DPH for selected drive
	push	b		;save DPH address for ret

	mov	a,m		;select table 1st byte
				;..is the skew table offset
	stax	b		;DPH byte 0 is table offset

	inx	b
	mov	a,e		;DPH byte 1 is BPS(0:3)
	stax	b
;=======================================================
;Create Disk Paramater Block (DPB) for this kind of disk
;
;DPBS (DPB Seed table) interpretation:
;  Byte  Name   Function               Interpretation
; 0<1:0> BPB   Bytes/block            00:1024, 01:2048
;                                     10:4096, 11:8192
; 0<2>   DSMH  DSM high byte          1 means +256
; 0<3>   RTOF  Reserved Track Offset  0:2, 1:3
; 0<4>   CKSS  Check Vector Size      0:16, 1:32
; 0<5>   AL01  Allocation Vectors     0:128, 1:192
; 0<7:6> DENT  Directory Entries      01:64, 10:128
;   1    SPT   128-byte Sectors/track SPT (max 255)
;   2    DSML  DSM low byte           DSM mod 256
;
;  top-of-stack=address of DPH for this drive type
;  hl points to this drive's select table entry
;=======================================================
;--------------------------------------------
;Compute this drive type's DPBS table address
;--------------------------------------------
	inx	h		;point to DPBS page address
	mov	l,m		;page address
				;hl=complete DPBS address
;---------------------------------------------
;Get all 3 DPBS table bytes for this disk type
;  b=0
;  hl=DPBS table entry for this drive type
;  Carry is clear
;---------------------------------------------
	mov	c,m		;byte 0 (various bits)
	inx	h
	mov	d,m		;byte 1 (SPT)
	inx	h
	mov	e,m		;byte 2 (DSM low byte)
;-------------------------------------------
;Look up this drive's DPB address in the DPH
;  c=DPBS(BUFDSK) byte 0 (various bits)
;  d=DBPS(BUFDSK) byte 1 (SPT)
;  e=DPBS(BUFDSK) byte 2 (DSM low byte)
;  top-of-stack=address of DPH(BUFDSK)
;-------------------------------------------
	pop	h		;get DPH address
	push	h		;save it for return
	push	b		;c=DPBS entry byte 0

	lxi	b,10		;offset to DPB addr in DPH
	dad	b		;hl points to DPB address

	mov	a,m		;low byte of DPB address
	inx	h
	mov	h,m		;high byte of DPB address
	mov	l,a		;hl=DPB address
;-------------------------------------------
;DPB Bytes 0 and 1
;
;Save 2-byte SPT
;  b=0
;  d=DBPS(BUFDSK) byte 1 (SPT)
;  e=DPBS(BUFDSK) byte 2 (DSM low byte)
;  hl=DPB(BUFDSK) address (SPT low byte 1st)
;-------------------------------------------
	mov	m,d		;SPT low byte

	inx	h		;point to SPT high byte
	mov	m,b		;SPT high byte (Always 0)

	pop	b		;c=DPBS entry byte 0
;------------------------------------
;DPB Byte 2
;
;Calculate and save 1-byte BSH
;    BPB   BSH   Meaning
;    00b    3   1024 BPB
;    01b    4   2048 BPB
;    10b    5   4096 BPB
;    11b    6   8192 BPB
;  c=DPBS(BUFDSK) byte 0 (various bits)
;  e=DPBS(BUFDSK) byte 2 (DSM low byte)
;  hl=DPB(SPT high byte) address
;------------------------------------
	inx	h		;point to BSH

	mvi	d,3		;handy number for a while
	mov	a,c		;DPBS entry byte 0
	ana	d		;bits <1:0> encode BSH
	add	d		;compute BSH

	mov	m,a		;BSH
;------------------------------------
;DPB Byte 3
;
;Calculate and save 1-byte BLM
;    BPB   BLM
;    00b     7
;    01b    15
;    10b    31
;    11b    63
;  a=BSH
;  c=DPBS entry byte 0 (various bits)
;  d=3
;  e=DPBS(BUFDSK) byte 2 (DSM low byte)
;  hl=DPB(BUFDSK+BSH) address
;------------------------------------
	inx	h		;point to BLM
	mov	b,a		;BSH
	xra	a

CDPB01:	stc			;shift in 1's
	ral
	db	DJNZ,(CDPB01-$-1) xor 0FF00h

	mov	m,a		;BLM
;--------------------------------------
;DPB Byte 4
;
;Calculate and save 1-byte EXM
; if DSM<256 then EXM=BLM/8
; else EXM=BLM/16
;  a=BLM
;  c=DPBS(BUFDSK) byte 0 (various bits)
;  d=3
;  e=DPBS(BUFDSK) byte 2 (DSM low byte)
;  hl=DPB(BUFDSK+BLM) address
;--------------------------------------
	inx	h		;point to EXM

	mov	b,d		;3 (divide by 8)
	dw	BIT2C		;bit <2> is DSM high byte
	db	jRZ,CDPB02-$-1
	inr	b		;b=4 (divide by 16)

CDPB02:	ora	a		;clear carry
	rar			;divide BLM by 8 0r 16
	db	DJNZ,(CDPB02-$-1) xor 0FF00h

	mov	m,a		;EXM
;--------------------------------------
;DPB Bytes 5 and 6
;
;Calculate and save 2-byte DSM
;  b=0
;  c=DPBS(BUFDSK) byte 0 (various bits)
;  d=3
;  e=DPBS(BUFDSK) byte 2 (DSM low byte)
;  hl=DPB(EXM) address
;--------------------------------------
	inx	h		;point to DSM low byte
	mov	m,e		;DSM low byte

	inx	h		;point to DSM high byte

	mov	a,c		;bit <2> is DSM high byte
	ani	00000100b	;0 or 4
	rrc
	rrc			;0 or 1

	mov	m,a		;DSM high byte
;--------------------------------------
;DPB Bytes 7 and 8
;
;Calculate and save 2-byte DRM
;  b=0
;  c=DPBS(BUFDSK) byte 0 (various bits)
;  d=3
;  hl=DPB(DSM high byte) address
;--------------------------------------
	inx	h		;point to DRM

	mov	a,c		;bits <7:6> encode DRM
	ani	11000000b
	dcr	a
	mov	m,a		;DRM low byte

	inx	h		;Point to DRM high byte

	mov	m,b		;DRM high byte is 0
;------------------------------------
;DPB Bytes 9 and 10
;
;Calculate and save AL0 and AL1
;  b=0
;  c=DPBS entry byte 0 (various bits)
;  d=3
;  hl=DPB(DRM high byte) address
;------------------------------------
	inx	h		;Point to AL0
	mov	a,c
	ani	00100000b	;bit <5> encodes AL0
	rlc			;0 or 64
	adi	128		;128 or 192
	mov	m,a		;AL0

	inx	h		;AL1 is always 0, for
				;..floppy disks anyway
	mov	m,b		;AL1 is 0
;--------------------------------------
;DPB Bytes 11 and 12
;
;Calculate and save 2-byte CKS
;  b=0
;  c=DPBS(BUFDSK) byte 0 (various bits)
;  d=3
;  hl=DPB(AL1) address
;--------------------------------------
	inx	h		;point to CKS

	mov	a,c		;bit <4> encodes CKS
	ani	00010000b	;0 or 16
	adi	16		;16 0r 32
	mov	m,a		;CKS low byte

	inx	h		;Point to CKS high byte

	mov	m,b		;CKS high byte is 0
;--------------------------------------
;DPB Bytes 13 and 14
;
;Calculate and save 2-byte OFF
;  b=0
;  c=DPBS(BUFDSK) byte 0 (various bits)
;  d=3
;  hl=DPB(CKS high byte) address
;--------------------------------------
	inx	h		;point to OFF

	dw	BIT3C		;Reserved Track Offset
	db	JRNZ,CDPB03-$-1
	dcr	d		;d=2 or 3
CDPB03:	mov	m,d		;OFF low byte

	inx	h		;Point to OFF high byte

	mov	m,b		;OFF high byte is 0
;----------------------------
;Mark this drive as logged in
;----------------------------
	call	PTLU
	dw	RES7M		;clr this drive's PRMDSK
	xra	a		;success: set Z
;--------------------------------------
;SELDSK Exit
; Z set for success, cleared for fail
; top-of-stack = new DPH address
; next-on-stack = original SP
;--------------------------------------
SDEXIT:	pop	d		;DPH address
	db	JRZ,SDEX1-$-1	;Z set means ok
	lxi	d,0		;..and ret with hl=0

SDEX1:	pop	h		;original SP
	sphl
	xchg			;DPH pointer to hl
	ret			;with hl valid

;***BIOS Function**************
;Home Disk Head
;(Set current drive to track 0)
;On Exit:
;  SEKTRK=0
;Trashes psw,c
;******************************
HOME:	mvi	c,0

;Fall into SETTRK

;***BIOS Function*********************
;Set Track
;On Entry:
;  c = desired track
;  BUFTRK = track number in the buffer
;On Exit:
;  SEKTRK = desired track
;Trashes psw
;*************************************
SETTRK:	mov	a,c
	sta	SEKTRK
	ret

;***BIOS Function**************************************
;Set Sector
;On Entry:
;  c = 0-based desired sector, with MSB indicating side
;      (as provided by SECTRAN)
;On Exit:
;  SEKSEC = 0-based sector number
;  SEKSID = SIDE0 or SIDE1 for the side
;Trashes psw
;******************************************************
SETSEC:
;Peel off and save just the sector, removing the side info

	mov	a,c
	ani	7Fh		;strip side indicator
	sta	SEKSEC

;Set SEKSID to SIDE0 for side 0, SIDE1 for side 1
;This assumes SIDE0=0FFH and SIDE1=0FDh

	xra	c		;0 for side 0, 80h for side 1
	rlc
	rlc			;0 for side 0, 02h for side 1
	cma			;a=SIDE0 or SIDE1

	sta	SEKSID
	ret

;***BIOS Function*******
;Set DMA Address
;On Entry:
;  bc = DMA address
;On Exit:
;  DMAADR = DMA address
;***********************
SETDMA:	dw	SBCD,DMAADR	;save bc at DMAADR
	ret

;============================================================
;Logical Sector Routines
;These are external calls to the BIOS, and read or write a
;single logical (SECSIZ-byte) sector on disk at the location 
;set by prior calls to SETDSK, SETTRK, and SETSEC, and in
;memory at the address set by SETDMA.
;READ and WRITE will read from and write to the track buffer
;only. The disk is only accessed when the data already in the
;track buffer is for a different disk/track/side than what
;was specified by the above SET routines.
;Because the track buffer is in a different memory bank, data
;is transfered through the SECSIZ-byte SECBUF.
;When a new track is accessed, the previous track is first
;written back to the disk, if any logical sectors were ever
;written into the track buffer by previous calls to WRITE.
;Then the entire new track is read from disk into the track
;buffer. If the calll is to READ, then the requested logical
;sector is copied from the track buffer to the DMA address.
;If the call is to WRITE, then the data at the DMA address is 
;copied to the right logical sector location in the track
;buffer.
;============================================================

;***BIOS Function********************************************
;Write One Logical Sector
;Transfers one logical sector from CP/M to the track buffer.
;Loads the track buffer from disk prior to tranafering data
;if necessary. Writes the track buffer to disk (flushes)
;prior to reloading if TBSTAT is 0FFh.
;On Entry:
;  TBSTAT = 0FFh if buffer is different than the disk track
;           (and should be flushed if the track changes)
;  DMAADR = target address
;  SEKSEC = 0-based selected translated logical sector number
;  SEKDSK = selected disk
;  SEKTRK = selected track
;  SEKSID = SIDE0 or SIDE1 for the selected side
;  BUFDSK = disk for track in buffer
;  BUFTRK = track number for track in buffer 
;  BUFSID = SIDE0 or SIDE1 for track in buffer 
;On Exit:
;  a=0 and Z set if no errors
;  a=1 and Z clear if unrecovered error
;  de = next track buffer address
;  hl = next DMA address
;  TBSTAT=FFh if the write was succesful, 0 otherwise
;Trashes bc
;Stack depth: 4 (system stack, not including call WRITE)
;            18 (local stack)
;************************************************************
WRITE:	mvi	c,0
	db	11h		;lxi d opcode skips 2 bytes

;Skip into RWLSEC	

;***BIOS Function********************************************
;Read One Logical Sector
;Transfers one logical sector from the track buffer to CP/M.
;If the track in the sector buffer is different than the
;track containing the requested sector, or if TBSTAT=0, then
;the track buffer is first loaded from disk. If TBSTAT = 0FFh
;(indicating that the track buffer is dirty), then the track
;buffer is flushed to disk before it is refilled. Unless
;is a disk error, this exits with TBSTAT=1, indicating valid
;there data in the track buffer.
;On Entry:
;  TBSTAT = 0FFh if buffer is different than the disk track
;           (and will need to be flushed)
;           0 if the buffer data is invalid
;           1 if track buffer is valid and matches the disk
;  DMAADR = target address
;  SEKSEC = 0-based selected translated logical sector number
;  SEKDSK = selected disk
;  SEKTRK = selected track
;  SEKSID = SIDE0 or SIDE1 for the selected side
;  BUFDSK = disk for track in buffer
;  BUFTRK = track number for track in buffer 
;  BUFSID = SIDE0 or SIDE1 for track in buffer 
;On Exit:
;  a=0 and Z set if no errors
;  a=1 and Z clear if unrecovered error
;  de = next DMA address
;  hl = next track buffer address
;  TBSTAT = 1 if the read was succesful, 0 otherwise
;Trashes bc
;Stack depth: 4 (system stack, not including call READ)
;            18 (local stack)
;************************************************************
READ:	mvi	c,1		;1 means read operation

;Fall into RWLSEC

;***Subroutine***********************************************
;Read  or write one logical sector
;On Entry from READ:
;  c=1
;  DMAADR = target address
;On Entry from WRITE:
;  c=0
;  data from DMAADR is in SECBUF
;On Entry:
;  TBSTAT = 0FFh if buffer data different than the disk track
;           0 if the buffer data is invalid
;           1 if track buffer is valid and matches the disk
;  DMAADR = target address
;  SEKSEC = 0-based selected translated logical sector number
;  SEKDSK = selected disk
;  SEKTRK = selected track
;  SEKSID = SIDE0 or SIDE1 for the selected side
;  BUFDSK = disk for track in buffer
;  BUFTRK = track number for track in buffer 
;  BUFSID = SIDE0 or SIDE1 for track in buffer 
;On Exit:
;  a=0 and Z set if no errors
;  a=1 and Z clear if unrecovered error
;  de = next destination address
;  hl = next source address
;  TBSTAT incremented if the disk transfer was succesful,
;         set to 0 otherwise
;Trashes bc
;Stack depth: 4 (system stack)
;            18 (local stack)
;************************************************************
RWLSEC:	lxi	d,SEKSID	;is sector on the same track
	lxi	h,BUFSID	;..as what's in the buffer?
	mvi	b,3		;check side, track, and disk

RWS1:	ldax	d
	xra	m
	db	JRNZ,RWS2-$-1	;flush & load if difference

	inx	d		;cmp SEKTRK & SEKSID
	inx	h		;..to BUFTRK & BUFSID
	db	DJNZ,(RWS1-$-1)xor 0FF00h
				;hl=TBSTAT at end of loop

	xra	m		;TBSTAT = 0 if buffer invalid
				;(clear carry too)
	db	JRNZ,RWS3-$-1	;valid: no flush or load

RWS2:	call	BFLUSH		;n: flush it if needed
				;success sets Z
				;error exit w/ Z cleared, a=1

	cz	RDTRAK		;go read the new track
				;success: TBSTAT=1, a=0,
				;Z set, carry clear

	rnz			;BFLUSH or RDTRAK error
				;(a=1, TBSTAT=0)

	mov	b,a		;b=0
RWS3:
;------------------------------------------------------
;Locate the desired SECSIZ-byte logical sector within
;the track buffer. (This code assumes that SECSIZ=128.)
;  The correct track is in the track buffer
;  b=0
;  c=0 for write, 1 for read
;  carry cleared
;  TBSTAT=1 or FF
;------------------------------------------------------
	lda	SEKSEC		;0-based logical sector
	rar			;to build buffer offset
	mov	d,a
	mov	a,b		;b=0: catch lsb
	rar
	mov	e,a		;de = SEKSEC*128 = offset
	lxi	h,TRKBUF
	dad	d		;hl=logical sector address

	dw	LDDE,DMAADR	;de=DMA address

if BANKED
;------------------------------------------------------------
;Use intermediate buffer to move logical sector:
;track buffer <--> SECBUF <--> DMA address
;  b=0
;  c=0 for write, 1 for read
;  de=DMA address
;  hl=logical sector address in the track buffer
;Note: no stack usage allowed when TBBANK is selected
;------------------------------------------------------------	
	dcr	c		;Z set if read, c=FF if write
	mov	a,c		;a=FF if write

	mvi	c,SECSIZ	;bc=byte count
	db	JRZ,RWS4-$-1	;z set for read

;Write via the sector buffer: DMAADR-->SECBUF
;  a=FFh
;  bc=SECSIZ
;  de=DMA address
;  hl=logical sector address in track buffer
	sta	TBSTAT		;mark track buffer as dirty
	xchg			;de=track buffer address,
				;hl=DMA address

	mvi	a,TBBANK	;switch track buffers in
				;after 1st LDIR
	db	JR,RWS5-$-1	;move sector out of SECBUF

;Read via the sector buffer: track buffer-->SECBUF
;  bc=SECSIZ
;  de=DMA address
;  hl=logical sector address in track buffer
RWS4:	mvi	a,TBBANK	;switch track buffers in
	out	BBANK		;for 1st LDIR

	mvi	a,OSBANK	;switch track buffers out
				;after 1st LDIR

;initial transfer of logical sector into SECBUF
;  a=correct bank address for after LDIR
;  bc=SECSIZ
;  de=2nd transfer destination address
;  hl=1st transfer source address

RWS5:	push	d		;final destination address
	lxi	d,SECBUF	;1st transfer destination
	push	d		;SECBUF
	dw	LDIR		;(hl) --> SECBUF
	pop	h		;SECBUF
	pop	d		;recover final destination

;Final transfer logical sector from SECBUF
;  a=correct bank address for 2nd transfer
;  de=2nd transfer destination address
;  hl=SECBUF (2nd transfer source address)
	out	BBANK

	mvi	c,SECSIZ	;bc=byte count
	dw	LDIR		;SECBUF --> (DMAADR)
				;or SECBUF --> track buffer

	mvi	a,OSBANK	;switch track buffer back out
	out	BBANK

	xra	a		;indicate no errors
	ret

 if not BANKED
;-----------------------------------------------
;Direct transfer track buffer <--> DMA address
;  b=0
;  c=0 for write, 1 for read
;  de=DMA address
;  hl=logical sector address in the track buffer
;-----------------------------------------------
	dcr	c		;Z set: read, c=FFh if write
	db	JRZ,RWS7-$-1	;go transfer sector
				;buffer-->DMAADR

	mov	a,c		;a=FF
	sta	TBSTAT		;mark track buffer as dirty
	xchg			;Write: DMA adr is the source
				;DMAADR-->track buffer

RWS7:	mvi	c,SECSIZ	;bc=byte count
	dw	LDIR		;SECBUF --> (DMAADR)

	xra	a		;indicate no errors
	ret
 endif	;not BANKED

;============================================================
;Buffer <-> Track Routines
;These routines read or write entire sectors from/to disk,
;to/from the track buffer. The transfer is done at disk
;speed,starting with the first sector encountered on the
;disk. Thus, the transfer will take just one disk revolution
;time plus the necessary seek time. Read or write failures
;are retried several times before giving up.
;============================================================

;***Subroutine**********************************************	
;Write buffer to disk track if necessary, invalidate buffer
;On Entry:
;  TBSTAT = 0FFh if any writing is needed
;           0 or 1 if no flush required
;  BUFDSK = disk number for track buffer data
;  BUFTRK = track number for track buffer data
;  BUFSID = SIDE0 or SIDE1 for buffer 
;  BUFSPT = SPT for track buffer data
;  BDCTRL = DCTRL value for track buffer, with auto-wait set
;On Exit:
;  a=0 and Z set if success
;  a=1 and Z clear if fatal disk error
;  carry clear
;  TBSTAT=0 (buffer is invalid)
;  Buffer is written to selected track if needed
;Stack depth: 18 (local stack)
;***********************************************************
BFLUSH:	push	h

	lxi	h,TBSTAT
	inr	m			;was TBSTAT FF?

	mvi	a,CCWRSEC		;write cmd, side 0
	db	JRZ,RWTRAK-$-1		;y:go flush buffer

	xra	a			;n: nothing to flush
	mov	m,a			;buffer is invalid
	pop	h
	ret

;***Subroutine***********************************************	
;Read entire track into buffer
;On Entry:
;  SEKSID = SIDE0 or SIDE1 for track to be read
;  SEKTRK = track number for track to be read
;  SEKDSK = disk to read
;On Exit:
;  a=0 and Z set if success
;  a=1 and Z clear if fatal error
;  carry clear
;  TRKBUF is loaded with selected track
;  BUFSID = SEKSID
;  BUFTRK = SEKTRK
;  BUFDSK = SEKDSK
;  TBSTAT = 1 on success, 0 on failure
;  BDCTRL is correct w/ auto-wait set, for the track we read
;Stack depth: 18 (local stack)
;************************************************************
RDTRAK:	push	h

;Update track buffer variables to reflect new track, and
;set TBSTAT to 1, indicating a (hopefully) successful read

	lhld	SEKSID			;SEKSID & SEKTRK
	shld	BUFSID			;BUFSID & BUFTRK
	lhld	SEKDSK			;SEKDSK & 01h
	shld	BUFDSK			;BUFDSK & TBSTAT

	mvi	a,CCRDSEC		;read command, side 0

;Fall into RWTRAK

;***Subroutine end*******************************************
;Read or write an entire track with physical sector retries
;Use local stack, due to deep stack usage
;On Entry:
;  a = WD1793 read or write command (CCRDSEC or CCWRSEC)
;      (set up to verify side 0)
;  BUFDSK = Drive number for track read or write
;  BUFTRK = track number for track read or write
;  BUFSID = SIDE0 or SIDE1 for track read or write
;  BDCTRL = DCTRL value for track buffer data for writes only
;  TBSTAT = 1 if reading, 0 if writing
;On Exit:
;  BDCTRL = DCTRL value for track buffer, with auto-wait set
;  a=0 and Z set if successful
;  a=1 and Z cleared if seek, read or write failure
;  If track write then BUFTRK=0 (to force a reload next read)
;  TBSTAT = 0 if successful write or read/write error
;         = 1 if successful read
;  carry clear
;Stack depth: 18 (local stack)
;************************************************************
RWTRAK:	lxi	h,0		;(10)use local stack
	dad	sp		;(11)
	lxi	sp,STACK	;(10)
	push	h		;(11)save incoming sp
	push	d		;(11)save regs on local stack
	push	b		;(11)

	mov	b,a		;(4)remember read/write cmd
;------------------------
;Seek the requested track
;------------------------
	call	SEEKID		;seek & ID track, set side
				;e=BPS(0:3)
				;Carry is clear
				;ADCMD=BUFSID
				;IDSECT=found sector
				;BUFSPT is valid

	mov	d,b		;in case of error
	jnz	RWFAIL		;Hard seek error?
;---------------------------------------------------
;Combine command with the disk side for verification
;  d=read or write command, with F2SCS1 bit cleared
;  e=BPS(0:3)
;  Carry is clear
;---------------------------------------------------
	lda	BUFSID		;FF for SIDE0, FD for SIDE1
	cma			;00: side 0, 02: side 1
	rlc			;keeps carry clear
	rlc			;00: side 0, 08: Side 1
				;(F2SCS1 set for side 1)
	ora	d		;Combine with command
	mov	d,a
;----------------------------------------------
;Get this track's physical SPT found by SEEKID
;  d=read or write command with side compare
;  e=BPS(0:3)
;  carry is clear
;----------------------------------------------
	lda	BUFSPT		;physical SPT for RWTRAK loop
	mov	b,a		;for loop counter
;---------------------------------------------------------
;Get the sector number found by SEEKID as a starting point
;  carry is clear
;---------------------------------------------------------
	lda	IDSECT		;sector found by ID read
	mov	c,a
;---------------------------------------------------
;Calculate hl = sector buffer address
;             = TRKBUF+c*SECSIZ*2^e
;At the same time, compute e=BPS/4 for disk accesses
;  carry is clear
;  a=sector found by IDSECT (same as c)
;  b=SPT (for loop counter)
;  c=sector found by IDSECT (same as a)
;  d=read or write command with side compare
;  e=BPS(0:3)
;  BUFSPT=SPT (for max sector number)
;--------------------------------------------------
	push	d

	rar			;(carry was clear)
	mov	h,a
	mvi	a,0		;catch carry
	rar
	mov	l,a		;hl=c*128

	mvi	a,10h		;sector base length/2

	inr	e		;BPS(1:4)

	db	CPI		;skip first dad

BALOOP:	dad	h		;calculate hl=C*128*2^(e-1)
	add	a		;Compute a=BPS/4
	dcr	e
	db	JRNZ,(BALOOP-$-1) xor 0FF00h

 if BANKED ;(TRKBUF is on a page boundary and e=0 here)
	mvi	d,TRKBUF/256
 endif ;BANKED

 if not BANKED
	lxi	d,TRKBUF
 endif ;not BANKED

	dad	d		;hl=buffer address

	pop	d

	mov	e,a		;e=BPS/4
;------------------------------------------------------------
;Loop to read/write BUFSPT sectors (the whole track) starting
;after sector c (the sector found by IDREAD).
;Some 5.25" "minidisks" (e.g. the Magnetic Periperals 9428)
;take so long to complete their trim-erase and then turn the
;write channel off and the read channel on that it is
;impossible to write back-to-back sectors: the drive cannot
;finish writing in time to turn around and read the next
;sector header. So in the case where we are writing to a
;minidisk (DCMAXI=0), we interleave the write, writing every
;other sector. In all other cases, we read and write with no
;interleave.
;  b=sector loop counter
;  c=previous physical sector
;  d=read or write command with side compare
;  e=BPS/4 (0 means 256)
;  hl=DMA address for end of the previous sector
;  BUFSPT = max sector number
;------------------------------------------------------------
SECLUP:	mvi	a,RWTRYS+1	;(7)reset retry counter
	sta	RCOUNT		;(13)..for the sector r/w

 if INTRLV
;If we are writing to a minidisk the we will interleave
;2:1. All other cases, no interleave. Some 5.25" "minidisks"
;(e.g. the Magnetic Periperals 9428) take so long to complete
;their trim-erase and then turn the write channel off and the
;read channel on that it is impossible to write back-to-back
;sectors: the drive cannot finish writing in time to turn
;around and read the next sector header. So in the case where
;we are writing to a minidisk (DCMAXI=0), we interleave the
;write, writing every other sector.
	inr	c		;(4)next sector

	lda	BDCTRL		;(13)minidisk?
	cma			;(4)so 1 means mini
	ani	DCMAXI		;(7)this is bit 4
	rlc			;(4)-DCMAXI to bit 5
	ana	d		;(4)mini and writing?
				;d bit 5 is 1 iff write

	lda	BUFSPT		;(13)
	db	JRZ,NOINTL-$-1	;(13)N: no interleave

;Writing to a minidisk: 2:1 interleave
;  a=BUFSPT
;  b=sector loop counter
;  c=previous sector+1
;  d=read or write command with side compare
;  e=BPS/4 (0 means 256)
;  hl=DMA address for end of the previous sector
	inr	c		;interleave 2
	cmp	c		;sector number we just did
	db	JRNC,SKPSEC-$-1	;wrapping?

;Wrap to the next 2:1 interleaved sector, either sector 1
;or sector 2, depending on whether we just wrote to an
;even or odd sector. Note: if the disk has an even number
;of sectors/track, then we will go from sector (e.g.) 10
;to sector 1, which is the very next sector. This will
;cause us to slip a rev when we wrap. Harmless, but makes
;the write take 3 revs.
	mov	a,c
	ani	01h		;a=1 if last sector was odd
				;clears carry
	mov	c,a
	inr	c		;next sector is 1 or 2

	lxi	h,TRKBUF	;wrap to beginning of buf
	rar			;carry=1 if next sector is 2
	db	JRNC,NOINTL-$-1	;sector 1 next?

;Skip a sector in TRKBUF by adding BPS to DMA address
;  b=sector loop counter
;  c=the next sector number
;  d=read or write command with side compare
;  e=BPS/4, with 0 meaning 256
;  hl=next TRAKBUF address without interleave
SKPSEC:	push	d
	xra	a
	mov	d,a
	cmp	e		;e=0 means 256 BPS/4
	db	JRNZ,SKPSC1-$-1
	inr	d		;de=256
SKPSC1:
	dad	d		;de=BPS/4
	dad	d		;..so add 4 of them to
	dad	d		;..compute sector 2 address
	dad	d

	pop	d
	mov	a,c		;force jrnc to DOSEC below

;Not writing on a minidisk, so no interleave. Wrap to
;sector 1 if we just did the highest sector.
;  a=BUFSPT
;  b=sector loop counter
;  c=previous sector+1
;  d=read or write command with side compare
;  e=BPS/4, with 0 meaning 256
;  hl=next TRAKBUF address without interleave
NOINTL:	cmp	c		;(4)Wrap?
	db	JRNC,DOSEC-$-1	;(7)
	mvi	c,1		;(7)wrap to sector 1
	lxi	h,TRKBUF	;(11)
DOSEC:
 endif ;INTRLV

 if not INTRLV
;Compute next sector number, and wrap
;the buffer address if needed
	lda	BUFSPT		;(7)wrap?
	sub	c		;(4)sector number we just did
	db	JRNZ,SECLP2-$-1	;(7)
	mov	c,a		;(4)wrap to sec 0 (becomes 1)
	lxi	h,TRKBUF	;(11)wrap to beginning of buf
SECLP2:	inr	c		;(4)next sector
 endif ;not INTRLV

;Read or write a sector
;  b=sector loop counter
;  c=sector number
;  d=read or write command with side compare
;  e=BPS/4, with 0 meaning 256
;  hl=DMA address
SRETRY:	push	b		;(11)sector & loop count
	push	h		;(11)Buffer address for retry

	mov	a,c		;(4)sector number
	out	DSCTR		;(11)WD1793 sector port

	mov	b,e		;(4)RDPSEC/WRPSEC byte count

	call	RWPSEC		;(17)read or write sector
				;returns with a=0 on success
				;...& WD1793 status on error
	db	JRNZ,RWSERR-$-1	;(7)retry on disk error
				;a=0, z set on success

;Good sector read/write. Next physical sector
;Wrap to beginning of track when necessary. This will occur
;exactly once per track (regardless of which sector is first)
;  a=0
	pop	b		;(10)chuck old buffer address
	pop	b		;(10)sector & loop count

	db DJNZ,(SECLUP-$-1) XOR 0FF00h ;(7) More sectors?

;If this was a write, then delay for ~570 uS from the end of
;the write to the return from this subroutine, time for
;the trim-erase to turn off before moving the head. (It will
;take at least an additional 30 uS before a seek or
;restore operation can occur, so we are allowing for at least
;600 uS trim-erase time.)
;  a=0
;  d=read or write command (with side compare)
	dw	BIT5D		;(8)write command?
	db	JRZ,RWEXIT-$-1	;(7)n: no delay needed
				;a=0 here

;CPUMHZ is: 26 for 2 MHz, 52 for 4 MHz
	lda	CPUMHZ		;(13)

WDELAY:	call	RWRET		;(27)stall
	dcr	a		;(4)
	db JRNZ,(WDELAY-$-1) XOR 0FF00h ;(12)

;Fall into RWEXIT with Z set and a=0 for success

;--------------------------------------------------------
;Repair stack and registers
;  a=0 and Z set for success, a=1 and Z clear for fail
;--------------------------------------------------------
RWEXIT:	pop	b	;(10)restore original registers
	pop	d	;(10)
	pop	h	;(10)restore incoming sp
	sphl		;(6)

	pop	h	;(10)restore incoming hl
RWRET:	ret		;(10)
;----------------------------------------------------
;Retry the same sector if not too many retries aready
;  a=status from read/write operation
;  d=read or write command
;----------------------------------------------------
RWSERR:	mvi	l,WPMSG and 0FFh ;Write protected
	dw	BIT6A		;write protect error?
	jnz	MWBOOT		;y: tell user, reboot

	lxi	h,RCOUNT
	dcr	m		;bump retry counter

	pop	h		;retry RAM address
	pop	b
	db	JRNZ,(SRETRY-$-1)XOR 0FF00h
;-------------------------------------------------
;Fail: too many retries. If this is a write, then
;print track-write failure message before quitting.
;Then invalidate buffer, set a=1, clear Z
;  d=read or write command
;-------------------------------------------------
RWFAIL:	mvi	l,WFMSG and 0FFh ;'Drive x write error'
	dw	BIT5D		;Write command?
	cnz	PMSGP		;y: print that message

	xra	a
	sta	TBSTAT		;track buffer is invalid now
	inr	a		;Z clear, a=1	
	db	JR,(RWEXIT-$-1)XOR 0FF00h

;============================================================
;Physical Sector Routines
;These routines read or write a single physical sector from/
;to disk, to/from a specified place in the track buffer.
;Also, IDREAD (and its variants) will read just the 6 ID
;bytes from the next sector found on the disk, parking them
;in IDSAVE.
;============================================================

;***Subroutine********************************************
;Read or write one physical sector
;On Entry:
;  b=BPS/4 (2 for IDREAD)
;  d = WD1793 command (with side compare on read or write)
;  hl=DMA address
;  DSCTR register in 1793 = sector to read/write
;  BDCTRL = DCTRL value with Auto-wait enabled
;On Exit:
; Z set and a=0 if success
; Z clear and a=error flags if error detected
;  hl=next DMA address
;Trashes bc
;Stack depth: 0
;*********************************************************
RWPSEC:

 if BANKED
	mvi	a,TBBANK	;(7)select track buffer bank
	out	BBANK		;(11)
 endif ;BANKED

	lda	BDCTRL		;(13)select, density, size,
				;..autowait
	out	DCTRL		;(11)output DCTRL value

	mvi	c,DDATA		;(7)port address
	mov	a,d		;(4)read or write sector cmd
	dw	BIT5A		;(7)Write command?
	out	DCMMD		;(11) issue WD1793 command
	db	JRZ,RDPSEC-$-1	;(12)

;Fall into WRPSEC

;***Subroutine*****************************************
;Write one physical sector
;Almost as fast as possible: 39 cycles in-to-in (19.5 uS
;at 2MHz, 9.75 uS at 4 MHz). This is too slow for any DD
;maxi disk with a 2 MHz CPU.
;On Entry:
;  write sector command has been written do DCMMD
;  b=byte count/4 (1024 BPS encoded as 0)
;  c=DDATA (port address)
;On Exit:
; Z set and a=0 if success
; Z clear and a=error flags if error detected
;  hl=next DMA address
;Trashes bc
;Stack depth: 0
;******************************************************
WRPSEC:	in	DFLAG	;(11) autowait
	dw	OUTI	;(16)
	inr	b	;(4) undo 'dcr b' in OUTI
	in	DFLAG	;(11) autowait
	dw	OUTI	;(16)
	inr	b	;(4) undo 'dcr b' in OUTI
	in	DFLAG	;(11) autowait
	dw	OUTI	;(16)
	inr	b	;(4) undo 'dcr b' in OUTI
	in	DFLAG	;(11) autowait
	dw	OUTI	;(16)
	db	JRNZ,(WRPSEC-$-1) xor 0FF00h
			;(12) z set/cleared by OUTI

;Check for all the possible errors during a sector write
	mvi c,SNOTRDY+SRNFER+SCRCERR+SLOSTD+SWFALT+SWRPROT

	db	JR,EORW-$-1	;wait for completion,
				;get WD1793 status
				;return from there
;***Subroutine*******************************************
;Read one physical sector
;Almost as fast as possible: 39 cycles in-to-in (19.5 uS
;at 2MHz, 9.75 uS at 4 MHz). This is too slow for any DD
;maxi disk with a 2 MHz CPU.
;On Entry:
;  read sector or read address command has been
;     issued to the WD1793 (DCMMD)
;  b=byte count/4 (1024 BPS encoded as 0)
;  b=RABC (=2) for read address command
;  c=DDATA (port address)
;On Exit:
; Z set and a=0 if success
; Z clear and a=error flags if error detected
;  hl=next DMA address
;Trashes bc
;Stack depth: 0
;*******************************************************
RABC	equ	2	;Byte count for read address command

RDPSEC:	dw	BIT1B	;(8)Read address?
	db	JRNZ,RDPS2-$-1 ;(12)

RDPS1:	in	DFLAG	;(11) autowait
	dw	INI	;(16)
	inr	b	;(4) undo 'dcr b' in INI
	in	DFLAG	;(11) autowait
	dw	INI	;(16)
	inr	b	;(4) undo 'dcr b' in INI
RDPS2:	in	DFLAG	;(11) autowait
	dw	INI	;(16)
	inr	b	;(4) undo 'dcr b' in INI
	in	DFLAG	;(11) autowait
	dw	INI	;(16)
	db	JRNZ,(RDPS1-$-1) xor 0FF00h
			;(12) z set/cleared by INI

;Check for all the possible errors during a sector read
;or a read address
	mvi	c,SNOTRDY+SRNFER+SCRCERR+SLOSTD+SRCTYF

; Fall into EORW with b=0 and c=error mask

;***Subroutine******************************************
;Wait for read/write command to complete
;timeout after 33/4*256=2.1 mS (4.2 mS at 2 MHz)
;On Entry:
;  b = 0
;  c = error mask
;On Exit:
; Z set and a=0 if success
; Z clear and a=error flags if error detected
; Rude exit to DNRERR if drive not ready (Goes to WBOOT)
;trashes a,b
;*******************************************************
EORW:

 if BANKED
	mvi	a,OSBANK	;deselect track buffer bank
	out	BBANK
 endif ;BANKED

EORW1:	dcr	b		;(4)timeout?
	db	JRZ,EORW2-$-1	;(7)y: drive not ready

	in	DFLAG		;(11)Wait for interupt bit
	rrc			;(4)1793 EOJ flag set?
	db	JRNC,(EORW1-$-1) xor 0FF00h
				;(7)y: n: keep waiting
	in	DSTAT		;(11)
	ana	c		;(4)error mask
	rp			;(11)ret w/ any err but dnr

EORW2:	jmp	DNRERR		;drive not ready?

;***Subroutine**********************************************
;Read current track ID using WD1793 'read address' command
;to find its track number and bytes per sector (BPS), and
;to verify the correct side.  Then look up its sectors per
;track (SPT).
;On Entry:
;  BUFDSK = disk to ID
;  Disk is selected
;Entry at IDRD0 sets BUFSID to SIDE0 and waits for the
;  drive to be ready
;Entry at IDRDRA:
;  a=number of retries
;On Success Exit:
;  a=0
;  d=track number found
;  e=IDSSIZ=BPS(0:3)
;  hl=PRMTBL(BUFDSK)
;  Z set
;  Density set for this track in DCTRL byte of PRMTBL
;  BDCTRL = DCTRL value for this drive with auto-wait set
;  BUFSPT = sectors per track for this track
;  Drive is selected
;  ADCMD set up with side specified in BUFSID
;  DTRCK matches the found track number
;  IDSAVE byte s are current for the current track
;  if new drive, WD1793's DTRCK reg = track found
;On Error Exit:
;  a=FF
;  Z cleared
;Stack depth: 8
;***********************************************************
IDRD0:	mvi	a,SIDE0
	out	ADCMD		;select side 0

IDREAD:	mvi	a,RATRYS*2	;set up retry counter

IDRDRA:
				;(2x for density tries)
IDRTRY:	sta	RCOUNT

	call	TRAKID
	db	JRNZ,IDRDER-$-1	;deal with read error on ID
				;(may need to toggle density)
				;a=found track number
				;hl=PRMTBL(BUFDSK)

	out	DTRCK		;set track reg
	mov	d,a		;d=track number for exit
	lda	IDSSIZ		;BPS(0:3) from 'read address'
	mov	e,a		;e=BPS(0:3) for exit
;--------------------------------------------
;Look up sectors per track for this track
; based on disk size, density, and BPS
;d=track number
;e=BPS(0:3)
;hl=PRMTBL(BUFDSK)
;--------------------------------------------
	push	h

 if DRIV35
	mvi	a,(T330 and 0FFh)-2 ;-2 because DD
	dw	BIT4M		;test PRM35: 3.5" drive?
	db	JRNZ,IDRD3-$-1
 endif ;DRIV35

 if HDRIVE
	dw	BIT3M		;test PRMHD: HD drive?
	mvi	a,(T826 and 0FFh)-2
	db	JRNZ,IDRD3-$-1
 endif ;HDRIVE

 if QDRIVE
	dw	BIT2M		;TEST PRMQD: QD drive?
	mvi	a,(T518 and 0FFh)-2
	db	JRNZ,IDRD3-$-1
 endif	;QDRIVE

;it's either a minidisk or an 8" disk
	inx	h		;point to DCTRL value
	dw	BIT4M		;DCMAXI?
	mvi	a,T518 and 0FFh	;5.25" drive?
	db	JRZ,IDRD2-$-1
 	mvi	a,T826 and 0FFh	;8" drive
IDRD2:

;Minidisk or an 8" disk: Adjust for density
	dw	BIT6M		;test DCDDEN: double density?
	db	JRZ,IDRD3-$-1
	sui	2		;y:adjust bps pointer

;Combine BPS from e with table page address in a
IDRD3:	add	e		;combine with DD offset if DD
	add	e		;2-bytes per entry

;Point hl to correct translate table entry, and get the SPT
	mov	l,a		;hl=addr of correct table

	mov	a,m		;get actual SPT
	sta	BUFSPT	

	pop	h		;hl=PRMTBL(BUFDSK)
;---------------------------
;Normal exit with a=0, Z set
;---------------------------
	xra	a		;a=0, Z set
	ret			;Normal exit
;----------------------------------------------------------
;Error during 'read address'. Toggle density and try again.
;  hl=PRMTBL(BUFDSK)
;----------------------------------------------------------
IDRDER:	inx	h		;PRMTBL(BUFDSK)+1 = DCTRL val
	mvi	a,DCDDEN	;toggle density in PRMTBL
	xra	m
	mov	m,a
	dcx	h		;PRMTBL(BUFDSK) for return

	lda	RCOUNT
	dcr	a		;bump retry counter
	rm			;too many tries: Z clear

	db	JR,(IDRTRY-$-1)XOR 0FF00h

;***Subroutine*********************************************
;Determine the drive and disk type, and set up PRMTBL
;
;(Note that some drives will report 'not ready' if you
;select them with side 1 when a 1-sided disk is in.)
;
;DCMAXI must be off until we know whether or not this drive
;is on the 50-pin interface, since drives on the 34-pin
;interface will never signal Ready if DCMAXI is asserted.
;The Ready input is the logical OR of:
; 1. READY from the 50-pin interface connector
; 2. DCMAXI bit = 0 (MINI selected)
; (There is no connection to the 34-pin interface's ready
; signal.)
;The head must be loaded for all operations here, because
;the 16FDC and 64FDC deassert all drive select signals when
;the head load signal is deasserted.
;On Entry:
;  BUFDSK specifies the disk
;  initialized PRMTBL entry is set up for minidisk mode
;On Success Exit:
;  a=0, Z set if no errors
;  hl=PRMTBL(BUFDSK)
;  DCTRL value in PRMTBL(BUFDSK) is set up
;  ADCMD is written for side 0
;  Step rate in PRMTBL is set up
;  PRMTBL(BUFDSK)+0, bit 6 (whih is PRMDRV) is cleared
;  The head is on track 0 or 3
;On Error Exit (track 0 not found, no index pulse, etc.)
;  Z cleared
;  a<>0
;  hl invalid
;Trashes bc,de
;Stack depth: 8
;**********************************************************
DRIVID:	mvi	c,RSTRCM+HDLOAD	;restore with head loaded
	call	T1DCMD		;Restore is a type I command
				;hl=PRMTBL(BUFDSK)
				;trashes de, select side 0
				;return disk status
	ani	SSEEKER		;error seeking track 0?
	rnz			;error exit
;----------------------------------------------------
;Measure the index pulse rate to help determine which
;type of drive we have. (Accuracy is not important.)
;Wait for a leading edge, then time to the next edge.
;  hl=PRMTBL(BUFDSK)
; The above Restore command routed the disk drive's
;   index signal to the WD1793's DSTAT register
;----------------------------------------------------
	call	TINDEX
	db	JRZ,INDTO-$-1	;Z set means error
				;result in d
;Nominal de values:
;             2 MHz          4 MHz
; CPUMHZ     26 (1Ah)       52 (34h)
; 360 RPM  6289 (1891h)   12,579 (3123h)
; 300 RPM  7547 (1D7Bh)   15,094 (3AF6h)
	lda	CPUMHZ		;calculated in BOOT


	cmp	d		;compare to timer result

;carry is set if this is a 300 RPM disk.

 if not DRIV35 and not QDRIVE and not HDRIVE
	mvi	m,STEP5+PRMDSK	;assume 40-track 5.25 for now
	db	JRC,DID3-$-1	;40-track minidisk?

;Fall into DID2 for 8" drives
 endif ;not DRIV35 and not QDRIVE and not HDRIVE

 if DRIV35 or QDRIVE or HDRIVE
	db	JRNC,DID2-$-1	;360 RPM drive?
 endif ;DRIV35 or QDRIVE or HDRIVE

 if DRIV35
;-----------------------------------------------
;Try reading a non-system track to see if its
;high data rate (implying 3.5") or low data rate
;-----------------------------------------------
	mvi	a,3		;seek past system tracks
	call	SEEKA		;(does not ID target track)
				;hl=PRMTBL(BUFDSK)

	call	IDRD0		;try to read track 3 ID
				;Z set if success (mini)
				;hl=PRMTBL(BUFDSK)
	db	JRZ,DID1-$-1	;success means mini or QD

;-----------------------------------
;It's either a 3.5" drive or a fail.
;ID the track to find out.
;-----------------------------------
	inx	h		;point to byte 1
	dw	SET4M		;turn on DCMAXI

	call	IDRD0		;try to read track 3 ID
				;..at high "maxi" data rate
				;a=0, Z set if success (3.5")
				;hl=PRMTBL(BUFDSK)
	db	JRNZ,DIFAIL-$-1	;Z clear: fail
;-----------------
;It's a 3.5" drive
;  a=0
;  Z set
;-----------------
	mvi	m,STEP35+PRM35+PRMDSK
	ret	;success means 3.5"
DID1:
 endif ;DRIV35
 if QDRIVE
;;------------------------------------------------------
;Determine whether this is a 40-track 'minidisk' drive
;or an 80-track 'quad density' drive by seeking past
;the last minidisk track. We don't care which side
;here, because 80-track drives are always 2-sided.
;  hl=PRMTBL(BUFDSK)
;  Density was set by IDRD0 above
;------------------------------------------------------
	mvi	m,STEP5Q+PRMQD+PRMDSK
	mvi	a,TPD525+1	;seek 2 past a 40-track
				;..drive's track (This
				;must be an odd number)
	call	ENDSEK		;Does this track exist?
	rz			;y: 80-track drive (a=0)
 endif ;QDRIVE

 if DRIV35 or QDRIVE or HDRIVE
;-------------------------
;It's a 40-track minidisk.
;-------------------------
	mvi	m,STEP5+PRMDSK	;40-track drive
	db	JR,DID3-$-1	;success exit
 endif ;DRIV35 or QDRIVE or HDRIVE

;----------------------------------------------------
;360 RPM drive: either 5.25" HD or 8" (PerSci or not)
;  hl=PRMTBL(BUFDSK)
;----------------------------------------------------
DID2:	inx	h		;DCTRL value is byte 1
	dw	SET4M		;Set DCMAXI in PRMTBL entry
	dcx	h		;PRMTBL(BUFDSK)+0
 if PERSCI
;-------------------------------------------
;It's either an 80-track HD drive or its an
;8" drive of some sort. Test for a voicecoil
;drive first, before trying to seek.
;  hl=PRMTBL(BUFDSK)
;-------------------------------------------
	mvi	m,PRMVC+PRMDSK	;assume voicecoil for now
				;Clears PRMDRV too

	in	ASTAT		;Seek complete?
	ani	BSIP
	rz			;y: voicecoil drive
				;(a=0, Z set)
;Not a voicecoil drive.
 endif ;PERSCI

 if HDRIVE
;--------------------------------------------
;The drive is either a 77-track non-voicecoil
;8" drive or an 80-track HD drive. (Both are
;360 RPM.) Seek track 77 to find out which.
;  hl=PRMTBL(BUFDSK)
;--------------------------------------------
	mvi	m,STEP5H+PRMHD+PRMDSK
	mvi	a,TPD8		;seek past a 77-track
				;..drive's last track
				;(must be an odd number)
	call	ENDSEK		;Does this track exist?
	rz			;y:HD disk is set up

 endif ;HDRIVE
;---------------------------------------
;It's a 77-track non-voicecoil 8" drive.
;  hl=PRMTBL(BUFDSK)
;---------------------------------------
	mvi	m,STEP8		;SA801 step rate
;--------------------
;DRIVID success exits
;--------------------
DID3:	xra	a		;success: set Z
	ret
;------------------
;DRIVID Error Exits
;------------------
;Error: can't read track 3
; a=0FFh
; hl=PRMTBL(BUFDSK)

DIFAIL:	inx	h		;PRMTBL(BUFDSK)+1
	dw	RES4M		;turn DCMAXI bit off again

;Timeout waiting for index

INDTO:	dcr	a		;clear Z, a<>0 for error
	ret			;Timeout

;***Subroutine********************************************
;Read current track ID using WD1793 'read address'
;comman.
;On Entry:
;  BUFDSK = disk to ID
;  BCTRL is set up
;  Head is on desired track
;  PRMTBL(BUFDSK) specifies the drive and its density
;On Success Exit:
;  Z set
;  a=track number found
;  hl=PRMTBL(BUFDSK)
;  BDCTRL = DCTRL value for this drive with auto-wait set
;  Drive is selected
;  IDSAVE bytes are current for the current track
;On Error Exit:
;  Z cleared
;Trashes de
;Stack depth: 6
;*********************************************************
TRAKID:	call	WATRDY		;wait for drive to be ready
				;trashes de
;---------------------------------------------------
;Look up this drive's DCTRL value, set its auto-wait
;bit, and save the result for RWPSEC
;---------------------------------------------------
	push	b
	call	PTLU		;hl=PRMTBL(BUFDSK)
				;a=DCTRL value for this drive
				;hl=PRMTBL(BUFDSK)

	ori	DCAUTOW		;enable auto-wait
	sta	BDCTRL		;..for RWPSEC
;---------------------------------------------------
;Set up and use RWPSEC to read the track ID into RAM
;at IDSAVE. Always include the head-load delay since
;this only gets called prior to a track read or
;write - almost always on a new track.
;  hl=PRMTBL(BUFDSK)
;---------------------------------------------------
	push	h		;PRMTBL pointer for exit
	lxi	h,IDSAVE	;put 'read addr' data  here

	mvi	b,RABC		;set up to read data 6 bytes
	mvi	d,CCRDADD	;Read addr w/ head load delay

	call	RWPSEC		;On return, Z means good read

	pop	h		;hl=PRMTBL(BUFDSK)
	pop	b

	rnz			;fail: Z cleared
;-----------------------------------------------
;Get the found track number and set the WD1793's
;track register. Return wth Z (still) set.
;-----------------------------------------------
	in	DSCTR		;track # from 'read address'
				;The 1793 put it in its
				;..sector reg
	ret			;Z set: success
				;a=track number

;============================================================
;Head Movement Routines: Seeks, Steps, Restores
;============================================================

 if QDRIVE or HDRIVE
;***Subroutine*********************************************
;Note that in both cases where this subroutine is used to
;determine the drive type, the drive with the larger number
;of tracks will only be double-density (Quad Density ahd
;High Density drives). So if it's set up for Single Density
;then it's not the larger drive.
;
;When returning after failing to find the reqwuested track
;(because the drive is the smaller one), the WD1793's track
;register is set to be the requested track number. This will
;cause the next seek to be outward, and long enough to get
;the head back onto readable tracks.
;On Entry:
;  a=a few tracks past the end of the smaller drive
;   (Must be an odd number)
;  BUFDSK=drive to test
;  hl=PRMTBL(BUFDSK)
;  PRMTBL(BUFDSK) density is set up for the disk
;On Exit:
;  DTRACK=requested track number
;  a=0 and Z set if the track exists and is DD
;  A<>0 and Z clear if not
;  If NZ then head may be off the end of the track
;  hl=PRMTBL(BUFDSK)
;Stack Depth: 10 (8 if PERSCI=FALSE)
;Trashes bc,de
;**********************************************************
ENDSEK:	mov	b,a		;temp save
	call	SEEKA		;Seek past drive's last track
				;(must be an odd number) 

	call	TRAKID		;returns a=found track #
				;returns hl=PRMTBL(BUFDSK)
				;error returns with bit 0=0
	xra	b		;find the right track?

	mov	a,b
	out	DTRCK		;so the next seek will be 
	ret			;...outward
				;Z set on sucess

 endif ;QDRIVE or HDRIVE

;***Subroutine*******************************************
;Seek track, get its parameters, set up for transfer.
;This routine does not assume that every track on
;the disk is formatted the same: each track may have
;different density and/or SPT.
;On Entry:
;  BUFDSK=target disk
;  BUFTRK=target track
;  BUFSID=target side
;On Successful Exit:
;  DCTRL is set up
;  IDSECT=found sector
;  a=0
;  d=track number
;  e=IDSSIZ=BPS(0:3)
;  hl=PRMTBL(BUFDSK)
;  Z set
;  Carry clear
;  ADCMD=side selected by BUFSID
;  BDCTRL = DCTRL value for this track
;On failure exit:
;  Z cleared
;Rude abort to WBOOT if the drive is not ready or
;a timeout in SEEKA
;Trashes c
;Stack depth: 10
;********************************************************
SEEKID:	mvi	a,SKTRYS	;reset seek retry counter

SKRTRY:	sta	SCOUNT		;seek retries
;---------------------------------------
;Seek the desired track, and read its ID
;---------------------------------------
	lda	BUFTRK		;desired track
	call	SEEKA		;Seek track in a
				;(selects side 0)

	lda	BUFSID		;Now select current side
	out	ADCMD
	call	IDREAD		;read header to get track
				;d=found track number
				;e=BPS(0:3)
				;hl=PRMTBL(BUFDSK)
				;WD1793 track reg updated
				;set density for track
				;updates BDCTRL
	rnz			;fail on IDREAD error
;---------------------------------------
;Verify that we are on the correct track
;  d=found track number
;  e=BPS(0:3)
;  hl=PRMTBL(BUFDSK)
;---------------------------------------
	lda	BUFTRK		;desired track
	xra	d		;correct track?
	rz			;success exit
;-------------------------
;Retry the seek if we can.
;Exit with Z clear if not.
;  hl=PRMTBL(BUFDSK)
;-------------------------
	lda	SCOUNT		;Seek retry counter
	dcr	a
	rm			;Abort if too many retries

	db	JR,(SKRTRY-$-1)XOR 0FF00h
;***Subroutine***********************************************
;Seek track, wait for completion
;On Entry:
;  a=target track
;  BUFDSK=disk number
;  PRMTBL(BUFDSK) has step rate and fast-seek values
;On Exit:
;  a=disk status bits: Busy, Track 0, Head Load, Seek Error
;  Side 0 is selected in ADCMD
;  Fast-Seek is selected in ADCMD if the drive is voicecoil
;  hl=PRMTBL(BUFDSK)
;Rude abort to WBOOT if the drive is not ready or timeout
;Trashes c,de
;Stack depth: 4
;************************************************************
SEEKA:	out	DDATA		;track to seek
	mvi	c,SEEKCM+HDLOAD	;seek track with head loaded

;Fall into T1DCMD

;***Subroutine***********************************************
;Set up the controller, issue Type I command (Seek, Step, or 
;Restore) to the WD1793, and wait for completion. Use the
;step rate from the selected drive's PRMTBL entry. If a
;Voicecoil drive has been found, then retrigger the HLT
;1-shot (with a rising edge of HLD) to check the state of the
;SEEK COMPLETE signal from the drive. Do so until SEEK
;COMPLETE becomes true or it times out.
;On Entry:
;  BUFDSK=drive number
;  PRMTBL(BUFDSK) has step rate and fast-seek values
;  c=command (DCMMD value without step rate)
;On Exit:
;  a=disk status bits: Busy, Track 0, Head Load, Seek Error
;  Side 0 is selected in ADCMD
;  Fast-Seek is selected in ADCMD if the drive is voicecoil
;  hl=PRMTBL(BUFDSK)
;Rude abort to WBOOT if the drive is not ready or timeout
;Trashes c,de
;Stack depth: 4
;************************************************************
T1DCMD:	call	PTLU		;DCTRL value & PRMTBL pointer
				;a=DCTRL value from PRMTBL
	out	DCTRL		;(11)Select disk, motor on

;Set side 0, and (if a voicecoil drive has been
;detected) set up for voicecoil seek, both in ADCMD

 if PERSCI
	mov	a,m		;(7)PRMTBL(BUFDSK) bit 5
	rrc			;(4)..is now in bit 4
	ani	ADFSEKN		;(7)..set means fast-seek
	cma			;(4)ADCMD bit 4 clear
				;..means fast seek, SIDE0
 endif ;PERSCI

 if not PERSCI
	mvi	a,SIDE0
 endif	;not PERSCI

	out	ADCMD		;(11)side and seek type

	call	WATRDY		;wait for drive to be ready
				;trash de, abort on timeout

;Combine the step rate with the given command, issue the
;command to the WD1793, and wait for the command to complete

	mov	a,m		;(7)get step rate
	ani	03h		;(7)..and just the step rate
	ora	c		;(4)combine cmd w/ step rate

 if not PERSCI
;Fall into DT1CMD to issue cmd and await completion
 endif ;not PERSCI

 if PERSCI
	call	DT1CMD		;issue cmd, wait completion

;If this is a voicecoil drive, then we wait for completion

	dw	BIT5M		;(8)test PRMVC: voicecoil?
	rz			;no VC: done, status in a

;Wait for 1 mS, so that the microcontroller in a PerSci
;299B has time to raise its -Seek Complete signal
;(Just assume a 4 MHz CPU here, for worst case)
	lxi	d,120*256+250

VDELAY:	dcr	e		;(4)
	db	JRNZ,(VDELAY-$-1) XOR 0FF00h ;(12)

;Wait for voicecoil fast-seek to complete. A full-stroke seek
;should complete in 100 mS, so timeout after about 500 mS
;(1 sec if the CPU is set for 2 MHz.)

T1DC1:	in	ASTAT		;(11)
	ani	BSIP		;(7)Z means seek complete

	in	DSTAT		;(11) status for ret

	rz			;(10)Seek is complete

	dcx	d		;(6)
	mov	a,e		;(4)
	ora	d		;(4)
	db	JRNZ,(T1DC1-$-1)xor 0FF00h ;(12)

	db	JR,DNRERR-$-1	;error abort
 endif ;PERSCI

;***Subroutine***********************************************
;Issue a Type 1 command (Seek, Step, or Restore) to the
;WD1793, and then wait for the WD1793 to complete, with
;timeout.
;On Entry:
;  a=complete WD1793 command
;  ADCMD is set up with side and fast seek (as needed)
;On Exit:
;  WD1793 command has been executed and completed
;  a=disk status bits: Track 0, Seek Error
;Rude abort to WBOOT if the drive is not ready or timeout
;Trashes c,de
;110 cycles fastest path
;************************************************************
DT1CMD:	out	DCMMD		;(11)issue command to WD1793

;Wait for command to complete, with 2.5 second timeout
;(longer than the worst-case seek on a 5.25" disk)
;(2.8 - 3.5 second timeout de[ending on e on entry)

	mvi	d,4		;(10)

;Inner loop: 11024 cycles (a little random on the first pass)

DT1DC1:	dcr	c		;(4)
	db	JRNZ,DT1DC2-$-1	;(12)

	dcx	d		;(6)
	mov	a,d		;(4)
	ora	e		;(4) clears carry too
	db	JRZ,DT1DC3-$-1	;(7)Timeout?

DT1DC2:	in	DFLAG		;(11)wait for completion
	rar			;(4)WD1793 INTRQ?

	db	JRNC,(DT1DC1-$-1)xor 0FF00h
				;(12)n: keep waiting

;Read and mask the disk controller status. Abort with
;'drive not ready' if the drive is not ready.

	in	DSTAT		;(11)WD1793 status
	ora	a		;(7)ready?
	rp			;(10)done unless not ready

;Abort: the drive is not ready

DT1DC3:	db	JR,DNRERR-$-1	;error abort

;***Subroutine*******************************************
;Wait for up to 734 mS (at least 3 index-pulse times)
;for the drive to be ready, reboot if timeout.
;On Exit:
;  BCTRL written
;  WD1793 INTRQ has been cleared
;  Drive and controller are ready
;Stack depth: 0
;Trashes psw, de
;********************************************************
WATRDY:	mvi	d,255		;45 x 255*256/4= 734 mS min

WRDY1:	in	DSTAT		;(11)disk status, clear INTRQ
	ral			;(4)Drive ready?
	rnc			;(5)y:done
	dcx	d		;(6)
	mov	a,d		;(4)
	ora	e		;(4)timeout?
	db JRNZ,(WRDY1-$-1)xor 0FF00h ;(11)n: keep waiting

;Fall into DNRERR

;***Error Reboot*******************************
;Drive not ready
;report error, go wait on input, then warm-boot
;(where the stack gets reset)
;On Entry:
;  Drive letter already pasted into DNRMSG
;**********************************************
DNRERR:	mvi	l,DNRMSG and 0FFh
	jmp	MWBOOT		;print message, wait, reboot
;=================
;Misc. Subroutines
;=================
;***Subroutine*******************************************
;Time the index signal
;On Entry:
;  The drive is selected and the last disk commnad was
;    a type I command (which routes the Index signal to
;    the 1793's status register)
;On Exit:
; a=0FFh
; d=index time in units of 13,568 clock cycles
; Z set if timeout, cleared otherwise
;trashes bc,e
;*******************************************************
TINDEX:	lxi	b,3*256+0FFh	;b=loop counter
				;c=inverter
TINDX1:	mov	d,b		;timer

;Time the index. 53 cycles per pass:
;First find a rising edge, then time
;to the next falling edge
	
TINDX2:	inx	d		;(6)Bump 16-bit timer
	mov	a,d		;(4)
	ora	e		;(4)Timeout?
	rz			;(5)y: ret with Z set

	in	DSTAT		;(11)
	xra	c		;(4)maybe invert

	ani	SINDEX		;(7)active-low signal
	db	JRNZ,(TINDX2-$-1) XOR 0FF00h
				;(12)

	mov	a,c		;look for the other
	cma			;..index state next time
	mov	c,a	

	db	DJNZ,(TINDX1-$-1) XOR 0FF00h

	inr	a		;clear Z (A was 0)
	ret
;***Subroutine******************************************
;Look up disk DCTRL value in this disk's PRMTBL entry
;On Entry at PTLU
;  (BUFDSK)=disk number
;On Entry at PTLUA
;  a=disk number
;On Exit:
;  hl=address of BUFDSK's DCTRL value in PRMTBL (Byte 0)
;   =PRMTBL+((requested disk)*3)
;  a=DCTRL value for this drive =(hl)
;Stack depth: 0
;NOTE: The entire PRMTBL table is in one 256-byte page.
;*******************************************************
PTLU:	lda	BUFDSK

PTLUA:	mov	l,a
	add	l
	add	l		;a=(BUFDSK)*3

	lxi	h,PRMTBL+1

	add	l
	mov	l,a
	mov	a,m		;Value from table
	dcx	h		;point to byte 0
	ret

;============================================================
;Input/Output Routines
;============================================================

;***Error Reboot***********************
;Restart error (something bad happened)
;**************************************
RSTERR:	mvi	l,RSTMSG and 0FFh

;Fall into COMERR

;***Error Reboot*****************************************
;COM error: an undefined IO device was selected. This may
;have been the console, so reinitialize the IOBYTE first.
;on Entry
;  hl points to error message string
;********************************************************
COMERR:	mvi	a,IOINIT	;Re-initialize broken IOBYTE
	sta	IOBYTE
	jmp	MWBOOT		;msg, user input, warm boot

;=========================================================
;BIOS output handlers - these implement the IOBYTE, though
;many stub to the IOER error handler.
;=========================================================

;*******************************************
;Generic I/O routine for nonexistant devices
;*******************************************
IOER:	mvi	l,IOMSG and 0FFh

	db	JR,(COMERR-$-1)XOR 0FF00h

;==================================================
;Output drivers for the 16FDC and 64FDC serial port
;==================================================
TTPNCH:
TTYOUT:	call	TTYOST		;wait for TX ready
	db	JRZ,(TTYOUT-$-1)XOR 0FF00h

	mov	a,c		;c has character to send
	out	UDATA
	ret

TTYOST:	in	USTAT		;8250 line status
	ani	TXMTY		;Isolate TX buffer empty
	rz		;not ready: return with 0
	mvi	a,0FFh	;ready: return with FF
	ret

;***BIOS Function*******************************************
;Console Output
;The track bufer gets flushed if it need flushing. If a disk
;error occurs when trying to flush the track buffer, then an
;error message is printed on the console. (There is no
;mechanism to tell BDOS that the track-write failed during
;a call to CONOUT.)
;On Entry:
;  c=character to print on console
;IOBYTE<1:0>  Console Output Device
;   00                 TTY
;   01                 CRT
;   10        BAT (current LST device)
;   11                 UC1
;Trashes psw
;***********************************************************
CONOUT:	lda	IOBYTE
	ani	3		;console assignment
	db	JRZ,(TTYOUT-$-1)XOR 0FF00h

	cpi	2
	db	JRC,CRTOUT-$-1
	db	JRNZ,UC1OUT-$-1

;console out=BAT: Fall into LIST

;***BIOS Function*********************
;List Output
;On Entry:
;  c=character to print on List device
;IOBYTE<7:6>    List Output Device
;   00                 TTY
;   01                 CRT
;   10                 LPT
;   11                 UL1
;Trashes psw
;*************************************
LIST:	lda	IOBYTE
	ani	0C0h		;list assignment
	db	JRZ,(TTYOUT-$-1)XOR 0FF00h

	cpi	80h	
	db	JRC,CRTOUT-$-1
	db	JRZ,LPTOUT-$-1
	db	JR,UL1OUT-$-1

;***BIOS Function******************
;Punch Output
;On Entry:
;  c=character to punch
;IOBYTE<5:4>    Punch Output Device
;   00                 TTY
;   01                 HSP
;   10                 UP1
;   11                 UP2
;Trashes psw
;**********************************
PUNCH:	lda	IOBYTE
	ani	30h		;punch assignment
	db	JRZ,(TTPNCH-$-1)XOR 0FF00h

	cpi	20h
	db	JRC,HSPOUT-$-1
	db	JRZ,UP1OUT-$-1
	db	JR,UP2OUT-$-1

;***BIOS Function*********************
;Get List Output Status
;On Exit:
;  device available: a=0FFh, Z cleared
;  device busy: a=0, Z set
;IOBYTE<7:6>    List Output Device
;   00                 TTY
;   01                 CRT
;   10                 LPT
;   11                 UL1
;*************************************
LISTST:	lda	IOBYTE
	ani	0C0h		;list assignment
	db	JRZ,(TTYOST-$-1)XOR 0FF00h

	cpi	80h
	db	JRC,HSPST-$-1
	db	JRZ,LPTOST-$-1
	db	JR,UL1OST-$-1

;=============================
;Output drivers for the TU-ART
;=============================
;----------------------------
;Serial channel A out can be:
;  UC1:
;  LPT:
;  UP1:
;----------------------------
 if C1TAS			;UC1: output device
UC1OUT:
 endif ;C1TAS

 if P1TAS			;UP1: output device
UP1OUT:
 endif ;P1TAS

 if LPTAS			;LPT output device
LPTOUT:
 endif ;LPTAS

 if C1TAS or P1TAS or LPTAS
TUAOUT:	call	TUAOST
	db	JRNZ,(TUAOUT-$-1) xor 0FF00h

	mov	a,c		;Chr to send
	out	TUBASA+TUDATA
	ret
 endif ;C1TAS or P1TAS or LPTAS

 if LPTAS
LPTOST:				;LPT output status
 endif ;LPTAS

 if C1TAS or P1TAS or LPTAS
TUAOST:	in	TUBASA+TUSTAT
	ani	TUTBA
	db	JR,FINST-$-1
	
 endif ;C1TAS or P1TAS or LPTAS
;----------------------------
;Serial channel B out can be:
;  UC1:
;  LPT:
;  UP2:
;----------------------------
 if C1TBS			;UC1: output device
UC1OUT:
 endif ;C1TBS

 if P2TBS			;UP2: output device
UP2OUT:
 endif ;P2TBS

 if LPTBS			;LPT output device
LPTOUT:
 endif ;LPTBS

 if C1TBS or P2TBS or LPTBS
TUBOUT:	call	TUBOST
	db	JRNZ,(TUBOUT-$-1) xor 0FF00h

	mov	a,c		;Chr to send
	out	TUBASB+TUDATA
	ret
 endif ;C1TBS or P2TBS or LPTBS

 if LPTBS
LPTOST:				;LPT output status
 endif ;LPTBS

 if C1TBS or P2TBS or LPTBS
TUBOST:	in	TUBASB+TUSTAT
	ani	TUTBA
	db	JR,FINST-$-1
	
 endif ;C1TBS or P2TBS or LPTBS
;--------------------------
;Parallel channel A can be:
;  LPT:
;--------------------------
 if LPTAP
LPTOUT:	call	LPTOST
	db	JRZ,(LPTOUT-$-1)xor 0FF00h

	mov	a,c		;data to send
	out	TUBASA+TUPARP	
	ret

LPTOST:	in TUBASA+TUPARP
	ani	TOPDA
	db	JR,FINST-$-1
 endif ;LPTAP
;--------------------------
;Parallel channel B can be:
;  UL1:
;--------------------------
 if L1TBP
UL1OUT:	call	UL1OST
	db	JRZ,(UL1OUT-$-1)xor 0FF00h

	mov	a,c		;data to send
	out	TUBASB+TUPARP	
	ret

UL1OST:	in TUBASB+TUPARP
	ani	TOPDA
	db	JR,FINST-$-1
 endif ;L1TBP


;==========================
;Output drivers for the PRI
;==========================

;---------------------------
;PRI dot matrix port can be:
;  LPT
;---------------------------
 if LPPRI
LPTOUT:	call	LPTOST
	db	JRZ,(LPTOUT-$-1)xor 0FF00h

	mov	a,c		;data to send
	ani	7Fh		;..with strobe low
	out	PRBASE+PRDATA
	ori	80h		;and then strobe high
	out	PRBASE+PRDATA
	ret

LPTOST:	in	PRBASE+PRSTAT
	cma			;so 1 means ready
	ani	PRBUSY
	db	JR,FINST-$-1

 endif ;LPPRI

;=================================
;Nonexistant output device drivers
;=================================

;Console output devices

CRTOUT:		;CRT

 if C1NONE
UC1OUT:		;user console 1
 endif ;C1NONE

;Punch devices

HSPOUT:		;high speed punch
HSPST:		;high speed punch status

 if P1NONE
UP1OUT:		;user punch 1
 endif ;P1NONE

 if P2NONE
UP2OUT:		;user punch 2
 endif ;P2NONE

;List devices

 if LPNONE
LPTOUT:		;line printer
LPTOST:		;line printer output status	
 endif ;LPNONE

 if L1NONE
UL1OUT:		;user list device 1
UL1OST:		;user list device 1 status
 endif ;L1NONE

OUTERR:	jmp	IOER	;jump to nonexistant device driver

;=======================================================
;BIOS input handlers- these implement the IOBYTE, though
;many stub to the IOER error handler.
;=======================================================

;=================================================
;Input drivers for the 16FDC and 64FDC serial port
;=================================================
TTYRDR:
TTYIN:	call	TTYST	;wait for character
	db	JRZ,(TTYIN-$-1)XOR 0FF00h

	in	UDATA	;read character
	ret

TTYST:	in	USTAT	;8250 line status
	ani	RXRDY	;Rx data available?

FINST:	rz		;not ready: return with 0
	mvi	a,0FFh	;ready: return with FF
	ret

;***BIOS Function************************
;Get Console Input Status
;On Exit:
;  character available: a=0FFh, Z cleared
;  character not available: a=0, Z set
;IOBYTE<1:0>  COnsole Input Device
;   00                 TTY
;   01                 CRT
;   10       BAT (current RDR device)
;   11                 UC1
;****************************************
CONST:	lda	IOBYTE
	ani	3	;console assignment
	db	JRZ,(TTYST-$-1)XOR 0FF00h

	cpi	2
	db	JRC,CRTST-$-1
	db	JRNZ,UC1ST-$-1

;console input=BAT: Fall into reader input status

;***Subroutine***************************
;Get Reader Input Status
;On Exit:
;  character available: a=0FFh, Z cleared
;  character not available: a=0, Z set
;IOBYTE<3:2>   Reader Input Device
;   00                 TTY
;   01                 HSR
;   10                 UR1
;   11                 UR2
;****************************************
RDRST:	lda	IOBYTE
	ani	0Ch	;console assignment
	db	JRZ,(TTYST-$-1)XOR 0FF00h

	cpi	8
	db	JRC,HSRST-$-1
	db	JRZ,UR1ST-$-1
	db	JR,UR2ST-$-1

;***Subroutine***********************
;Raw console input
;called BY CONIN, which strips parity
;On Exit:
;  a = console character
;IOBYTE<1:0>  COnsole Input Device
;   00                 TTY
;   01                 CRT
;   10       BAT (current RDR device)
;   11                 UC1
;************************************
CIRAW:	lda	IOBYTE
	ani	3	;console assignment
	db	JRZ,(TTYIN-$-1)XOR 0FF00h

	cpi	2
	db	JRC,CRTIN-$-1
	db	JRNZ,UC1IN-$-1

;console=BAT: Fall into reader input

;***BIOS Function***********************
;Reader Input
;Wait for and get reader character
;Non-standard driver: returns with Z set
;if no character is available.
;On Exit if characterr ready:
;  a=reader character
;  Z cleared
;On Exit if chharacter not ready:
;  Z set
;IOBYTE<3:2>   Reader Input Device
;   00                 TTY
;   01                 HSR
;   10                 UR1
;   11                 UR2
;***************************************
READER:	lda	IOBYTE
	ani	0CH	;Reader assignment
	db	JRZ,(TTYRDR-$-1)XOR 0FF00h

	cpi	8
	db	JRC,HSRIN-$-1
	db	JRZ,UR1IN-$-1
	db	JR,UR2IN-$-1

;============================
;Input drivers for the TU-ART
;============================

;-------------------------------
;Serial channel A in can be UC1:
;OR standard/non-standard UR1:
;-------------------------------
 if C1TAS			;UC1: input device

UC1IN:	call	TUAIST		;Wait for input
	db	JRZ,(UC1IN-$-1) xor 0FF00h

	in	TUBASA+TUDATA	;get data
	ret

UC1ST:
 endif ;C1TAS

 if R1TAS			;UR1: input device
UR1ST:
 endif ;R1TAS

 if C1TAS or R1TAS
TUAIST:	in	TUBASA+TUSTAT
	ani	TURBF
	db	JR,(FINST-$-1)XOR 0FF00h
 endif ;C1TAS or R1TAS

 if R1TAS			;UR1: input device
UR1IN:	call	UR1ST
 	db	JRZ,(UR1IN-$-1)XOR 0FF00h

	in	TUBASA+TUDATA	;get data
	ret
 endif ;R1TAS
;-------------------------------
;Serial channel B in can be UC1:
;OR standard/non-standard UR2:
;-------------------------------
 if C1TBS			;UC1: input device

UC1IN:	call	TUBIST		;Wait for input
	db	JRZ,(UC1IN-$-1) xor 0FF00h

	in	TUBASB+TUDATA	;get data
	ret

UC1ST:
 endif ;C1TBS


 if R2TBS			;UR2: input device
UR2ST:
 endif ;R2TBS
 if C1TBS or R2TBS
TUBIST:	in	TUBASB+TUSTAT
	ani	TURBF
	db	JR,(FINST-$-1)XOR 0FF00h
 endif ;C1TBS or R2TBS


 if R2TBS			;UR2: input device
UR2IN:	call	UR2ST
 	db	JRZ,(UR2IN-$-1)XOR 0FF00h

	in	TUBASB+TUDATA	;get data
	ret
 endif ;R2TBS
;================================
;Nonexistant input device drivers
;================================

;Reader devices

HSRIN:		;high speed reader
HSRST:		;high speed reader status

 if R1NONE
UR1IN:		;reader 1
UR1ST:		;reader 1 status
 endif ;R1NONE

 if R2NONE
UR2IN:		;reader 2
UR2ST:		;reader 2 status
 endif ;R2NONE


;Console input devices

 if C1NONE
UC1IN:
UC1ST:
 endif ;C1NONE

CRTIN:
CRTST:	db	JR,(OUTERR-$-1)XOR 0FF00h

;***BIOS Function*******************************************
;Get Console Input
;Wait for and get one character from the currently-assigned
;console device, strips its parity, and return with the chr
;in a.
;The track bufer gets flushed if it need flushing. If a disk
;error occurs when trying to flush the track buffer, then an
;error message is printed on the console. (There is no
;mechanism to tell BDOS that the track-write failed during
;a call to CONIN.) Deselect all disks to lift their heads
;and allow doors to be opened.
;Entry at CONIN2 does not flush the buffer.
;On Exit:
;  a = console character with parity stripped
;  Track buffer flushed if it was dirty
;  all drives marked as invalid
;  BUFTRK = 0FFh (forces a reload)
;IOBYTE<1:0>  COnsole Input Device
;   00                 TTY
;   01                 CRT
;   10       BAT (current RDR device)
;   11                 UC1
;This uses 6 bytes of the calling program's stack.
;***********************************************************
CONIN:	call	BFLUSH	;flush buffer if needed

;Deselect all drives, but only if BDOS called CONIN, so that
;we don't mess up programs (e.g. MSYSGEN) that talk directly
;to the disk controller hardware. Note that any such program
;that call BDOS to get a console character will still have
;troubles here.

	xthl		;get return address
	mov	a,h	;just the high byte
	xthl		;put it back

	cpi	CCP/256	;called from BDOS?
	db	JRC,CI1-$-1

CONIN2:	mvi	a,DCMDMO ;leave the motors on
	out	DCTRL	;deselect all drives

CI1:	call	CIRAW	;get input character
	ani	7Fh	;strip parity per CP/M spec
	ret

;***Subroutine***************************************
;Print string in message page at l, preceded and
;followed by CRLF. String is terminated by msb set
;or null. MSB adds bell and ' error' to the end.
;Replace 01h with the drive letter.
;02 means the next chr is a count of the following chr.
;LF gets appended after every CR.
;On Exit:
;  Trashes psw,c,hl
;****************************************************
PMSGP:	mvi	h,MPAGE/256	;message page

;fall into PMSG

;***Subroutine***************************************
;Print string at (hl), preceded and followed by CRLF.
;String is terminated by msb set or null.
;MSB adds bell and ' error' to the end.
;Replace 01h with the drive letter.
;02 means the next chr is a count of the following chr.
;LF gets appended after every CR.
;On Exit:
;  Trashes psw,c,hl
;****************************************************
PMSG:	push	h		;string pointer
	call	PCRLF		;print initial CRLF

	pop	h		;string pointer
	call	PRINTF		;print string now

;Fall into PCRLF for final CR/LF

;***Subroutine****
;Print CR/LF
;Trashes psw,c,hl
;*****************
PCRLF:	lxi	h,CRMSG

;Fall into PRINTF

;***Subroutine****************************************
;Print string at (hl)
;String is terminated by msb set or null.
;MSB adds bell and ' error' to the end.
;01 means insert BUFDISK as ASCII
;02 means the next chr is a count of the following chr.
;  (Repeating chrs only allowed during initial boot)
;LF gets appended after every CR.
;On Exit:
;  a=0
;Trashes c,hl
;Trashes b only during init
;******************************************************
PRINTF:	mov	a,m
	ani	7Fh
	rz			;00h (or 80h): end

	mov	c,a		;string chr

	dcr	a		;01:Drive letter?
	db	JRNZ,PF1-$-1
	lda	BUFDSK		;the troublesome drive
	adi	'A'		;make it ASCII
	mov	c,a

;repeating chrs are only allowed during initialization.
;If used afterwards, it will crash...

PF1:	dcr	a		;02:repeat?
	cz	PRPEAT		;y: go repeat
				;returns with Z set

	cnz	CONOUT		;print the chr in c
				;returns with a=c

	cpi	CR		;need to add LF?
	mvi	c,LF
	db	JRZ,(PF1-$-1) XOR 0FF00h

	dw	BIT7M		;msb set, meaning error msg?
	inx	h		;Next string chr
	db	JRZ,(PRINTF-$-1) XOR 0FF00h

	mvi	l,ERRMSG and 0FFh	;add BELL,' error'
	db	JR,(PRINTF-$-1) XOR 0FF00h

;===Table====================================================
;CP/M Disk Parameter Headers, one for each supported drive
;(see CP/M docs for DPH details)
;These get filled in by SELDSK when a disk gets selected.
;Bytes   Name             Meaning
; 0-1    XLT     Addr of logical-to-physical translate vector
;   0              Skew table page address
;   1              BPS(0:3)
; 2-3   TRACK    BDOS track work area (init to 0)
; 4-5   SECTOR   BDOS sector work area (init to 0)
; 6-7   DIRNUM   BDOS directory number work area (init to 0)
; 8-9   DIRBUF   Address of common 128-byte scratch area
;10-11   DPB     Address of Disk Parameter Block (DPB)
;12-13   CSV     Address of disk change check scratch pad
;14-15   ALV     Address if disk allocation scratch pad area
;============================================================
DPHBASE:
	dw	0,0,0,0,DIRBF,DPBA,CHK00,ALL00	;drive 0 DPH
	dw	0,0,0,0,DIRBF,DPBB,CHK01,ALL01	;drive 1 DPH
	dw	0,0,0,0,DIRBF,DPBC,CHK02,ALL02	;drive 2 DPH
	dw	0,0,0,0,DIRBF,DPBD,CHK03,ALL03	;drive 3 DPH

;=====================
;Initialized Variables
;=====================
SEKSEC:	db	0	;requested sector

SEKSID:	db	0	;requested side
SEKTRK:	db	0FFh	;requested track (must follow SEKSID)
SEKDSK:	db	0	;requested disk (must follow SEKTRK)
ALWAY1:	db	1	;need a 1 here in RDTRAK

BUFSID:	db	0	;Disk side (SIDE0 or SIDE1)
BUFTRK:	db	0FFh	;Track number (must follow BUFSUD)
BUFDSK:	db	0	;Current Disk (must follow BUFTRK)
TBSTAT:	db	0	;FF: track data needs to be written
			;0: the buffer data is invalid
			;1: the buffer is valid but does not
			;yet need to be written
			;(must follow BUFDSK)

BUFSPT:	db	0	;Sectors per Track

 if DEBUG

;***DEBUG Subroutine*****
;Print hl as 4 hex digits
;All registers preserved
;************************
PHLHEX:	push	psw
	mov	a,h
	call	PAHEX
	mov	a,l
	call	PAHEX
	pop	psw
	ret

;***DEBUG Subroutine****
;Print a as 2 hex digits
;All registers preserved
;***********************
PAHEX:	push psw
	rrc
	rrc
	rrc
	rrc
	call	PNHEX
	pop	psw
	push	psw
	call	PNHEX
	pop	psw
	ret

;***DEBUG Subroutine*********
;Print low nibble of a in hex
;Trashes psw
;****************************
PNHEX:	ani	0Fh		;get nibble
	adi	'0'		;Make ASCII
	cpi	'9'+1		;A-F?
	db	JRC,PRINTA-$-1
	adi	'A'-'9'-1

;Fall into PRINTA

;***DEBUG Subroutine****
;Print a on the console
;Trashes psw
;***********************
PRINTA:	push	b
	mov	c,a
	call	CONOUT
	pop	b
	ret

 endif	;DEBUG

;============================================================
;One-time Boot Code
;Used only during cold boot. This code gets overwritten by
;writes to the uninitialized data area (listed below)
;============================================================

;***BIOS Function**************************************
;Cold Boot
;CP/M was loaded into memory by the EPROM-based loader.
;Here, we initialize I/O ports and page 0 variables,
;print a verbose sign-on message, and then re-use some
;of the warm-boot code to finish initialization.
;Note that RDOS set up the console serial port already.
;******************************************************
BOOT:	di			;no interrupts ever

;---------------------------
;Initialize the TU-ART ports
;---------------------------
 if TUARTA

;Initialize serial port A

	mvi	a,URESET
	out	TUBASA+TUCTRL

	mvi	a,TABAUD and 0FFh	;baud rate byte
	out	TUBASA+TUBAUD

	mvi	a,TABAUD/256		;high-baud bit
	out	TUBASA+TUCTRL

 endif ;TUARTA

 if TUARTB

;Initialize serial port B

	mvi	a,URESET
	out	TUBASB+TUCTRL

	mvi	a,TBBAUD and 0FFh	;baud rate byte
	out	TUBASB+TUBAUD

	mvi	a,TBBAUD/256		;high-baud bit
	out	TUBASB+TUCTRL

 endif ;TUARTB
;-----------------------------------
;Set up stack in TBUF so we can call
;-----------------------------------
	lxi	h,TPA		;this is 100h
	sphl
;-------------------------
;Initialize all page 0 RAM
;-------------------------
	dcr	h		;hl=0

CLRP0:	mov	m,h		;m=0
	inr	l		;clear 256 bytes
	db	JRNZ,(CLRP0-$-1)XOR 0FF00h
	
;Initialize non-zero page 0 entry

	mvi	a,IOINIT
	sta	IOBYTE

;Now that we've cold-booted, further calls to BOOT
;will not reload the BIOS or perform the above tasks

	lxi	h,CBOOT
	shld	WBOOTE-2	;BOOT=CBOOT
;---------------------------------
;Print sign-on message, including:
; CP/M version number
; BIOS version number
; Port configuration 
;---------------------------------
	mvi	c,CPMVER
	call	BDOSE		;returns version in hl

	lxi	b,'0'*256	;some useful constants

	mov	a,l		;get major version
	rrc			;..from the high nibble
	rrc
	rrc
	rrc
	ani	0Fh

	db	JRZ,CPMV2-$-1	;early version?

	add	b		;make it ASCII
	sta	LMSG3		;paste it in

	mov	a,l		;get minor version
	ani	0Fh		;..from the low nibble
	add	b		;make it ASCII
	sta	LMSG3+2		;paste it in
CPMV2:

	lxi	h,LOGMSG	;sign onto system
	call	PMSG

 if BANKED
;-------------------------------------------------
;Test for track buffer RAM presence, and perform a
;quick memory test. Print message and halt if any
;errors. Assume main memory is engaged on entry.
;-------------------------------------------------
	lxi	h,TRKBUF
	lxi	b,TBSIZE

MTLOOP:	mov	d,m		;remember main memory byte

	mvi	a,TBBANK	;select track buffer bank
	out	BBANK

	mov	m,d		;put main memory
				;..data in track buffer
	mov	a,d
	cmp	m		;successful write?
	db	JRNZ,TBEROR-$-1	;n: memory error

	cma			;invert the data
	mov	m,a		;..and write it to memory
	cmp	m		;inverted write ok?
	db	JRNZ,TBEROR-$-1	;n: memory error

	mvi 	a,OSBANK	;select main bank
	out	BBANK

	mov	a,d		;did the inverted data
				;..change main memory?
	dw	ZCPI		;compare, bump hl & bc
	db	JRNZ,BSEROR-$-1	;y: no buffer memory

	jpe	MTLOOP		;continue until bc=0
 endif ;BANKED

;-----------------------------------------------
;Time the index pulse to determine CPU speed.
;Warn the user if 2MHz. Remember result in a form
;that's useful for interpreting TINDEX's result.
;  BUFDSK=0
;------------------------------------------------
	in	DTRCK
	call	SEEKA		;null seek to route Index
				;..to the 1793's Status reg

	call	TINDEX		;time the index

	mov	a,d		;timer result

;Nominal de values:
;             2 MHz          4 MHz
; 360 RPM  6289 (1891h)   12,579 (3123h)
; 300 RPM  7547 (1D7Bh)   15,094 (3AF6h)
;Anything below 2800h will be considered 2 MHz

;Compute CPUMHZ for the TINDEX result:
;  2 MHz CPU: 1Ah (26)
;  4 MHz CPU: 34h (52)

	lxi	h,MHZMSG	;CPU speed message

	cpi	28h
	mvi	a,1Ah
	db	JRC,MHZDON-$-1	;carry means 2MHz
	
	mvi	m,'4'		;modify message
	xra	a		;and shorten it
	sta	MH4MSG

	mvi	a,34h		;4 MHz

MHZDON:	sta	CPUMHZ

	call	PRINTF
	lxi	h,BEMSG		;end of signon message
	call	PMSG		
;------------------------------------------------------------
;Initialize lots of disk parameters by selecting drive A
;(which is the drive we loaded from). BUFDSK was initialized
;to FF. Also measures Z-80 speed, saving the result in CPUMHZ.
;------------------------------------------------------------
	mvi	c,0		;Drive A

	call	SELDSK		;Wipes out 1st 6 bytes
				;..of boot (this code).
				;Z clear means fail
	jnz	BFAIL		;error during drive ID read

;------------------------------
;One-time boot code is complete
;------------------------------
	jmp	BOOT0		;Do boot common code

 if BANKED
;----------------------------
;Track buffer error.
;Print error message and halt
;----------------------------
BSEROR:	lxi	h,BSEMSG	;Bank select error
	db	JR,BHALT-$-1

TBEROR:	lxi	h,TBEMSG	;Track buffer mem test error

BHALT:	call	PMSG

	hlt

;***Subroutine******************
;Print a character several times
;On Entry:
; (hl+1) = repeat count
; (hl+2) = chr to repeat
;On exit:
; Z set
; b=0
; c=repeat character
; hl has been bumped twice
;trashes a
;********************************
PRPEAT:	inx	h		;point to repeat count
	mov	b,m		;b=repeat count

	inx	h		;point to repeat chr
	mov	c,m		;c= repeat chr

PRLOOP:	call	CONOUT		;print b copies of c
	db	DJNZ,(PRLOOP-$-1) XOR 0FF00h
	ret


;Transfer buffer error messages (PRINTF will append ' error')

TBEMSG:	db	'Track buffer RA','M'+80h
BSEMSG:	db	'Bank selec','t'+80h
 endif ;BANKED
;----------------------------------------------
;WY-50 initialization and WY-50 banner message
;are part of the logon message. NOTE: PMSG will
;not allow the message to contain 00, 01, or
;any character with its high bit set.
;----------------------------------------------
LOGMSG:
 if WY50
; WY30/WY50 Initialization

	db	ESC,'~',DQUOT	;Select native mode
	db	ESC,'~ '	;Deselect enhanced mode
	db	ESC,'H',CTRLC	;Deselect graphics mode
	db	ESC,'X'		;Deselect monitor mode
	db	ESC,SQUOT	;Deselect protected mode
	db	ESC,DQUOT	;unlock keyboard
	db	ESC,BQUOT,'9'	;Screen display ON
	db	14h		;Aux print off
	db	ESC,'*'		;Clear screen

;This banner gets printed at the top of the screen.
;Max 42 characters on WY-30 banner bar.

	db	ESC,'F'		;Print to banner line
 endif	;WY50
 if (not DEBUG) and WY50
	db	'CP/M CroBIOS '
 endif	;(not DEBUG) and WY50
 if DEBUG and WY50
	db	'DEBUG '
 endif	;DEBUG and WY50
 if WY50
	db ((BIOSVER AND 0F00h)/256)+'0','.'
	db ((BIOSVER AND 0F0h)/16)+'0',(BIOSVER AND 0Fh) +'0'
 endif ;WY50
 if WY50 and BANKED
	db 'B'
 endif ;WY50 and BANKED
 if WY50
	db	CR
 endif ;WY50
;---------------------------------------------
;Sign-on message, only used during cold boot.
;CP/M's version number gets pasted in at LMSG3
;prior to printing. (M/PM would go in LMSG2,
;but is not currently supported.)
;---------------------------------------------
 db CR
 db 02,48,'-'	;print repeatedly
 db CR
 db MSIZE/10+'0',MSIZE MOD 10 + '0','k '

LMSG2:
 db 'CP/M '

LMSG3:
 db '1.4, copyright (c) 1979 DIGITAL RESEARCH',CR

 if BANKED
 db 'Banked/Track buffered Cromemco BIOS  version '
 endif ;BANKED
 if not BANKED
 db 02,4,' '	;print repeatedly
 db 'Track buffered Cromemco BIOS  version '
 endif ;not BANKED

 db ((BIOSVER AND 0F00h)/256)+'0','.'
 db ((BIOSVER AND 0F0h)/16)+'0',(BIOSVER AND 0Fh) +'0'
 db CR
 db 02,14,' '	;print repeatedly
 db 'by Martin Eberhard'
 db CR,LF		;(extra blank line)

 db 'Free RAM through '
HOFSET set 'A'-10
 if (((CCP-1) SHR 12) - A) SHR 15
HOFSET set '9'
 endif ;(((CCP-1) SHR 12) - A) SHR 15
 db ((CCP-1) SHR 12) + HOFSET

HOFSET set 'A'-10
 if ((((CCP-1) SHR 8) and 0Fh) - A) SHR 15
HOFSET set '9'
 endif ;((((CCP-1) SHR 8) and 0Fh) - A) SHR 15
 db (((CCP-1) SHR 8) and 0Fh) + HOFSET

 db 'FFh'

 if TUARTA or TUARTB
 db CR,'TU-ART ports:'
 endif ;TUARTA or TUARTB

 if C1TAS
 db CR,'  UC1: Serial A'
 endif ;C1TAS
 if C1TBS
 db CR,'  UC1: Serial B'
 endif ;C1TBS

 if LPTAS
 db CR,'  LPT: Serial A'
 endif ;LPTAS
 if LPTBS
 db CR,'  LPT: Serial B'
 endif ;LPTBS
 if LPTAP
 db CR,'  LPT: Parallel A'
 endif ;LPTAP

 if L1TAS
 db CR,'  UL1: Serial A'
 endif ;L1TAS
 if L1TBS
 db CR,'  UL1: Serial B'
 endif ;L1TBS
 if L1TBP
 db CR,'  UL1: Parallel A'
 endif ;L1TBP

 if R1TAS
 db CR,'  UR1: Serial A'
 endif ;R1TAS

 if R2TBS
 db CR,'  UR2: Serial B'
 endif ;R2TBS

 if P1TAS
 db CR,'  UP1: Serial A'
 endif ;P1TAS

 if P2TBS
 db CR,'  UP2: Serial B'
 endif ;P2TBS

 if PRI
 db CR,'PRI port:'
 db CR,'  LPT: Dot Matrix Interface'
 endif ;PRI

 db 0

;--------------------------------------------------
;CPU Speed message, and final bit of signon message
;(gets modified if 4 MHz detected.)
;--------------------------------------------------
MHZMSG:
 db '2 MHz Z-80 detected.'	;(gets modified)

MH4MSG:
 if DRIV35 and HDRIVE
 db 'HD, 3.5", and '
 endif ;DRIV35 and HDRIVE

 if DRIV35 and not HDRIVE
 db '3.5" and '
 endif ;DRIV35 and not HDRIVE

 if HDRIVE and not DRIV35
 db 'HD and '
 endif ;HDRIVE and not DRIV35

 db 'DD 8" disks won',SQUOT,'t work!',0

BEMSG:
 db 02,48,'-'	;print repeatedly
 db 0

;*** Assembly Check ***
;Check for BIOS too big
;**********************
ENDBIOS	equ	$

 if ((BOOT and not (ENDBIOS-1)) or (RAMSIZ*256 - (ENDBIOS+3)/4))/8000h
 ERROR: BIOS is too large
 endif ;((BOOT and not (ENDBIOS-1)) or (RAMSIZ*256 - (ENDBIOS+3)/4))/8000h

;============================================================
;Uninitialized data area
;(Not part of system memory image on disk)
;These data overwrite one-time cold boot code above.
;============================================================
	org	BOOT

;Variables

DMAADR:	ds	2	;2-byte memory adr for data transfers
CPUMHZ:	ds	1	;26,52,78 for 2,4,6 MHz
BDCTRL:	ds	1	;DCTRL port value with auto-wait set
;--------------------------------------------------
;Dynamically generated Disk Parameter Blocks (DPBs)
;(See CP/M documentation for details)
;  Bytes   Name   Function
;   0-1    SPT  Logical Sectors per track
;    2     BSH  block shift factor
;    3     BLM  block mask
;    4     EXM  extent mask
;   5-6    DSM  blocks/disk
;   7-8    DRM  max directory entries
;    9     AL0  Allocation Vector 0
;    10    AL1  Allocation Vector 1
;  11-12   CKS  Directory check vector size
;  13-14   OFF  System track offset
;--------------------------------------------------
DPBA:	ds	15
DPBB:	ds	15
DPBC:	ds	15
DPBD:	ds	15
;---------------------------------------------
;Scratch RAM area for BDOS use
;Note: the check vector sizes must be at least
;as big as the largest CKV entry in the Disk
;Parameter Blocks of the supported disks.
; ALL0n size is (max DSM)/8+1
; CHK0n size is CKS bytes
;---------------------------------------------
DIRBF:	ds	128	;Scratch directory area

 if DRIV35
ALL00:	ds	44	;drive 0 allocation vector
ALL01:	ds	44	;drive 1 allocation vector
ALL02:	ds	44	;drive 2 allocation vector
ALL03:	ds	44	;drive 3 allocation vector
 endif ;DRIV35
 if not DRIV35
ALL00:	ds	38	;drive 0 allocation vector
ALL01:	ds	38	;drive 1 allocation vector
ALL02:	ds	38	;drive 2 allocation vector
ALL03:	ds	38	;drive 3 allocation vector
 endif ;not DRIV35


CHK00:	ds	32	;drive 0 check vector
CHK01:	ds	32	;drive 1 check vector
CHK02:	ds	32	;drive 2 check vector
CHK03:	ds	32	;drive 3 check vector

 if BANKED
;----------------------------------------------------
;Sector transfer Buffer
;Used for transfering data from the track buffer bank
;when the DMA address is in the same 16K block as the
;track buffer.
;----------------------------------------------------
SECBUF:	ds	SECSIZ	;CP/M sector transfer buffer
 endif ;BANKED

 if not BANKED
;---------------------------
;Track buffer in main memory
;---------------------------
TRKBUF	ds	TBSIZE

 endif ;not BANKED

;-----------------------------------------------
;Sector ID area
;The WD1793 Read Address command reads 6 bytes
;from the header of the next sector encountered.
;IDREAD writes these 6 bytes here.
;-----------------------------------------------
IDSAVE:
IDTRAC:	ds	1	;track number
IDSIDE:	ds	1	;side number
IDSECT:	ds	1	;sector address
IDSSIZ:	ds	1	;BPS(0:3)
IDCRC1:	ds	1	;CRC 1
IDCRC2:	ds	1	;CRC 2
;------------------------------------------------
;Local stack
;Used during SECTRAN, READ, and WRITE BIOS calls.
;(Code currently needs ~18 bytes. When IDREAD is
;called, only 10 bytes of stack are needed.) 
;------------------------------------------------
 if not DEBUG
	ds	20	;local stack space
 endif ;not DEBUG
 if DEBUG
	ds	24
 endif ;DEBUG
STACK:			;(the stack grows toward 0)

;*** Assembly Check ********************************
;Check for BIOS plus data area too big.
;Note that ASM's math is limited to 16 bits.
;Note also that (despite ASM's documentation), the
;'if' pseudo-op only looks at the lsb of the result.
;***************************************************
ENDDAT	equ	$		;data area end

 if ((BOOT and not (ENDDAT-1)) or (RAMSIZ*256 - (ENDDAT+3)/4))/8000h
 ERROR: Available memory exceeded
 endif ;((BOOT and not (ENDDAT-1)) or (RAMSIZ*256 - (ENDDAT+3)/4))/8000h

	end
