;============================================================
;               BANKED & TRACK BUFFERING BIOS
;      for the CALIFORNIA COMPUTER SYSTEMS 2200 SERIES
;
;Supporting the following hardware:
;  CCS 2410 Z80 CPU with 8250 UART
;  CCS 2422 WD1793-based multimode floppy disk controller
;
;  CCS 2719 2P+2S board with an NEC 3550 spinwriter
;     or 
;  CCS 2720 Quad parallel port with an NEC 3550 spinwriter
;
;  One CCS 2065 or 2066 64K DRAM board or equivalent, with
;  bank select if track buffering is enabled here 
;
;  Optionally one CCS 2116 (16K) 2065 (64K), or 2066 (64K)
;  DRAM board with bank select, for disk track buffer
;
;  Any combination of the following disks, up to 4 drives:
;   5.25" 40-track 300 RPM minidisks: 1SSD, 1SDD, 2SSD, 2SDD
;   5.25" 80-track 300 RPM Quad Density drives, 2SDD only
;   8" 77-track Shugart-type disks: 1SSD, 1SDD, 2SSD, 2SDD
;   8" 77-track voicecoil (PerSci) disks with fast seeks:
;      Model 70: single drive, 1SSD
;      Model 277: dual drive, 1SSD
;      Model 299 & 299B: dual drive, 1SSD, 1SDD, 2SSD, 2SDD
;   3.5" 80-track 300 RPM, 2SDD only
;      (On the 50-pin interface, requires an adapter)
;   5.25" 80-track, 360 RPM, high-density drives, 2SDD only
;      (On the 50-pin interface, requires an adapter)
;
;RDR driver may optionally be modified to return with Z set
;if no character is ready. (Handy for XMODEM, etc.)
;
;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 5.25" disk. Using a non-standard number of system tracks
;would make reading disks from other machines impossible.)
;However, single-density disks may have all system tracks
;except track 0 formatted as double-density.
;
;3.5" drives and HD 5.25" drives are supported on the CCS
;2422's 50-pin (8") interface, with the appropriate
;interface adapter. (If these drives were on the 34-pin
;interface, then the CCS boot ROM on the 2422 would not be
;able to boot from them because the data rate would be set to
;the lower data rate.) Only 2-sided, double density formats
;are supported for HD and 3.5" disks. However, on these
;disks, tracks 0 and 1 are system tracks, which are formatted
;the same as system tracks on 8" disks.
;
;Fast-seeking drives, such as those made by PerSci, are
;supported when PERSCI=TRUE. The Seek Complete signal from
;such drives is not directly readable in any o the CCS 2422
;registers, and so some monkey-business is required to read
;this signal. Fast-seeking drives ar automatically detected.
;You can have a combination of normal (Shugart-type) drives
;and fast-seeking drives in the same interface with an
;appropriate interfacing cable, documente elsewhere.
;
;There are several assembly options (selectable below) that
;trade off BIOS size and functionality. Currently, all
;possible drive types can be simultaneously supported, so
;long as VERBOS=FALSE (making error messages a tad more
;terse) and FASTER=FALSE (making disk trasnsfers about 1.25
;mS slower for every 128-byte logical sector).
;
;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. This means that Z-80 instructions are
;generated the hard way, using DS and DW pseudo-operations.
;The relative branch instructions need to be checked after
;assembly: the relative address must be less than 80h for
;forward branches, and more than 7Fh for backward branches.
;============================================================
;---------------------------
;This BIOS'S revision number
;---------------------------
BIOSVER	equ	0523h	;high byte = major revision number
			;low byte=2-digit minor version
;----------------
;Revision History
;----------------
; 5.23 7 November 2021  M. Eberhard
;      Fix boot code to match MEBOOT sectors/track for all
;      drive types. Some minor squeezing. Improve comments.
;      Speed up sidedness detection for 1-sided disks for
;      drives that respond to SIDESEL.
; 5.22 6 November 2021  M. Eberhard
;      Squeeze enough that all drives can be supported with
;      with either VERBOS=FALSE or FASTER=FALSE. More
;      comments cleanup.
; 5.21 4 November 2021  M. Eberhard
;      Add interleave option for minidisks writes, to support
;      drives (e.g. MPI 9428) that cannot turn around from
;      writing to reading fast enough to reand the next
;      sector header. Add FASTER option to save code space.
;      (This makes the improvement in version 5.02 optional.)
;      Fix bug when both HDRIVE and PERSCI were TRUE.
;      Some squeezing and lots of comment cleanup.
; 5.20 1 November 2021  M. Eberhard
;      Compress code enough to make non-VERBOS messages a
;      bit less terse
; 5.19 1 November 2021  M. Eberhard
;      Compress code enough that all drive types can be
;      enabled (with VERBOS=FALSE) and the BIOS fits in a
;      63K CP/M environment.
; 5.18 29 October 2021  M. Eberhard
;      Add optional support for HD 5.25" floppy drives.
;      fix bug in IDREAD for 3,5" drives with non-512 BPS.
;      Add VERBOS option. Squeeze code a bit.
; 5.17 22 October 2021  M. Eberhard
;      Squeeze. Add optional support for QD 5.25" drives
; 5.16 18 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.
; 5.15 17 October 2021  M. Eberhard
;      Fix cold boot/warm boot logic. Squeeze code a bit.
; 5.14 15 October 2021  M. Eberhard
;      Restore BUFDSK on error in SELDSK.  Speed up disk r/w
;      loops. Measure CPU speed during init, and adjust
;      accordingly. Warn if 2 MHz CPU detected. Squeeze code
;      a bit, carrying over CroBios improvements.
; 5.13 2 May 2021  M. Eberhard
;      Fix vers. bug in minidisk select code, created in 5.12
; 5.12 11 April 2021  M. Eberhard
;      Fix significant bug that caused the wrong DPB to
;      be used sometimes after switching drives.
; 5.11 7 April 2021  M. Eberhard
;      Calculate the required CP/M DPB from compressed
;      (3-byte/entry) tables, instead of a 15 byte/entry DPB
;      per supported disk configuration. Support 256 and 1024
;      BPS (in addition to 512) for 3.5" disks.
; 5.10 5 April 2021  M. Eberhard
;      Select side 0 before waiting for the drive to be ready
;      Fix message printing bug
; 5.09 5 Jan 2021  M. Eberhard
;      Optionally support 3.5" drives on the 8" connector.
;      Create PERSCI option to remove voicecoil support.
;      Motor-on signal for 8" and 3.5" drives too.
;      Add INTEN assembly option for interrupts. (Note
;      that this BIOS has never been tested in an interrupt
;      environment.) fix a few bugs, compress code a bit
; 5.08 11 Jun 2020  M. Eberhard
;      Delay for >590 uS after writing, for trim-erase on
;      floppy drives other than Shugart full-height drives.
;      Add comments for CCS 2066 DRAM board setup. Delay
;      for ~1 mS before sampling voicecoil -Seek Complete,
;      to accomodate PerSci 299B's slow raise of this signal
;      after it receives a -Step signal.
; 5.07 2 May 2019  M. Eberhard
;      Don't ever flush buffer on cold-boot BIOS entry
; 5.06 30 Oct 2018  M. Douglas
;      Fix TTYRDR routine. Add support for the MODRDR option
;      in TTYRDR. Add missing ";" in UR1ST: in the R1NONE 
;      conditional assembly section.
; 5.05 11 Feb 2018  M. Eberhard
;      Improved algorithm for initial ID of a disk. Add
;      assembly option for non-banked buffer. Some code
;      squeezing. Some comment improvements.
; 5.04 5 February 2018  M. Eberhard
;      Support voicecoil (PerSci) drives too. Some code
;      squeezing to make it all fit.
; 5.03 10 October 2017  M. Eberhard
;      Rewrite DCTRL during SELDSK so WBOOT works always.
;      Enable interrupts except during disk ops.
; 5.02 1 September 2017  M. Eberhard
;      Speedup: Don't use intermediate transfer buffer unless
;      the DMA address is in the same 16K block as the track
;      buffer. Move the track buffer to 8000h.
; 5.01 30 August 2017  M. Eberhard
;      Test track buffer RAM during cold boot. Squeeze
;      messages by adding "repeat" function to PMSG
; 5.00 20 August 2017
;      Move track buffer into a seperate memory bank, freeing
;      RAM below F000h. (This is the equivalent of a 63K CP/M
;      implementation.)
; 4.xx Implement track buffer in main RAM
; 3.xx support 2-sided and 40-track 5.25" disks
; 2.xx Various improvements on CCS's BIOS
; 1.xx CCS versions
;============================================================
;Jumper Settings
; CCS 2410 Z80 CPU with 8250 UART
;   2MHz/4 MHz switch in 4 MHz position
;   SERIAL ADDRESS SELECT<A7:A3> = 00100
;   SER EN = ON
;   ROM EN = OFF
;   WAIT EN = OFF
;   ADR MIR = OFF
;   REFRESH = ON
;   NMI = OFF
;   PHANTOM = OFF
;   -2/4 MHZ = ON
;   JUMP EN = ON
;   <JA15:JA0> = 1111 0000 0000 0000
;
; CCS 2422 WD1793-based multimode floppy disk controller,
; for Shugart-type drives
;   A = 1-2
;   B = 2-3
;   C = 1-2
;   D = 2-3
;   E = 1-2
;   Wire from jumper D pin 11 to 50-pin connector pin 12
;     (This commects the PerSci Eject signal)
;   M MAP = No jumper
;   PR EN = No jumper
;   WAIT = No jumper
;   BANK BYTE <7:0> = Don't care
;   ROM ENABLE = 1-2
;   BANK EN = 2-3
;     
; ccs 2719 2P+2S board with an NEC 3550 spinwriter
;   1BEN = Jumper
;   1AEN = Jumper
;   1BDIR = 0
;   1ADIR = 1
;   DAISY DSBL = Jumper
;   RDY = Jumper
;   ADR SEL<7:4> = 0101
;   DLE = EN
;
; CCS 2720 Quad parallel port with an NEC 3550 spinwriter
;   PDOE = X
;   PAOE = X
;   PDDAS = I
;   PDDAV = H
;   PADAV = L
;   PADAS = N
;   PASSI = N
;   PADSI = E
;   PAIMI = H
;   PORT ADDR<A7B:A2B> = 000010
;   ROM ADDR<A15:A11> = Don't care (No ROM installed)
;
; CCS 2065 64K DRAM board (Main Memory)
;   BLOCK SEL <4:1> = ME BE ME ME
;   A<7:0> = 01000000
;   D<7:0> = N/C N/C N/C N/C N/C N/C N/C Jumper
;   CPU SEL = Z80
;   PHANT = ON
;   RESET = ON
;
; CCS 2065 64K DRAM board (as Track Buffer Memory)
;   BLOCK SEL <4:1> = N/C BE N/C N/C
;   A<7:0> = 01000000
;   D<7:0> = Jumper N/C N/C N/C N/C N/C N/C N/C
;   CPU SEL = Z80
;   PHANT = ON
;   RESET = OFF
;
; CCS 2066 64K DRAM board (Main Memory)
;   J1 Bank Select header: jump "C" to "0" (pin 8 to pin 11)
;   Multibank Jumpers: don't care
;   Block Address jumpers <DCBA> = 11 10 01 00
;   Phantom Enable Jumpers (PHTM) <DCBA> = jumper N/C N/C N/C
;   Write Phantom jumper (V) = D
;   Reset Enable jumpers DCBA = DEEE
;   Bank Port Address (A7:A0> = default (01000000)
;   CPU Select = default (Z80)
;
; CCS 2066 64K DRAM board (alt. Track Buffer Memory)
;   J1 Bank Select header: jump "C" to "7" (pin 1 to pin 11)
;                          jump "A", "B", and "D" to "6"
;                               (pin 2 to pins 9, 10, and 12)
;   Multibank Jumpers: don't care
;   Block Address jumpers <DCBA> = 11 10 01 00
;   Phantom Enable Jumpers (PHTM) <DCBA> = N/C N/C N/C N/C
;   Write Phantom jumper (V) = D
;   Reset Enable jumpers DCBA = DDDD
;   Bank Port Address (A7:A0> = default (01000000)
;   CPU Select = default (Z80)
;
; CCS 2116 16K Rev C SRAM board (alt. Track Buffer Memory)
;   BANK BYTE<7:0> = 10000000
;   BANK ADDRESS<7:0> = 01000000
;   BANK RESET = A
;   WAIT = OFF (Assuming at least 300 nS SRAM chips)
;   PHANTOM = (Don't Care)
;   D<15:12> = 1011
;   D(ME/BE) = BE
;   c<15:12> = 1010
;   C(ME/BE) = BE
;   B<15:12> = 1001
;   B(ME/BE) = BE
;   A<15:12> = 1000
;   A(ME/BE) = BE  
;
; CCS 2116 16K Rev B SRAM board (alt. Track Buffer Memory)
; (Same as the JTM 16K SRAM board)
;   BANK BYTE<7:0> = 10000000
;   BANK ADDRESS<7:0> = 01000000
;   BD = Open
;   BO = Open
;   BANK RESET = W2
;   WAIT = Open (Assuming at least 300 nS SRAM chips)
;   PHANTOM = (Don't Care)
;   D<15:12> = 1011
;   c<15:12> = 1010
;   B<15:12> = 1001
;   A<15:12> = 1000
;============================================================
;Supports the following drive types:
;
;1-Sided Single Density (1SSD)
;      40 Track  77 TracK
; BPS    5.25"     8"
; 128    YES       YES
; 256    YES       YES
; 512    YES       YES
;1024    NO        NO
;
;1-Sided Double Density (1SDD)
;      40 Track  77 TracK
; BPS    5.25"     8"
; 128    No        No
; 256    YES       YES
; 512    YES       YES
;1024    YES       YES
;
;2-Sided Single Density (2SSD)
;      40 Track  77 TracK
; BPS    5.25"     8"
; 128    YES       YES
; 256    YES       YES
; 512    YES       YES
;1024    NO        NO
;
;2-Sided Double Density (2SDD)
;      40 Track  77 TracK  80 Track  80 Track  80 Track
; BPS    5.25"     8"      QD 5.25"  HD 5.25"    3.5"
; 128    No        No        No        No        No
; 256    YES       YES     QDRIVE    HDRIVE    DRIV35
; 512    YES       YES     QDRIVE    HDRIVE    DRIV35
;1024    YES       YES     QDRIVE    HDRIVE    DRIV35
;
;On 8", 5.25" HD, and 3.5" disks, tracks 0 and 1 are system
;tracks. On all other 5.25" disks, tracks 0 through 2 are
;system tracks. System tracks may be formatted diferently
;than the rest of the disk. Track 0 is normally formatted as
;128 BPS, single-density, regardless of how the rest of the
;disk is formatted. Side 1 of the system tracks are not used
;by CP/M or for the boot image. In order to fit this BIOS in
;the system area, all but the first system track are normally
;formatted as double-density (even for an otherwise single-
;density disk). 
;
;Connecting a 3.5" Drive and/or HD 5.25" Drive
;
;3.5" drives are supported if DRIV35=TRUE, and HD 5.25"
;drives are supported when HDRIVE=TRUE. These drives are
;connected via the 2422's 50-pin (8-inch) connector, using an
;adapter on the cable that routes the drive signals to the
;correct 2422 signals. This adapter must drive the 2SIDED
;and READY signals on the 2422 interface whenever the 3.5"
;drive is selected. It should also drive the MOTOR ON
;signal on the drive. Note that most 3.5" drives are
;hardwired to be drive B, so the drive select signal must
;be routed on the adapter with this in mind.
;
;Track Buffer Strategy
;
;This BIOS only transfers data to and from the disk one whole
;track at a time, via an 9K-byte track buffer: big enough for
;the largest track possible on an supported 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
;7, while RAM bank 0 is the main memory. If the banked RAM
;occupies the same address as the source or destination of a
;DMA transfer then READ and WRITE calls will transfer data
;through an iontermediate 128-byte buffer that is located in
;main memory, in a segment that is still enabled when RAM
;bank 7 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 ias 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 SEEK.
;
;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 MOSS EPROM (which is on the CCS 2422 disk
;controller, and contains the boot loader as well as a
;monitor program) occupies the top 4K of the memory space
;between addresses F000h and FFFFh. This ROM is disabled
;(and DRAM is enabled) upon the first write to the bank
;select port (port 40h). The boot loader must write to this
;port prior to loading CP/M into this portion of memory.
;
;If the BANKED option is true, then this BIOS uses CCS's bank
;select system to locate the 9K track buffer out of main
;memory, in its own private memory bank. CCS 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 7 (selected by writing 80h to port 40h) is the
;track buffer bank.
;
;Also for the BANKED option, the CCS RAM boards (both main
;and th track buffer memory) should be set up such that only
;the 16K of RAM between addresses 8000 and BFFFh 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 9K track buffer is located at address 8000h. The
;remaining memory in the track buffer bank is unused.)
;
;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
;after the BIOS was loaded from disk, the new drive bit is
;cleared for that drive, and the drive is restored to track 0
;(via a call to DRIVID), where the  disk controller can
;detect whether it is on the 34-pin interface (and therefore
;has the lower data rate), or on the 50-pin interface (and
;therefore has the higher data rate).
;
;A lower data rate drive is either a 40-track 5.25" drive or
;an 80-track Quad Density 5,25" drive, unless QDRIVE=FALSE.
;If QDrive=TRUE then this bios figures out which by seeking
;track 41, and then reading that track's ID. if it reads as
;41 then it is assumed to be an 80-track drive. Otherwise
;it's a 40-track drive. 
;
;If it's a higher data rate drive and DRIV35=TRUE, then this
;BIOS times the index frequency to determine if it is a 3.5"
;disk (which rotates at 300 RPM). Otherwise, it is an 8" disk
; or an HD 5.25" disk (which both rotate at 360 RPM).
;
;If it's not a 3.5" disk and if HDRIVE=TRUE, then this BIOS
;will seek and ID  track 77 to determine whether it is a
;77-track 8" drive or an 80-track HD 5.25" drive.
;
;If it seems to be an 8" drive, then the drive's PRMTBL entry
;is updated to support a single-sided SA801 8-inch disk drive
;(setting both the data rate and step rate).
;
;If the drive is an 8" drive and PERSCI is TRUE, then DRIVID
;will also test to see if it is a fast-seeking (voicecoil)
;drive by performing a pair of null-seeks, unloading and then
;loading the head (fast enough that the head does not
;actually unload). Immediately afterwards, the WD1793 status
;is read, and the HEAD LOADED bit is checked. This bit will
;remain false for around 35 mS with a Shugart-type drive,
;because of the external HLT 1-shot. But the SEEK COMPLETE
;signal from a voicecoil drive will clear this 1-shot
;whenever it is not seeking. Thus, the HEAD LOADED bit will
;be set when WD1793 status is read if it's a voicecoil drive.
;
;Detecting the disk type:
;
;The first time a disk is accessed after a call to warm boot
;or cold boot, SELDSK steps past the system tracks to
;determine the density, sidedness, and BPS for the rest of
;the disk. This is because the system tracks (not only track
;0) may be formatted differently than the rest of the disk.
;
;The disk is then tested to see if it is a 2-sided disk.
;This is a simple register test for 8" disk, but requires
;sampling side 1 and looking for errors on a 5.25" disk. 3.5"
;disk, QD disks, and HD disks are always assumed to be always
;2-sided.
;
;The ID, sidedness, and seek-type information is used to fill
;in the PRMTBL entry and Disk Parameter Header (DPH) for this
;drive, based on its disk size (8", 5.25", or 3.5"), its
;sidenness, sector size, density, and seek type, some as
;determined by DRIVID, other (density, sector size) as
;determined during SELDSK.
;
;Until a 2-sided disk is seen in an 8" drive, these drives
;appear the same as a (single-sided) drive. Once a 2-sided 8"
;disk is ever seen in a given drive, the step rate for that
;drive in PRMTBL is (permanently) updated to the faster SA851
;drive's step rate.
;
;Once the disk and drive type are known, SELDSK generates the
;correct Disk Parameter Block (DPB) for that drive, before
;returning. Each drive has a unique DPB location.
;============================================================

;============================================================
;Assembly Options
;============================================================
FALSE	equ	0
TRUE	equ	not FALSE
;-----------------------------------------------------
;Option to put the track buffer in its own memory bank
;-----------------------------------------------------
BANKED	equ	TRUE
;------------------------------------------------------------
;Option to allow direct transfers to/from the track buffer
;from/to the DMA address when possible. (Otherwise, transfers
;pass through an intermediate buffer.) This makes every 128-
;byte disk transfer to/from a DMA address that's in the lower
;32K of RAM about 1.25 mS faster (with a 4 MHz CPU).
;FASTER=FALSE saves 22 (16h) bytes
;------------------------------------------------------------
FASTER	equ	TRUE
;---------------------------------
;Option to support 3.5" drives
;(on the 50-pin interface)
;DRIV35=FALSE save 100 (64h) bytes
;---------------------------------
DRIV35	equ	TRUE
;------------------------------------------------
;Option to support 80-track 'High Density' drives
;(on the 50-pin interface)
;HDRIVE=FALSE saves 36 (24h) bytes
;------------------------------------------------
HDRIVE equ	TRUE
;------------------------------------------------
;Option to support 80-track 'Quad Density' drives
;(on the 34-pin interface)
;QDRIVE=FALSE saves 46 (2Eh) bytes
;------------------------------------------------
QDRIVE equ	TRUE
;-------------------------------------------
;Option to support PerSci (voicecoil) drives
;PERSCI=FALSE saves 62 (3Eh) 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 41 (29h) bytes and essentially
;doubles write speed for minidisks.
;-------------------------------------------------------
INTRLV	equ TRUE
;-----------------------------------------------------
;Option to choose slightly more verbose error messages
;VERBOS=FALSE saves 11 (0Bh) bytes
;-----------------------------------------------------
VERBOS	equ	FALSE
;-----------------------------------------
;Option to enable interupts
;Caution: INTEN=TRUE has never been tested
;-----------------------------------------
INTEN	equ	FALSE
;------------------------------------------
;Debug option includes PRINTA & PAHEX, etc.
;and seriously abbreviates error messages
;------------------------------------------
DEBUG	equ	FALSE
;--------------------------------------------------
;Optional Terminal Support
;This option enables initializing the terminal and
;writing to its banner line during cold-boot
;--------------------------------------------------
WY50 equ TRUE
;-----------------------------------------------------
;modified RDR: option: RDRIN returns with Z set if no
;character waiting, instead of waiting for a character
;-----------------------------------------------------
MODRDR	equ	FALSE
;-----------------------------
;I/O port installation options
;-----------------------------	
;UC1: selection- exactly one of these must be true
C119SA	equ	FALSE	;CCS 2719 serial channel A
C119SB	equ	FALSE	;CCS 2719 serial channel B
C1NONE	equ	TRUE	;no user console 1

;LPT: selection- exactly one of these must be true
LP19P1	equ	TRUE	;CCS 2719 parallel port 1
LP20P1	equ	FALSE	;CCS 2720 parallel port A
LPNONE	equ	FALSE	;no line printer

;UL1: selection- exactly one of these must be true
L119SB	equ	TRUE	;CCS 2719 serial channel B
L119P2	equ	FALSE	;CCS 2719 parallel port 2
L1NONE	equ	FALSE	;no user line printer 1

;UR1: selection- exactly one of these must be true
R119SA	equ	TRUE	;CCS 2719 serial channel A
R1NONE	equ	FALSE	;no user reader 1

;UR2: selection- exactly one of these must be true
R219SB	equ	TRUE	;CCS 2719 serial channel B
R2NONE	equ	FALSE	;no user reader 1

;UP1: selection- exactly one of these must be true
P119SA	equ	TRUE	;CCS 2719 serial channel A
P1NONE	equ	FALSE	;no user punch 1

;UP2: selection- exactly one of these must be true
P219SB	equ	TRUE	;CCS 2719 serial channel B
P2NONE	equ	FALSE	;no user punch 1

CCS2719	equ LP19P1 or L119SB or R119SA or P119SA or C119SA or C119SB or R219SB or P219SB
CCS2720	equ LP20P1
;------------------------------
;Memory and program size limits
;------------------------------
RAMSIZ	equ	64		;K-bytes of system RAM

 if BANKED
MSIZE	equ	RAMSIZ-1	;CP/M standard memory size in Kbytes
 endif ;BANKED

 if not BANKED
MSIZE	equ	RAMSIZ-9
 endif ;not BANKED

;----------------
;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:
;============================================================
;Equates
;============================================================
;----------------------------------
;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
;-------------------------
;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	10000000b	;track buffer's bank
BNKSIZ	equ	4000h		;Memory bank size

TRKBUF	equ	8000h		;track buffer in TBBANK
 endif ;BANKED

SECSIZ	equ	128		;logicasl sector size
				;(must be 128)
 if DRIV35
TBSIZE	equ	10*1024		;max track buffer size
 endif ;DRIV35
 if not DRIV35
TBSIZE	equ	8*1024		;max track buffer size
 endif ;not DRIV35
;------------
;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
;---------------------------------------
;CCS 2422 floppy disk controller equates
;---------------------------------------
BCTRL	equ	4		;Control port 2
BSTAT	equ	4		;status port 2
DSTAT	equ	30h		;disk status port
DCMMD	equ	DSTAT		;disk command port
DTRCK	equ	DSTAT+1		;track port
DSCTR	equ	DSTAT+2		;sector port
DDATA	equ	DSTAT+3		;data port
DFLAG	equ	DSTAT+4		;disk flag port
DCTRL	equ	DSTAT+4		;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: 5.25" disk mode halves the clock rate):
;
;  value   5.25"      3.5" & 8"
; RS03MS    6 mS        3 MS
; RS06MS   12 mS        6 MS
; RS10MS   20 mS       10 MS
; RS15MS   30 mS       15 MS

STEP5	equ	RS10MS		;SA400L Minidisk rate=20 mS
				;(SA450 is the same)
STEP5H	equ	RS03MS		;High-density 5.25" rate=6 mS
STEP5Q	equ	RS03MS		;Quad-density 5.25" rate=6 mS
STEP8	equ	RS10MS		;SA800 8" 1-sided rate=8 mS
STP8D	equ	RS03MS		;SA851 8" 2-sided rate=3 mS
STEPVC	equ	RS10MS		;Voicecoil step rate (WD1793 is
				;set up for much faster rates.)

STEP35	equ	RS03MS		;3.5" drive step rate

;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

;Control register 1 (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

;Control register 2 (BCTRL) output bits

BCEJECT	equ	00000100b	;remote eject (PerSci)
BCFSEKN	equ	00010000b	;fast seek:
				;0 for voicecoil drives
BCSIDE0	equ	01000000b	;0=side 1, 1=side 0
BCROMEN	equ	10000000b	;enable onboard ROM

;Composite BCTRL values used to select disk side
; Note: ROM is actually disabled via the page register
; Note: fast seek disabled via a hardware jumper

SIDE0	equ	BCROMEN+BCFSEKN+BCSIDE0	;Needs to be <>0
SIDE1	equ	BCROMEN+BCFSEKN		;Needs to be <>0

;WD1793 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

;Status register 1 (DFLAG) input bits

DFINTRQ	equ	00000001b	;WD1793 INTRQ signal, active high
DFDS1	equ	00000010b	;DS1 in control reg 1
DFDS2	equ	00000100b	;DS2 in control reg 1
DFDS3	equ	00001000b	;DS3 in control reg 1
DFDS4	equ	00010000b	;DS4 in control reg 1
DFSHLD	equ	00100000b	;WD1793 HLD, active high
DFAUTOB	equ	01000000b	;autoboot jumper, 0=autoboot
DFSDRQ	equ	10000000b	;WD1793 DRQ signal, 1=ready

;Status reg 2 (BSTAT) input bits

BTRK0	equ	00000001b	;when on track 0: 0=5.25" disk
BMINI	equ	00000010b	;-DCTRL reg bit: 1=5.25" disk
BWPRT	equ	00000100b	;0=disk is write-protected
BSIDE0	equ	00001000b	;SIDE0 bit in ctrl reg 2
BINDEX	equ	00010000b	;index signal, active low
BDDEN	equ	00100000b	;DDEN signal in ctrl reg 1
BSIDED	equ	01000000b	;0=selected disk is 2-sided
;-----------------------------------------------------
;Equates for 8250-based UART on the CCS 2810 CPU board
;-----------------------------------------------------
BASE10	equ	20h		;base address of 2810 I/O

SDATA	equ	BASE10		;data port
SINTEN	equ	SDATA+1		;interrupt enable port
SIDENT	equ	SDATA+2		;interrupt ID port
SLCTRL	equ	SDATA+3		;line control port
SMDMCT	equ	SDATA+4		;modem control port
SLSTAT	equ	SDATA+5		;line status port
SMSTAT	equ	SDATA+6		;modem status port

;Line status register bits

RXRDY	equ	00000001b	;Rx data ready
TXMTY	equ	00100000b	;Tx buffer empty

;Modem status register bits

RTSSTA	equ	00010000b	;RTS bit
DTRSTA	equ	00100000b	;DTR bit

;When autoboot jumper is enabled, the 2810
;serial port will be initialized to SBAUD.
;Other baud rates encoded as follows:
;
; baud rate    SBAUD
;      50      2304
;      75      1536
;     110      1047
;     134.5     857
;     150       768
;     300       384
;     600       192
;    1200        96
;    1800        64
;    2000        58
;    2400        48
;    3600        32
;    4800        24
;    7200        16
;    9600        12
;   19200         6
;   38400         3
;   56000         2

SBAUD	equ	12	;9600
;----------------------
;CCS 2719 2P+2S equates
;----------------------
 if CCS2719
BASE19:	equ	50h		;base address of 2719

CTC0	equ	BASE19		;CTC addresses
CTC1	equ	CTC0+1
CTC2	equ	CTC1+1
CTC3	equ	CTC2+1

SIOAD	equ	BASE19+4	;SIO channel A data reg
SIOAC	equ	SIOAD+1		;SIO channel A command reg
SIOAS	equ	SIOAC		;SIO channel A status reg
SIOBD	equ	SIOAS+1		;SIO channel B data reg
SIOBC	equ	SIOBD+1		;SIO channel B command reg
SIOBS	equ	SIOBC		;SIO channel B status reg

PA1AD	equ	BASE19+8	;PIA1 channel A data reg
PA1ADD	equ	PA1AD		;PIA1 channel A data DIR reg
PA1bD	equ	PA1AD+1		;PIA1 channel B data reg
PA1bDD	equ	PA1bD		;PIA1 channel B data DIR reg
PA1AC	equ	PA1bD+1		;PIA1 channel A command reg
PA1AS	equ	PA1AC		;PIA1 channel A status reg
PA1bC	equ	PA1AC+1		;PIA1 channel B command reg
PA1bS	equ	PA1bC		;PIA1 channel B status reg

PA2AD	equ	BASE19+12	;PIA2 channel A data reg
PA2ADD	equ	PA2AD		;PIA2 channel A data DIR reg
PA2BD	equ	PA2AD+1		;PIA2 channel B data reg
PA2BDD	equ	PA2BD		;PIA2 channel B data DIR reg
PA2AC	equ	PA2BD+1		;PIA2 channel A command reg
PA2AS	equ	PA2AC		;PIA2 channel A status reg
PA2BC	equ	PA2AC+1		;PIA2 channel B command reg
PA2BS	equ	PA2BC		;PIA2 channel B status reg

PIAMOD	equ	00111100b	;PIA operating mode
PIASTB	equ	00110100b	;strobes CB2 low
DATOUT	equ	11111111b	;PIA data direction for output

SIODCD	equ	00001000b	;DCD handshake
SIOTBE	equ	00000100b	;Tx buffer empty
SIORCA	equ	00000001b	;Rx character available

POFLTN	equ	00010000b	;device -fault
POSLCN	equ	00000100b	;device -select
POPO	equ	00000010b	;device paper out
POBUSY	equ	00000001b	;device busy

PREADY	equ	10000000b	;port ready

;Baud rate values, assuming the 2719 is configured to use its
;onboard 4 MHz crystal oscillator for generating baud rates
;
; Baud Rate  CTCMOD  CTCDIV
;     75      07h     208
;    110      07h     142
;    134.5    07h     116
;    150      07h     104
;    300      07h      52
;    600      47h     192
;   1200      47h      96
;   1800      47h      64
;   2000      47h      58
;   2400      47h      48
;   3600      47h      32
;   4800      47h      24
;   7200      47h      16
;   9600      47h      12
;  19200      47h       6
;  38400      47h       3
;  57600      47h       2
; 115200      47h       1

CTCMD0	equ	47h		;port A CTC: 38400 baud
CTCDV0	equ	3
CTCMD1	equ	47h		;port B CTC: 38400 baud
CTCDV1	equ	3
 endif ;CCS2719
;-----------------------------------
;CCS 2720 Quad parallel port equates
;-----------------------------------
 if CCS2720
BASE20	equ	08h		;base address of 2720

PPORTA	equ	BASE20		;port A I/O
PPORTB	equ	PPORTA+1	;port B I/O
PPORTC	equ	PPORTA+2	;port C I/O
PPORTD	equ	PPORTA+3	;port D out only
PSTAT	equ	PPORTA+3	;port status input

PPAIMT	equ	00000001b	;port A input empty
PPADAV	equ	00000010b	;port A output available
PPBIMT	equ	00000100b	;port B input empty
PPBDAV	equ	00001000b	;port B output available
PPCIMT	equ	00010000b	;port C input empty
PPCDAV	equ	00100000b	;port C output available
PPDDAV	equ	01000000b	;port D output available

;Equates for spinwriter support with port D out as control

PPASTR	equ	11111110b	;active low data strobe
PPAAF	equ	11111101b	;active low autofeed
PPAINI	equ	11111011b	;active low init
PPASLI	equ	11110111b	;active low select to printer

;Port A spinwriter control with port a input for status

PPABSY	equ	00000001b	;active high busy
PPAPE	equ	00000010b	;active high paper empty
PPASEL	equ	00000100b	;low means local mode
PPAERR	equ	00001000b	;active low printer error
 endif ;CCS2720
;----------------------------
;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
BIT2B	equ	050CBh		;BIT 2,b
BIT3B	equ	058CBh		;BIT 3,b
BIT4B	equ	060CBh		;BIT 4,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)

SET4M	equ	0E6CBh		;SET 4,(hl)
SET6M	equ	0F6CBh		;SET 6,(hl)
SET7M	equ	0FECBh		;SET 7,(hl)

RES7E	equ	0BBCBh		;RES 7,e
RES5M	equ	0AECBh		;RES 5,(hl)
RES6M	equ	0B6CBh		;RES 6,(hl)
RES7M	equ	0BECBh		;RES 7,(hl)

RRB	equ	0CB08h		;RR B

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

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.
;
;This BIOS uses Page 0 RAM only for temporary variables,
;so that a transient program can overwrite Page 0 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

;-----------------------------------------------
;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

;Ununitialized variables

SCOUNT:	ds	1	;Seek retry counter
RCOUNT:	ds	1	;R/W retry counter
BDCTRL:	ds	1	;DCTRL port value with auto-wait set

 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
;3 bytes for each drive:
; Byte 0: step rate etc.
;   bits 0:1=step rate per WD1793
;   bit 2 = 0 PRMQD: 1 if 5.25" quad-density
;   bit 3 = 0 PRMHD: 1 if 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 disk hasn't been seen since boot
; Byte 1: DCTRL value
;   bits 0:3: drive select bits
;   bit 4: DCMAXI: 1 means higher data rate (8", HD, or 3.5")
;                  0 means lower data rate (5.25" 'minidisk')
;   bit 5: DCMDMO (Motor on)
;   bit 6: DCDDEN (Density)
;   bit 7=0 (auto-wait gets OR'd onto this bit)
; Byte 2: Sector size
;   bits 0:2=sector size {0-3} means {128-1024}
;   bits 3:7=0
;Bytes 0 and 1 are initialized at load for cold boot,
;assuming slow DD 5.25" disks, These entries get updated by
;DRIVID if an 8" or 3.5" disk is discovered, and by IDREAD
;when SD/DD and BPS are known. Byte 2 is initialized for
;512 BPS, for 3.5" disks that don't get ID'd
;
;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 drive select values:
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:

;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.
;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	'P '			;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		;01 becomes drive letter
	db	' writ','e'+80h

WPMSG:	db	'Write protect- '	;Fall into DNRMSG

DNRMSG:	db	'Drive ',01h		;01 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:	db	01h			;gets drive letter
	db	': writ','e'+80h

WPMSG:	db	'Write prot '		;Fall into DNRMSG

DNRMSG:	db	01h			;gets drive letter
	db	': not read','y'+80h

MPEND	equ	$+1

ERRMSG:	db	' error'		;Fall into CRMSG
CRMSG:	db	CR,0			;LF gets appended
 endif ;not VERBOS and not DEBUG

;***************************************************
;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

;***Error Reboot**************************************
;Print message at hl, wait for user input, cold boot.
;(We use cold boot because we don't want to flush the
;buffer if we have already failed due to disk errors.)
;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
	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
 if INTEN
	ei			;enable interrupts
 endif ;INTEN

	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 restore the head to track 0, to
;discover whether or not this is a 5.25" disk (which is only
;possible when the head is on track 0). If it is not a 5.25"
;drive, then we also time a disk revolution to determine
;whether it is a 3.5" or 8" drive.
;
;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
;Local stqack depth: 18
;************************************************************
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 we must ID the disk
;and create the appropriate DPB. Otherwise, return
;with success.
;  b=PRMTBL(SEKDSK)
;  c=selected drive=SEKDSK
;  d=0
;  hl=DPH address
;--------------------------------------------------
	dw	BIT7B		;0 means logged in
	rz
;-------------------------------------------------
;The selected disk has never been seen, so flush
;the buffer and take a look at the drive and disk
;(to determine its density, BPS and sidedness),
;and then generate the correct DPB for this disk.
;  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 whether it is a 3.5", 5.25", or 8" drive
;  b=PRMTBL(BUFDSK)
;---------------------------------------------------
	dw	BIT6B		;never seen this drive?
	cnz	DRIVID		;new drive: find its size
				;updates and  returns
				;..hl=PRMTBL(BUFDSK)

	db	JRNZ,SELERR-$-1	;DRIVID error?
;------------------------------------------------------
;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

	xra	a
	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:	mov	b,a		;0 for MAXI, 1 for mini
	adi	2		;1st non-system track
	sta	BUFTRK		;target for SEEKID

	call	SEEKID		;could be a null seek
				;will retry if wrong track
				;hl=PRMTBL(BUFDSK)
	db	JRNZ,SELERR-$-1	;Seek error?
				;(hop skip there)

 if QDRIVE
	mov	d,m		;PRMTBL(BUFDSK) value
 endif ;QDRIVE

	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
;---------------------------------------------------------
;Figure out sidedness
;  SEEKID made BSTAT current
;  b=0 for high data rate drives (on the 50-pin interface)
;   =1 for low data rate drives (on the 34-pin interface)
;  c=DCTRL value
;  d=PRMTBL(BUFDSK) value (if QDRIVE)
;  e= BPS(0:3) from IDRD0
;  hl=PRMTBL(BUFDSK)+2
;---------------------------------------------------------
	db	DJNZ,SDSK5-$-1	;jump if it's high data rate

 if QDRIVE
;------------------------------------------------
;Low data rate rate drive detected (on the 34-pin
;interface). If it's a quad density drive, then
;we already know it's 2-sided, double-density
;  b=0
;  d=PRMTBL(BUFDSK) value
;  e= BPS(0:3) from SEEKID
;------------------------------------------------
					;assume QD for a sec
	mov	a,e			;a=table offset
	mvi	l,(SELTBLQ and 0FFh)-2	;only DD, 2-sided

	dw	BIT2D			;test PRMQD
	db	JRNZ,SDSK10-$-1		;jump if it's QD
 endif ;QDRIVE

;-----------------------------------------------------------
;40-track 5.25" disk Sidedness: Look at side 1 to see if
;this is a 2-sided disk. There are 3 possibilities: 
;1) This is a 1-sided drive. In this case, "side 1" will be
;   readable (since the side signal is ignored), but the
;   side will identify as side 0.
;2) This is a 1-sided disk in a 2-sided drive. In this case,
;   we will (probably) read garbage from the side, and the
;   IDREAD will fail.
;3) this is a 2-sided disk in a 20sided drive. In this case,
;the IDREAD will be successful, and the side will be 1.
;  b=0
;  c=DCTRL value
;  e=BPS(0:3)
;-----------------------------------------------------------
	mvi	a,SIDE1		;select side 1 of new disk
	out	BCTRL

	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,SDSK4-$-1	;Z for good read

;Error in IDREAD implies a 1-sided disk in a 2-sided drive.
;Reset to side 0 and the select table for 1-sided drive.

	call	IDRD0		;reset to side 0
				;BSTAT is again current
				;d=found track number
				;e=BPS(0:3)
				;a=0 on success

SDSK4:	mvi	l,SELTBL5 and 0FFh ;5.25"  entries

	db	JRZ,SDSK8-$-1	;error reading side 0?

SELERR:	jmp	SDEXIT		;error reading ID on side 0
				;(Z is clear)
;------------------------------------------------
;high data rate drive detected (on the 50-pin
;interface). If it's a 3.5" or HD drive, then we
;already know that it is 2-sided, double density.
;  b=0FFh
;  c=DCTRL value
;  e=BPS(0:3)
;  hl=PRMTBL(BUFDSK)+2
;------------------------------------------------
SDSK5:	dcx	h			;point PRMTBL byte 0
	dcx	h			;hl=PRMTBL(BUFDSK)

	inr	b			;a handy 0

 if DRIV35 and HDRIVE
	mov	a,m
	ani	PRMHD+PRM35		;neither 3.5" nor HD?
	db	jrz,SDSK6-$-1		;it's an 8" drive

	ani	PRM35			;3.5" drive?
	mov	a,e			;a=table offset		
	mvi	l,(SELTBL3 and 0FFh)-2	;only DD, 2-sided
	db	JRNZ,SDSK10-$-1		;jump if it's QD

	mvi	l,(SELTBLH and 0FFh)-2	;Must be HD
	db	JR,SDSK10-$-1		;jump if it's QD
 endif ;DRIV35 and HDRIVE

 if DRIV35 and not HDRIVE
	dw	BIT4M			;PRM35: 3.5" drive?
	db	JRZ,SDSK6-$-1		;nope

	mov	a,e			;a=table offset
	mvi	l,(SELTBL3 and 0FFh)-2	;only DD, 2-sided
	db	JR,SDSK10-$-1		;jump if it's QD
 endif ;DRIV35 and not HDRIVE

 if not DRIV35 and HDRIVE
	dw	BIT3M			;PRMHD: HD drive?
	db	JRZ,SDSK6-$-1		;if it, it's an 8" drive

	mov	a,e			;a=table offset
	mvi	l,(SELTBLH and 0FFh)-2	;only DD, 2-sided
	db	JR,SDSK10-$-1		;jump if it's QD
 endif ;not DRIV35 and HDRIVE
;----------------------------------------------------
;8" Sidedness: Look at BSIDED bit in BSTAT
;If we ever see a 2-sided disk in this 8" drive, then
;we know it can step at the higher SA851 step rate.
;So we permanently set this 8" drive for the faster
;step rate if we see a 2-sided disk here.
;  b=0
;  c=DCTRL value
;  e=BPS(0:3)
;  hl=PRMTBL(BUFDSK)
;----------------------------------------------------
SDSK6:	in	BSTAT			;2-sided 8"?
	cma
	ani	BSIDED			;0 means 1-sided disk
	db	JRZ,SDSK7-$-1

;Set up PRMTBL entry for 8" 2-sided (faster) step rate
	mvi	m,STP8D			;fast SA850 rate

SDSK7:	mvi	l,SELTBL8 and 0FFh	;8" select tables
;---------------------------------------------
;8" drive or 40-track 5.25" drive: Compute
;table offset for 2-sidedness if detected
;  a=0 for 1-sided drive, NZ for 2-sided drive
;  b=0
;  c=DCTRL value
;  e=BPS(0:3)
;  hl=address of SELTBL group
;---------------------------------------------
SDSK8:	ora	a		;1-sided?
	db	JRZ,SDSK9-$-1
	mvi	a,3		;2-sided offset
SDSK9:	mov	d,a
;-------------------------------------------------------
;8" drive or 40-track 5.25" drive:  Compute complete
;table offset based on sector size and density.
;  b=0
;  c = DCTRL value
;  d = 0 for 1-sided disk, 3 for 2-sided disk
;  e=BPS(0:3)
;  hl = address of Select table for selected disk
;      size. Note that 1024 BPS is illegal for SD disks,
;      and 128  BPS is illegal for DD disks. The 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
;---------------------------------------------
;All drive types: compute select table address
;  a=offsets for BPS, density, and sidedness
;  b=0
;  e=BPS(0:3)
;  hl=address of SELTBL group
;---------------------------------------------
SDSK10:	add	a		;2 bytes per entry
	mov	c,a		;bc=table offset
	dad	b		;(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 page address
	stax	b		;DPH byte 0 is page address

	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=this drive's select table entry address
;  (therefore h=TPAGE)
;=======================================================
;--------------------------------------------
;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
;  hl=DPBS table entry for this drive type
;---------------------------------------------
	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(BUFDSK)
;------------------------------------
;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(BUFDSK) 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, else
	lxi	d,0		;..error 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 SEKTRK

;***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

	xra	c		;80h for side 1
	rrc			;bit in place for BCSIDE0
	xri	SIDE0		;bit 6 is 1 for side 0
	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, SEKDSK
	inx	h		;..to BUFTRK, BUFSID, BUFDSK
	db	DJNZ,(RWS1-$-1)xor 0FF00h
				;b=0, hl=TBSTAT at loop end

	xra	m		;TBSTAT = 0 if buffer invalid
				;(clear carry too)
	db	JRNZ,RWS3-$-1	;valid and the right track
				;(01h or FFh) no flush or load

RWS2:	call	BFLUSH		;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 and FASTER
;-------------------------------------------------------
;Determine if we must use the intermediate sector buffer
;(If any part of the the logical sector's DMA addresses
;is in the same 16K memory block as the track buffer,
;assuming a misaligned DMA address.)
;  b=0
;  c=0 for write, 1 for read
;  de=DMA address
;  hl=logical sector address in the track buffer
;-------------------------------------------------------
	mov	a,d		;DMA address/256
	sui	TRKBUF/256	;offset to 0
	inr	a		;account for offset test
	cpi	BNKSIZ/256+1	;carry set to use SECBUF
	db	JRNC,RWS6-$-1	;no carry: no SECBUF needed
 endif ;BANKED and FASTER

 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 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-->track buffer
;  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-->DMAADR
;  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		;destination address
	lxi	d,SECBUF	;1st transfer destination
	push	d		;SECBUF
	dw	LDIR		;(hl) --> SECBUF
	pop	h		;SECBUF
	pop	d		;recover final destination

;Set up for 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
 endif ;BANKED

 if BANKED and FASTER
	db	JR,RWS7-$-1	;go transfer sector
;---------------------------------------------
;Direct transfer track buffer <--> DMA address
;  b=0
;  c=0 for write, 1 for read
;  de=DMA address
;  hl=logical sector address in track buffer
;---------------------------------------------
RWS6:	mvi	a,TBBANK	;switch track buffer in
	out	BBANK
 endif ;BANKED and FASTER


 if FASTER or not BANKED
	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
 endif ;FASTER or not BANKED

;--------------------------------------
;Common code: final logical sector move
;  BBANK is set up (if BANKED)	
;  b=0
;  de=destination
;  hl=source
;--------------------------------------
RWS7:	mvi	c,SECSIZ	;bc=byte count
	dw	LDIR		;SECBUF --> (DMAADR)
				;or SECBUF --> track buffer

 if BANKED
	mvi	a,OSBANK	;switch track buffer back out
	out	BBANK
 endif ;BANKED

	xra	a		;indicate no errors
	ret
;============================================================
;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 FFh?

	mvi	a,CCWRSEC+F2SCS1	;write command, side1
	db	JRZ,RWTRAK-$-1		;y:go flush buffer

	xra	a			;n: nothing to flush
	mov	m,a			;buffer is flushed
	pop	h
	ret

;***Subroutine***********************************************	
;Read entire track into buffer
;On Entry:
;  SEKSID = SIDE0 or SIDE1 for the side 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+F2SCS1	;read command

;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 1
;  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		;use local stack
	dad	sp
	lxi	sp,STACK
	push	h		;save incoming sp

	push	d		;save regs on local stack
	push	b

	mov	b,a		;remember read or write cmd
;------------------------
;Seek the requested track
;------------------------
	call	SEEKID		;Select disk BUFDSK
				;seek & ID track, set side
				;e=BPS(0:3)
				;interrupts enabled
				;BCTRL=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 (side
;    verification) bit set
;  e=BPS(0:3)
;---------------------------------------------------
	in	BSTAT		;Get current side bit
	ani	BSIDE0		;get side bit (0=side 1)
	xra	d		;invert, combine with command
				;(clears carry)
	mov	d,a		;so the side gets verified
;----------------------------------------------
;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
;-------------------------------------------------------
	lda	IDSECT		;sector found by ID read
	mov	c,a
;------------------------------------------------------------
;Calculate hl = sector buffer address = TRKBUF+c*SECSIZ*2^e
;This is a bit tricky... We calculate the buffer address for
;the NEXT sector without worrying about wrapping the the last
;sector: this will get fixed at the head of the loop. Note
;that physical sector numbers start with 1.
;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
;with sector c (the next sector expected on the disk)
;
;Note: for back-to-back sector read/writes, the next DCMMD
;must not be later than about 800 uS max from end of the
;previous sector.
;  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 the next 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 sector 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 the buffer 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 (1024 BPS)
SKPSC1:
	dad	d		;de=BPS/4
	dad	d		;..so add 4 of them to
	dad	d		;..compute sector 2 address
	dad	d

	pop	d
	db	JR,DOSEC-$-1

;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
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
;  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		;read or write sector
				;returns with a=0 on success
				;...& WD1793 status on error
	db	JRNZ,RWSERR-$-1	;(7)retry on disk error

;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 ;(13) More secs?

;If this was a write, then delay for ~580 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 20 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, 78 for 6 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, set/clear Z for success/fail
;  a=0 and Z set for success, a=1 and Z clear for fail
;--------------------------------------------------------
RWEXIT:	pop	b		;(10)restore 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 ;'Track write error on x'
	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
;  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 INTEN
	di			;mask interrupts
 endif ;INTEN

 if BANKED
	mvi	a,TBBANK	;select track buffer bank
	out	BBANK
 endif ;BANKED

	lda	BDCTRL		;(13)select, density, size,
				;..autowait
	out	DCTRL		;(11)output DCTRL value

	mvi	c,DDATA		;(7)Port address for INI & OUTI
	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
;Fast as possible: 26 cycles (13 uS) out-to-out.
;Too slow for an 8"DD 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
;  hl=DMA 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:	dw	OUTI	;(16)
	inr	b	;(4) undo 'dcr b' in OUTI

	dw	OUTI	;(16)
	inr	b	;(4) undo 'dcr b' in OUTI

	dw	OUTI	;(16)
	inr	b	;(4) undo 'dcr b' in OUTI

	dw	OUTI	;(16)
	jnz	WRPSEC	;(10) 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
;Fast as possible: 26 cycles (13 uS) in-to-in.
;Just fast enough for an 8"DD disk with a 2 MHz CPU.
;On Entry:
;  read or read address command has been issued to WD1793
;  b=byte count/4 (1024 BPS encoded as 0)
;  b=RABC (=2) for read address command
;  c=DDATA
;  hl=DMA 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:	dw	INI	;(16)
	inr	b	;(4) undo 'dcr b' in INI

	dw	INI	;(16)
	inr	b	;(4) undo 'dcr b' in INI

RDPS2:	dw	INI	;(16)
	inr	b	;(4) undo 'dcr b' in INI

	dw	INI	;(16)
	jnz	RDPS1	;(10) 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 35/4*256=2.1 mS
;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
; Interrupts enabled
; 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

 if INTEN
	ei			;re-enable interrupts
 endif ;INTEN

	ana	c		;error mask
	rp

EORW2:	jmp	DNRERR		;drive not ready?

;***Subroutine********************************************
;Read current track ID using WD1793 'read address'
;command, and compute parameters for this track.
;On Entry:
;  BUFDSK = disk to ID
;  Head is on desired track
;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
;  BCTRL set up with side specified in BUFSID
;  DTRCK matches the found track number
;  IDSAVE bytes are current for the current track
;On Error Exit:
;  a=FF
;  Z cleared
;Stack depth: 8
;*********************************************************
IDRD0:	mvi	a,SIDE0
	out	BCTRL		;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
;  e=BPS(0:3)
;  hl=PRMTBL(BUFDSK)
;----------------------------------------
	push	h

 if DRIV35
	mvi	a,(T330-SKWTBL)/2-1	;Offset 3.5" SPT tab
	dw	BIT4M			;PRM35: 3.5" drive?
	mov	l,e			;temp BPS(0:3)
	db	JRNZ,IDRD3-$-1		;1 possible TV table
 endif ;DRIV35

 if not DRIV35
	mov	l,e			;temp BPS(0:3)
 endif ;not DRIV35

;Compute translate vector table address offset for
;single- or double-density, along with BPS
;  l=BPS(0:3)
	in	BSTAT
	dw	BIT5A		;test BDDEN: double density?
	db	JRZ,IDRD2-$-1
	dcr	l		;y:adjust offset
IDRD2:

;Now offset this based on which 2422 interface
;connector (which is really which data rate)
;  l=offset for DD or SD with BPS
	ani	BMINI		;BSTAT bit 1 set for low
				;..data rate 

				;0 or 4, since there are
	rlc			;..4 SKWTBL entries from the
				;..high- to the low- data
				;rate table

IDRD3:	add	l		;combine w/ Dens, BPS offsets
	add	a		;2-bytes per entry

;Point hl to the correct skew table entry, and get the SPT
;  a=offset into skew table

	lxi	h,SKWTBL
	add	l		;Offset into table
	mov	l,a		;won't ever carry
	mov	a,m		;get actual skew

;Remember this drive's SPT and return
	sta	BUFSPT	
	
	pop	h		;hl=PRMTBL(BUFDSK)
;---------------------------
;Normal exit with a=0, Z set
;---------------------------
	xra	a
	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********************************************
;Read current track ID using WD1793 'read address'
;command.
;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
;============================================================

;****Subroutine**********************************************
;Determine the drive and disk type, and set up PRMTBL
;
;Restore to track 0, and then test BSTAT, to determine if the
;drive is on the 34-pin interface (and therefore has the
;lower data rate), or on the 50-pin interface (and therefore
;has the higher data rate).
;
;A lower data rate drive is either a 40-track 5.25" drive or
;an 80-track 5,25" drive, unless QDISK=FALSE. If QDISK=TRUE
;then figure out which, by seeking track 40, and then reading
;that track's ID. if it reads as 40 then it's an 80-track
;drive. Otherwise it's a 40-track drive. Set up PRMTBL
;accordingly.
;
;If it's a higher data rate drive and DRIV35=TRUE, then test
;its index  frequency to determine if it is a 3.5" disk
;(which rotates at 300 RPM). If so, set up for a 2-sided
;double density 3.5" disk.

;Otherwise, it is either a 77-track 8" disk or (if HD=TRUE)
;an 80-track HD 5.25" disk (which both rotate at 360 RPM), so
;seek past track 76 to determine which, and set up PRMTBL
;apprpriately.
;
;If it's an 8" disk then start out assuming it's an SA801,
;which has a slower step rate rate than the double-sided
;SA851.
;
;IF it is an 8" drive, and PERSCI=TRUE, then determine if
;it's a voicecoil drive, adn set the appropriate bit in
;PRMTBL if so.
;
;Note that some drives will report 'not ready' if you
;select them with side 1 when a 1-sided disk is in, so we
;always use side 0 here.
;On Entry:
;  BUFDSK specifies the drive
;  PRMTBL(BUFDSK) DCTRL valueis initialized for low data
;     rate disk (DCMAXI=0).
;On Exit:
;  Z set if no errors
;  hl=PRMTBL(BUFDSK)
;  Z cleared if track 0 not found (error) (and hl is invalid)
;  PRMTBL Bytes 0 and 1 are set up for the detected drive
;  BCTRL is written for side 0
;Trashes bc,de
;Stack depth: 12
;************************************************************
DRIVID:
;--------------------------------------------------------
;Step in one track on the disk specified by BUFDSK first,
;since some disk drives can over-step past track 0
;--------------------------------------------------------
	mvi	c,STPICM+UPDTTR+HDLOAD	;keep the head loaded
	call	T1DCMD	;trashes de, select side 0
;-------------------------------------------------
;Actually restore the disk specified by BUFDSK now
;-------------------------------------------------
	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			;Y: error exit
;---------------------------------------------------
;Test for a minidisk (really, a disk on the 34-pin
;interface) while on track 0.
;  hl=PRMTBL(BUFDSK)
;  head is on track 0
;---------------------------------------------------
	in	BSTAT		;read BSTAT while on track 0
	ani	BTRK0		;0 if 5.25" disk on track 0
	db	JRNZ,DID1-$-1
;------------------------------------------------------
;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.
;  head is on track 0
;------------------------------------------------------
 if QDRIVE
	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)

	mvi	m,STEP5+PRMDSK	;40-track drive

	db	JR,DID3-$-1	;success exit
 endif ;QDRIVE

;-------------------------------------------------
;This drive is on the 50-pin interface. Set DCMAXI
;in PRMTBL byte 1 for the higher data rate.
;-------------------------------------------------
DID1:	inx	h		;point to PRMTBL byte 1
	dw	SET4M		;this is DCMAXI
	dcx	h		;point to PRMTBL byte 0

 if DRIV35
;---------------------------------------------------------
;This is either a 360 RPM drive (8" or HD 5.25") or its a
;300 RPM drive (3.5") drive. Measure the index pulse rate
;to determine which. (Accuracy is not important.)
;  hl=PRMTBL(BUFDSK)
;---------------------------------------------------------
	call	TINDEX
	db	JRZ,INDTO-$-1	;Z set means error
				;result in d
;Nominal de values:
;             2 MHz          4 MHz            6 MHz
; 360 RPM  6289 (1891h)   12,579 (3123h)   18,868 (49B4)
; 300 RPM  7547 (1D7Bh)   15,094 (3AF6h)   22,642 (5872)

	lda	CPUMHZ		;calculated in BOOT
	cmp	d		;compare to timer result

;carry is set if this is a 3.5" (300 RPM) disk.

	mvi	m,STEP35+PRM35	;3.5" step rate
	db	JRC,DID3-$-1
 endif ;DRIV35

 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)
;-------------------------------------------
	call	TESTSC		;magic voicecoil test
	mvi	m,PRMVC+STEPVC	;voicecoil setup
	db	JRNZ,DID3-$-1
 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 exit
;-------------------
DID3:	xra	a		;success: set Z
	ret

 if DRIV35
;-------------------------
;Timeout waiting for index
;-------------------------
INDTO:	dcr	a		;clear Z, a<>0 for error
	ret			;Timeout

 endif ;DRIV35

 if PERSCI
;***Subroutine**********************************************
;Test to see of SEEK COMPLETE signal (from the drive) is
;true. SEEK COMPLETE will clear the HLT flip-flop, which
;would otherwise keep the SHEADLD bit (in the WD1793 status
;register) low for about 35 mS after the rising edge of HLD.
;On Exit:
;  a=final status from controller
;  Z set if SEEK COMPLETE is inactive
;  Z cleared if SEEK COMPLETE is active
;  head is loaded
;(about 335 cycles)
;Trashes c
;Stack depth: 4
;***********************************************************
TESTSC:	in	DTRCK		;(11)set up for null seek
	out	DDATA		;(11)

	push	d		;(11)
	mvi	a,SEEKCM	;(7)head not loaded
	call	DT1CMD		;(about 130)
				;unload, wait completion

	mvi	a,SEEKCM+HDLOAD	;(7)create rising edge on HLD
	call	DT1CMD		;(about 130)
				;load, wait completion
	DW	BIT5A		;(8)Test WD1793 SHEADLD bit
	pop	d		;(10)
	ret			;(10)
 endif ;PERSCI

 if QDRIVE or HDRIVE
;***Subroutine*************************************
;Check the end track number.
;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).
;On Entry:
;  a=a few tracks past the end of the smaller drive
;   (Must be an odd number)
;  BUFDSK=drive to test
;  PRMTBL(BUFDSK)+1 is set up for double density
;On Exit:
;  a=0 and Z set if the track exists
;  A<>0 and Z clear if not
;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?
	ret
 endif ;QDRIVE or HDRIVE

;***Subroutine*******************************************
;Seek track with retries, 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
;  BCTRL=side selected by BUFSID
;  BDCTRL = DCTRL value for this track
;  Density set for this track in DCTRL byte of PRMTBL
;  interrupts are enabled
;On failure exit:
;  Z cleared
;Rude abort to WBOOT if the drive is not ready or timeout
;Stack depth: 12 (10 if PERSCI=FALSE)
;Trashes c
;********************************************************
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		;Select current side
	out	BCTRL

	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
				;enables interrupts on ret


	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
;Stack depth: 6 (4 if PERSCI=FALSE)
;Trashes c,de
;************************************************************
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 the seek-
;complete signal 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)
;  Disk is already selected in DCTRL
;On Exit:
;  Side 0 is selected in BCTRL
;  Fast-Seek is selected in BCTRL if the drive is voicecoil
;  a=disk status
;  hl=PRMTBL(BUFDSK)
;Rude abort to WBOOT if the drive is not ready or timeout
;Stack depth: 6 (4 if PERSCI=FALSE)
;Trashes c,de
;************************************************************
T1DCMD:	call	PTLU		;DCTRL value & PRMTBL pointer
				;a=DCTRL value from PRMTBL
	out	DCTRL		;(11)Select drive, motor on

;Select side 0, and (if a voicecoil drive has been
;detected) set up for voicecoil seek, both in BCTRL
 if PERSCI
	mov	a,m		;PRMTBL(BUFDSK)
	rar			;shove PRMVC into bit 4
	ani	BCFSEKN		;(7)..set means fast-seek
	xri	SIDE0		;(7)BCTRL bit 4 clear
				;..if fast seek
 endif ;PERSCI

 if not PERSCI
	mvi	a,SIDE0
 endif ; not PERSCI

	out	BCTRL		;(11)side and seek type

	call	WATRDY		;wait for drive to be ready
				;trashes de

;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 need to check
;(indirectly) the state of the Seek Complete signal, by
;triggering the HLT 1-shot and then looking to see if it
;immediately clears, allowing the SHEADLD bit in the WD1793
;status register to go high.
	dw	BIT5M		;(8)test PRMTBL PRMVC bit
	rz			;no VC: done, status in a

;Wait for 966 uS, so that the microcontroller in a PerSci
;299B has time to raise its -Seek Complete signal
;(Just assume a 6 MHz CPU here, for worst case)
	mvi	e,42*6		;delay for 966 uS @ 6 MHz

VDELAY:	mvi	d,6*6		;(7)Seek timeout (delay)
	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
T1DC1:	call	TESTSC		;(about 335 cycles)
				;NZ means fast seek complete
	rnz			;(10)ret with status in a

	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
;  BCTRL is set up with side and fast seek (as needed)
;On Exit:
;  WD1793 command has been executed and completed
;  a=disk status
;Rude abort to WBOOT if the drive is not ready or timeout
;Stack depth: 0
;Trashes c,de
;113 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)

	lxi	d,902		;(10)(2.5 second timeout)

;Inner loop: 2.773 mS (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 (with a 4MHz CPU)
;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
;=================
 if DRIV35
;***Subroutine******************************************
;Time the index signal
;NOTE: This subroutine is in the one-time boot code (at
;the end) if DRIV35=FALSE
;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
;Stack depth: 0
;trashes bc,e
;*******************************************************
TINDEX:	lxi	b,3*256+0	;b=loop counter
				;c=inverter

RTLUP1:	mov	d,b		;timer

;Time the index. 53 cycles per pass:
;First find a rising edge, then time
;to the next falling edge
	
RTLUP2:	inx	d		;(6)Bump timer
	mov	a,d		;(4)
	ora	e		;(4)Timeout?
	rz			;(5)y: ret with Z set

	in	BSTAT		;(11)
	xra	c		;(4)maybe invert

	ani	BINDEX		;(7)active-low signal
	db	JRNZ,(RTLUP2-$-1) XOR 0FF00h	;(12)

	mov	a,c		;look for the other BINDEX
	cma			;..state next time
	mov	c,a	

	db	DJNZ,(RTLUP1-$-1) XOR 0FF00h

	ora	a		;clear Z (A=0FFh)
	ret
 endif ;DRIV35
;***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		;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 8250 UART on the CCS 2810
;================================================
TTPNCH:
TTYOUT:	call	TTYOST		;wait for TX ready
	db	JRZ,(TTYOUT-$-1)XOR 0FF00h

	mov	a,c		;c has character to send
	out	SDATA
	ret

TTYOST:	in	SLSTAT		;8250 line status
	ani	TXMTY		;Isolate TX buffer empty
	rz

	in	SMSTAT		;check hardware handshake
	ani	DTRSTA

FINOST:	rz			;not ready: ret 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
;
;On Exit:
;  Z set
;  a=c
;***********************************************************
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 CCS 2719 2P+2S board
;===========================================

;---------------------------
;Parallel port 1 can be LPT:
;---------------------------
 if LP19P1			;LPT: output device

LPTOUT:	mov	a,c		;get data for output
	out	PA1BD		;and send it

LPTW:	in	PA1AD		;check printer status
	rar			;POBUSY is bit 0
	db	JRC,(LPTW-$-1)XOR 0FF00h

	mvi	a,PIASTB	;strobe the data out
	out	PA1BC
	mvi	a,PIAMOD
	out	PA1BC		;end strobe pulse

	ret

LPTOST:	in	PA1AD		;check printer status
	cma			;so that 0 means busy
	ani	POBUSY		;just look at busy bit
	db	JR,(FINOST-$-1)XOR 0FF00h
 endif ;LP19P1
;---------------------------
;Parallel port 2 can be UL1:
;---------------------------
 if L119P2			;UL1: output device

UL1OUT:	mov	a,c		;get data for output
	out	PA2BD		;and send it

UL1W:	in	PA2AD		;check printer status
	rar			;POBUSY is bit 0
	db	JRC,(UL1W-$-1)XOR 0FF00h

	mvi	a,PIASTB	;strobe the data out
	out	PA2BC
	mvi	a,PIAMOD
	out	PA2BC		;end strobe pulse

	ret

UL1OST:	in	PA2AD		;check printer status
	cma			;so that 0 means busy
	ani	POBUSY		;just look at busy bit
	db	JR,(FINOST-$-1)XOR 0FF00h
 endif ;L119P2
;-----------------------------------------
;SIO channel A out can be UC1: and/or UP1:
;-----------------------------------------
 if C119SA			;UC1: output device
UC1OUT:
 endif ;C119SA

 if P119SA			;UP1: output device
UP1OUT:
 endif ;P119SA

 if P119SA or C119SA
S1AOUT:	mvi	a,00010000b	;force status update
	out	SIOAC
	in	SIOAS		;read status
	cma			;so 0 means good
	ani	SIODCD+SIOTBE
	db	JRNZ,(S1AOUT-$-1)XOR 0FF00h

	mov	a,c		;send data
	out	SIOAD
	ret
 endif ;P119SA or C119SA
;---------------------------------------------
;SIO channel B out can be UC1: or UL1: or UP2:
;---------------------------------------------
 if C119SB			;UC1: output device
UC1OUT:
 endif ;C119SB

 if L119SB			;UL1: output device
UL1OUT:
 endif ;L119SB

 if P219SB			;UP2: output
UP2OUT:
 endif ;P219SB

 if L119SB or C119SB or P219SB
S1BOUT:	call	SIOBOS
	db	JRZ,(S1BOUT-$-1)XOR 0FF00h
	mov	a,c		;Send data
	out	SIOAD
	ret
 endif ;L119SB or C119SB or P219SB

 if L119SB			;UL1: output status
UL1OST:
 endif ;L119SB

 if L119SB or C119SB or P219SB
SIOBOS:	mvi	a,00010000b	;force status update
	out	SIOBC
	in	SIOBS		;status
	ani	SIODCD		;handshake off?
	rz
	in	SIOBS		;status again
	ani	SIOTBE		;Tx buffer empty?

	db	JR,FINST-$-1
 endif ;L119SB or C119SB or P219SB

;===================================================
;Output drivers for the CCS 2720 parallel port board
;===================================================

;--------------------------------------
;Port A with part of port D can be LPT:
;--------------------------------------
 if LP20P1
LPTOUT:	call	LPTOST			;printer ready?
	db	JRZ,(LPTOUT-$-1)XOR 0FF00h

	mov	a,c			;get character
	out	PPORTA			;AND print IT
	mvi	a,PPASLI and PPASTR	;strobe data
	out	PPORTD
	mvi	a,PPASLI
	out	PPORTD			;End strobe
	ret

LPTOST:	in	PPORTA			;parallel ports status
	cma				;so that 0 means busy
	ani	PPABSY			;port'S ready bit
	db	JR,(FINST-$-1)XOR 0FF00h
 endif ;LP20P1

;=================================
;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 8250 UART on the CCS 2810
;===============================================
TTYRDR: 
 if MODRDR
	call	TTYST	;test for character
	rz		;no char, return with Z set
 endif ;MODRDR

TTYIN:	call	TTYST	;wait for character
	db	JRZ,(TTYIN-$-1)XOR 0FF00h

	in	SDATA	;read character
	ret

TTYST:	in	SLSTAT	;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 CCS 2719 2P+2S board
;==========================================

;-----------------------------
;SIO channel A in can be UC1:
;OR standard/NON-standard UR1:
;-----------------------------
 if C119SA			;UC1: input device
UC1IN:
SIOAIN:	call	SIOAST		;check character ready
	db	JRZ,(SIOAIN-$-1)XOR 0FF00h

	in	SIOAD		;get data
	ret
UC1ST:
SIOAST:	in	SIOAS
	ani	SIORCA		;Rx character available
	db	JR,(FINST-$-1)XOR 0FF00h
 endif ;C119SA

 if R119SA			;UR1: input device
UR1ST:	in	SIOAS
	ani	SIORCA		;Rx character available
	db	JR,(FINST-$-1)XOR 0FF00h

UR1IN:	call	UR1ST
 endif ;R119SA

 if R119SA and MODRDR
	rz
 endif ;R119SA and MODRDR

 if R119SA and not MODRDR
 	db	JRZ,(UR1IN-$-1)XOR 0FF00h
 endif ;R119SA and not MODRDR

 if R119SA
	in	SIOAD		;get data
	ret
 endif ;R119SA
;------------------------------------
;SIO channel B in can be UC1: or UR2:
;------------------------------------
 if C119SB			;UC1: input device
SIOBIN:	call	SIOBST		;check busy
	db	JRZ,(SIOBIN-$-1)XOR 0FF00h

	in	SIOBD		;get data
	ret

SIOBST:	in	SIOBS
	ani	SIORCA		;Rx character available
	db	JR,(FINST-$-1)XOR 0FF00h
 endif ;C119SB

 if R219SB
UR2ST:	in	SIOBS
	ani	SIORCA		;Rx character available
	db	JR,(FINST-$-1)XOR 0FF00h

UR2IN:	call	UR2ST
 endif ;R219SB

 if R219SB and MODRDR
	rz
 endif ;R219SB and MODRDR

 if R219SB and not MODRDR
 	db	JRZ,(UR2IN-$-1)XOR 0FF00h
 endif ;R219SB and not MODRDR

 if R219SB
	in	SIOBD		;get data
	ret
 endif ;R219SB

;================================
;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 buffer 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.)
;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

;Lift the heads and disable the motors. Do this 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 may 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:	xra	a
	out	DCTRL	;motors off, lift heads

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:
;  a=0
;  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
;On Exit:
;  a=0
;Trashes 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)
;Bytes   Name             Meaning
; 0-1    XLT     Addr of logical-to-physical translate vector
;   0              Skew table address offset
;   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 BUFSID)
BUFDSK:	db	0FFh	;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, measure and record the CPU's
;speed, test the track buffer RAM, and then re-use some of
;the warm-boot code to finish initialization.
;*********************************************************
BOOT:	di			;no interrupts during boot
	in	DFLAG		;CCS autoboot in progress?
	ani	DFAUTOB		;boot jumper bit
	db	JRNZ,BOOTA-$-1	;jump if not autoboot
;---------------------------------------------------
;Set up serial port since ROM monitor didn't already
;---------------------------------------------------
	mvi	a,0Fh		;modem control reg
	out	SMDMCT
	mvi	a,83h		;baud rate divisor access
	out	SLCTRL
	mvi	a,SBAUD/256	;Divisor high byte
	out	SINTEN
	mvi	a,SBAUD MOD 256
	out	SDATA		;Divisor low byte
	mvi	a,3		;8250 line control
	out	SLCTRL
	xra	a		;handshake lines active
	out	SINTEN
	out	SLSTAT
BOOTA:
;------------------------------------
;Initialize all ports of the CCS 2719
;------------------------------------
 if CCS2719

;Initialize both serial ports

	lxi	h,SIOCMD	;setup table
	mvi	b,SIOLEN

SIOI1:	mov	a,m
	out	SIOAC
	out	SIOBC
	inx	h
	db	DJNZ,(SIOI1-$-1)xor 0FF00h

	mvi	a,CTCMD0
	out	CTC0

	mvi	a,CTCMD1
	out	CTC1

	mvi	a,CTCDV0
	out	CTC0

	mvi	a,CTCDV1
	out	CTC1

;Initialize both parallel ports

	xra	a
	out	PA1AC		;access DDR registers
	out	PA2AC
	out	PA1BC
	out	PA2BC

	out	PA1ADD		;A ports are input
	out	PA2ADD

	mvi	a,DATOUT	;B ports are output
	out	PA1BDD
	out	PA2BDD

	mvi	a,PIAMOD	;mode control
	out	PA1AC		;access a output reg
	out	PA2AC
	out	PA1BC		;access B output reg,
	out	PA2BC		;...direct control of CB2

	xra	a
	out	PA1BD		;..to prime handshakes
	out	PA2BD		;and flush data
	in	PA1AD		;clear status
	in	PA2AD	
 endif ;CCS2719
;-----------------------------------------------
;Begin init pulse to initialize parallel port
;printer. Keep init signal low at Least 75 uSec.
;-----------------------------------------------
 if LP20P1
	mvi	a,PPASLI
	out	PPORTD		;select printer
	mvi	a,PPASLI and PPAINI
	out	PPORTD
 endif ;LP20P1
;-----------------------------------
;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:
; BIOS version number
; Port configuration 
;---------------------------------
	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

;-----------------------------------------------------
;Initialize lots of disk parameters by selecting drive
;A (which is the drive we loaded from). BUFDSK was
;initialized to FF
;c=0 here (for drive A)
;-----------------------------------------------------
	call	SELDSK		;Wipes out 1st 6 bytes
				;..of boot (this code).
				;Z clear means fail
	jnz	BFAIL		;error during drive ID read
;---------------------------------------
;End init pulse to parallel port printer
;---------------------------------------
 if LP20P1
	mvi	a,PPASLI	;End pulse, stay selected
	out	PPORTD
 endif ;LP20P1

;-----------------------------------------------
;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.
;------------------------------------------------
	call	SEEKID		;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            6 MHz
; 360 RPM  6289 (1891h)   12,579 (3123h)   18,868 (49B4)
; 300 RPM  7547 (1D7Bh)   15,094 (3AF6h)   22,642 (5872)
;anything above 4000h will be considered 6 MHz
;Anything below 2800h will be considered 2 MHz

;Compute CPUMHZ for the TINDEX result:
;  2 MHz CPU: 1Ah (26)
;  4 MHz CPU: 34h (52)
;  6 MHz CPU: 4Eh (78)

	cpi	28h
	db	JRNC,MHZOK-$-1

	lxi	h,MH2MSG		;2MHz: warn the user
	call	PMSG
	mvi	a,1Ah
	db	JR,MHZDON-$-1

MHZOK:	cpi	40h			;4MHz?
	mvi	a,34h
	db	JRC,MHZDON-$-1

	mvi	a,4Eh			;6 MHz

MHZDON:	sta	CPUMHZ
		
;------------------------------
;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 memory test error

BHALT:	call	PMSG

	hlt

;Transfer buffer error messages (PRINTF will append ' error')
BSEMSG:	db	'Bank selec','t'+80h
TBEMSG:	db	'Track buffer RA','M'+80h
 endif ;BANKED

;***Subroutine******************
;Print a character several times
;On Entry:
; (hl+1) = repeat count
; (hl+2) = chr to repeat
;On exit:
; Z set
; b=0
; a=c=repeated character
; hl has been bumped twice
;********************************
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

 if not DRIV35
;***Subroutine*******************************************
;Time the index signal
;NOTE: This subroutine is in the main code if DRIV35=TRUE
;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 statusregister)
;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+0	;b=loop counter
				;c=inverter

RTLUP1:	mov	d,b		;timer

;Time the index. 53 cycles per pass:
;First find a rising edge, then time
;to the next falling edge
	
RTLUP2:	inx	d		;(6)Bump timer
	mov	a,d		;(4)
	ora	e		;(4)Timeout?
	rz			;(5)y: ret with Z set

	in	BSTAT		;(11)
	xra	c		;(4)maybe invert

	ani	BINDEX		;(7)active-low signal
	db	JRNZ,(RTLUP2-$-1) XOR 0FF00h	;(12)

	mov	a,c		;look for the other BINDEX
	cma			;..state next time
	mov	c,a	

	db	DJNZ,(RTLUP1-$-1) XOR 0FF00h

	ora	a		;clear Z (A=0FFh)
	ret
 endif ;not DRIV35

MH2MSG:
 db  '2 MHz Z80 detected. Writes will fail for '

 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!'
 db 0

;-----------------------
;CCS 2719 SIO init table
;-----------------------
 if CCS2719
SIOCMD:	db	00011000b	;channel reset
	db	1		;write register 1
	db	00000000b	;disable interrupts, waits
	db	3		;write register 3
	db	11000001b	;Rx 8 bit, no auto EN, Rx EN
	db	4		;write register 4
	db	01000100b	;X16 clock, 1 stop, no parity
	db	5		;write register 5
	db	11101010b	;DTR, 8X 8 bit, Tx EN, RTS

SIOLEN	equ	$-SIOCMD
 endif ;CCS2719
;----------------------------------------------
;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, 02,
;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	'MBIOS '
 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.
;---------------------------------------------
 db CR

 db 02,50,'-'	;print repeatedly
 db CR

 db 02,9,' '	;print repeatedly
 db 'California Computer Systems 2200'
 db CR
 db CR
 db MSIZE/10+'0',MSIZE MOD 10 + '0','k '
 db 'CP/M 2.2, copyright (c) 1979 DIGITAL RESEARCH',CR

 if BANKED
 db 02,5,' '	;print repeatedly
 db 'Banked/Track buffered MBIOS version '
 endif ;BANKED
 if not BANKED
 db 02,8,' '	;print repeatedly
 db 'Track buffered MBIOS version '
 endif ;not BANKED

 db ((BIOSVER AND 0F00h)/256)+'0','.'
 db ((BIOSVER AND 0F0h)/16)+'0',(BIOSVER AND 0Fh) +'0'
 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 MODRDR
 db CR,'Z-modified RDR'
 endif ;MODRDR

 if CCS2719
 db CR,'CCS 2719 ports:'
 endif ;CCS2719
 if C119SA
 db CR,'  UC1: Serial A'
 endif ;C119SA
 if C119SB
 db CR,'  UC1: Serial B'
 endif ;C119SB
 if LP19P1
 db CR,'  LPT: Parallel 1'
 endif ;LP19P1
 if L119SB
 db CR,'  UL1: Serial B'
 endif ;L119SB
 if L119P2
 db CR,'  UL1: Parallel 2'
 endif ;L119P2
 if R119SA
 db CR,'  UR1: Serial A'
 endif ;R119SA
 if R219SB
 db CR,'  UR2: Serial B'
 endif ;R219SB
 if P119SA
 db CR,'  UP1: Serial A'
 endif ;P119SA
 if P219SB
 db CR,'  UP2: Serial B'
 endif ;P219SB
 if CCS2720
 db CR,'LPT: CCS 2720 Parallel port A'
 endif ;CCS2720

db CR,02,50,'-'	;print repeatedly
 db 0

;**********************
;Check for BIOS too big
;**********************
ENDBIOS	equ	$

 if ((BOOT and not (ENDBIOS-1)) or (RAMSIZ*256 - (ENDBIOS+3)/4))/8000h
 ERROR: BIOS file is too large for disk
 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

DMAADR:	ds	2	;2-byte memory adr for data transfers
CPUMHZ:	ds	1	;26,52,78 for 2,4,6 MHz
;--------------------------------------------------
;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, or when FASTER=FALSE.
;----------------------------------------------------
SECBUF:	ds	SECSIZ	;CP/M sector transfer buffer
 endif ;BANKED

 if not BANKED
;---------------------------
;Track buffer in main memory
;---------------------------
TRKBUF	ds	TBSIZE

 endif ;not BANKED
;-----------------------------------------------------
;Local stack
;Used during SECTRAN, READ, and WRITE BIOS calls.
;(Code currently need 18 bytes.) 
;-----------------------------------------------------
	ds	22	;local stack space
STACK:			;(the stack grows toward 0)

;***************************************************
;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
