IDE interface for Altair 8800c

Discuss construction, troubleshooting, and operation of the Altair 8800c computer

Re: IDE interface for Altair 8800c

Postby Wayne Parham » December 1st, 2022, 4:09 pm

Here's the code as it stands now. It is able to retrieve drive ID for drives or flash cards, both for drive 0 and drive 1.

It still needs some work, so we're not "cookin' with gas" yet but we definitely have the pilot light lit.

Code: Select all
;------------------------------------------------------------------------------
; Utility Program for IDE interface board
;   v2.9b   12/01/2022
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
;Build equates:
;------------------------------------------------------------------------------

FALSE      EQU   0
TRUE      EQU   NOT FALSE

CPM      EQU   TRUE   ;TRUE if output via CPM, FALSE if hardware direct
DEBUG      EQU   TRUE   ;TRUE for error messages
VERBOSE      EQU   FALSE   ;TRUE for extended error messages
CPM$TRANSLATE   EQU   TRUE   ;Translate Trk, Sec, Head to CPM TRACK# & SEC#

;------------------------------------------------------------------------------
;Drive number equates:
;------------------------------------------------------------------------------

IDE0      EQU   0   ;Physical disk 0 drive number assignment
IDE1      EQU   1   ;Physical disk 1 drive number assignment

;------------------------------------------------------------------------------
;Console equates:
;------------------------------------------------------------------------------

CONI      EQU   10H   ;Console input port
CONO      EQU   11H   ;Console output port

;------------------------------------------------------------------------------
;Display control equates:
;------------------------------------------------------------------------------

SCROLL      EQU   01H   ;Set scroll direction UP
LF      EQU   0AH
CR      EQU   0DH
BS      EQU   08H   ;Back space (required for sector display)
PERIOD      EQU   2EH
BELL      EQU   07H
SPACE      EQU   20H
QUIT      EQU   11H   ;Turns off any screen enhancements
NO$ENHANCEMENT   EQU   17H   ;Turns off whatever is on
FAST      EQU   10H   ;High speed scrool
TAB      EQU   09H   ;TAB ACROSS (8 SPACES FOR SD-BOARD)
ESC      EQU   1BH
CLEAR      EQU   1CH   ;Clear line (Use 80 spaces if not available)

;------------------------------------------------------------------------------
;IDE Interface equates:
;------------------------------------------------------------------------------
;Ports for 8255 chip. Change these to specify where your 8255 is addressed,
;The first three control which 8255 ports have the control signals,
;upper and lower data bytes.  The last one (IDEportCtrl), is for mode setting
;for the 8255 to configure its actual I/O ports (A,B & C). 
;
;Note most drives these days don't use the old Head,Track, Sector terminology.
;Instead we use "Logical Block Addressing" or LBA. This is what we use below.
;LBA treats the drive as one continous set of sectors, 0,1,2,3,... 3124,...etc.
;However as seen below we need to convert this LBA to heads,tracks and sectors
;to be compatible with CPM & MSDOS.
;
;NOTE: If you have only one drive/CF card, be sure it is in drive #0.
;The IDE hardware gets confused if there is only a drive in slot #1.
;------------------------------------------------------------------------------

IDEportA   EQU   030H   ;Lower 8 bits of IDE interface (8255)
IDEportB   EQU   031H   ;Upper 8 bits of IDE interface
IDEportC   EQU   032H   ;Control lines for IDE interface
IDEportCtrl   EQU   033H   ;8255 configuration port
IDEDrive   EQU   034H   ;Bit 0 - 0 for drive 0 and 1 for drive 1

READcfg8255   EQU   10010010b ;Set 8255 IDEportC to output, IDEportA/B input
WRITEcfg8255   EQU   10000000b ;Set all three 8255 ports to output mode

;------------------------------------------------------------------------------
;IDE control lines for use with IDEportC. 
;------------------------------------------------------------------------------

IDEa0line   EQU   01H   ;direct from 8255 to IDE interface
IDEa1line   EQU   02H   ;direct from 8255 to IDE interface
IDEa2line   EQU   04H   ;direct from 8255 to IDE interface
IDEcs0line   EQU   08H   ;inverter between 8255 and IDE interface
IDEcs1line   EQU   10H   ;inverter between 8255 and IDE interface
IDEwrline   EQU   20H   ;inverter between 8255 and IDE interface
IDErdline   EQU   40H   ;inverter between 8255 and IDE interface
IDErstline   EQU   80H   ;inverter between 8255 and IDE interface

;------------------------------------------------------------------------------
;Symbolic constants for the IDE drive registers
;------------------------------------------------------------------------------

REGdata      EQU   IDEcs0line
REGerr      EQU   IDEcs0line + IDEa0line
REGseccnt   EQU   IDEcs0line + IDEa1line
REGsector   EQU   IDEcs0line + IDEa1line + IDEa0line
REGcylinderLSB   EQU   IDEcs0line + IDEa2line
REGcylinderMSB   EQU   IDEcs0line + IDEa2line + IDEa0line
REGshd      EQU   IDEcs0line + IDEa2line + IDEa1line
REGcommand   EQU   IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGstatus   EQU   IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGcontrol   EQU   IDEcs1line + IDEa2line + IDEa1line
REGastatus   EQU   IDEcs1line + IDEa2line + IDEa1line + IDEa0line

;------------------------------------------------------------------------------
;IDE Command Constants.  These should never change.
;------------------------------------------------------------------------------

COMMANDrecal   EQU   10H
COMMANDread   EQU   20H
COMMANDwrite   EQU   30H
COMMANDinit   EQU   91H
COMMANDid   EQU   0ECH
COMMANDspindown   EQU   0E0H
COMMANDspinup   EQU   0E1H

;------------------------------------------------------------------------------
;IDE Status Register:
;------------------------------------------------------------------------------

;  bit 7: Busy   1=busy, 0=not busy
;  bit 6: Ready 1=ready for command, 0=not ready yet
;  bit 5: DF   1=fault occurred
;  bit 4: DSC   1=seek complete
;  bit 3: DRQ   1=data request ready, 0=not ready to xfer yet
;  bit 2: CORR   1=correctable error occurred
;  bit 1: IDX   vendor specific
;  bit 0: ERR   1=error occured
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
;Disk equates:
;------------------------------------------------------------------------------

SEC$SIZE   EQU   512   ;Bytes per sector
MAXSEC      EQU   3DH   ;Sectors per track
MAXTRK      EQU   0FFH   ;CPM3 allows up to 8MG so 0-256 "tracks"
BUFFER$ORG   EQU   3000H   ;<----- Will place all sector data here

CPM$BOOT$COUNT   EQU   12   ;Allow up to 12 CPM sectors for CPMLDR
CPMLDR$ADDRESS   EQU   BUFFER$ORG

RDCON      EQU   1   ;For CP/M I/O
WRCON      EQU   2
RESET$DISK   EQU   0DH   ;Reset all CPM disks
PRINT      EQU   9
CONST      EQU   11   ;CONSOLE STAT
BDOS      EQU   5

;------------------------------- INITIALIZATION -------------------------------   

   ORG   100H      ;<--- For CPM

begin:
   LXI   SP, STACK
   LXI     D, SIGN$ON   ;Print welcome message
   CALL   PSTRING
  IF VERBOSE
   LXI   D, SEL0MSG   ;Print select drive 0 message
   CALL   PSTRING
  ENDIF
   MVI   A, IDE0
   STA   @CURRENT$DRIVE
   OUT   IDEDrive   ;Select first drive
   
   CALL   CLEAR$ID$BUFFER   ;Clear ID Buffer
  IF VERBOSE
   LXI   D, INITDRIVE   ;Print initialization message
   CALL   PSTRING
  ENDIF
   CALL   IDEinit      ;Initialize the board and first drive
   JZ   INIT$OK      ;Continue on Zero
   
   LXI   D, INIT$0$ERROR   ;Non-zero is error, probably no drive
   CALL   PSTRING
   JMP   ABORT
   
INIT$OK:         ;Get drive 0 identification info         
   CALL   driveid
   JZ   INIT$OK1

   LXI   D, ID$ERROR   ;End program on error
   CALL   PSTRING
   JMP   ABORT

INIT$OK1:         ;Check sector count
   LXI     H, IDbuffer + 12
   MOV   A, M      ;(High Byte)
   ORA   A
   JNZ   INIT$OK2
   INX   H
   MOV   A, M      ;(Low Byte)
   ORA   A
   JNZ   INIT$OK2   ;Looks like we have a valid IDE drive
   
   LXI   D, BAD$DRIVE   ;Zero sectors means something's wrong
   CALL   PSTRING
   JMP   ABORT      ;No drive #0 so abort

INIT$OK2:         ;Print drive 0 info
   LXI   D, DRIVE0$INFO
   CALL   PSTRING
   LXI     D, msgmdl   ;Drive name   
   CALL   PSTRING
   LXI     H, IDbuffer + 54
   MVI   B, 20      ;Character count in words
   CALL   printSwap   ;Print [HL], [B] X 2 characters
   CALL   ZCRLF

   LXI     D, msgsn   ;Serial number
   CALL   PSTRING
   LXI     H, IDbuffer + 20
   MVI   B, 10      ;Character count in words
   CALL   printText
   CALL   ZCRLF

   LXI     D, msgrev   ;Firmware revision string
   CALL   PSTRING
   LXI     H, IDbuffer + 46
   MVI   B, 4      ;Character count in words
   CALL   printSwap
   CALL   ZCRLF

   LXI     D, msgcy   ;Drive specs (cyl/hd/sect)
   CALL   PSTRING
   LXI     H, IDbuffer + 2
   CALL   printparm
   LXI     D, msghd
   CALL   PSTRING
   LXI     H, IDbuffer + 6
   CALL   printparm
   LXI     D, msgsc
   CALL   PSTRING
   LXI     H, IDbuffer + 12
   CALL   printparm
   CALL   ZCRLF

INIT$OK3:         ;Move to second drive
   CALL   ZCRLF
  IF VERBOSE
   LXI   D, SEL1MSG   ;Print select drive 1 message
   CALL   PSTRING
  ENDIF
   MVI   A, IDE1      
   STA   @CURRENT$DRIVE
   OUT   IDEDrive   

   CALL   CLEAR$ID$BUFFER   ;Clear ID Buffer
   IF VERBOSE
   LXI   D, INITDRIVE   ;Print initialization message
   CALL   PSTRING
   ENDIF
   CALL   IDEinit      ;Initialize the second drive
   JZ   INIT$OK4

   LXI   D, INIT$1$ERROR   ;Non-zero is error, so print warning
   CALL   PSTRING
   JMP   MAINLOOP

INIT$OK4:         ;Get drive 1 identification info         
   CALL   driveid
   JZ   INIT$OK5

   LXI   D, ID$ERROR   ;On error, display message
   CALL   PSTRING
   JMP   MAINLOOP   ;Continue to main menu

INIT$OK5:         ;Check sector count
   LXI     H, IDbuffer + 12
   MOV   A, M      ;(High Byte)
   ORA   A
   JNZ   INIT$OK6
   INX   H
   MOV   A, M      ;(Low Byte)
   ORA   A
   JNZ   INIT$OK6   ;Looks like we have a valid IDE drive
BAD$DR1:   
   LXI   D, BAD$DRIVE   ;Zero sectors, so display error
   CALL   PSTRING
   JMP   MAINLOOP   ;Continue to main menu

INIT$OK6:         ;Print drive 1 info
   LXI   D, DRIVE1$INFO
   CALL   PSTRING
   LXI     D, msgmdl   ;Drive name   
   CALL   PSTRING
   LXI     H,IDbuffer + 54
   MVI   B, 20      ;Character count in words
   CALL   printSwap   ;Print [HL], [B] X 2 characters
   CALL   ZCRLF

   LXI     D, msgsn   ;Serial number
   CALL   PSTRING
   LXI     H, IDbuffer + 20
   MVI   B, 10      ;Character count in words
   CALL   printText
   CALL   ZCRLF

   LXI     D, msgrev   ;Firmware revision string
   CALL   PSTRING
   LXI     H, IDbuffer + 46
   MVI   B, 4      ;Character count in words
   CALL   printSwap
   CALL   ZCRLF

   LXI     D, msgcy   ;Drive specs (cyl/hd/sect)
   CALL   PSTRING
   LXI     H, IDbuffer + 2
   CALL   printparm
   LXI     D, msghd
   CALL   PSTRING
   LXI     H, IDbuffer + 6
   CALL   printparm
   LXI     D, msgsc
   CALL   PSTRING
   LXI     H, IDbuffer + 12
   CALL   printparm
   CALL   ZCRLF

INIT$DONE:         ;Cleanup and enter main menu
   CALL   CLEAR$ID$BUFFER
   CALL   IDEinit      ;Re-initialize drive 1
   MVI   A, IDE0
   STA   @CURRENT$DRIVE   ;Select drive 0
   OUT   IDEDrive
   CALL   IDEinit      ;Re-initialize drive 0
   LXI   H, 0
   SHLD   @SEC      ;Default to track 0 and sector 0
   SHLD   @TRK
   LXI   H, buffer   ;Set DMA address to buffer
   SHLD   @DMA
   JMP   MAINLOOP   ;Display Main Menu


;------------------------------------------------------------------------------   


ABORT:            ;Controlled termination
  IF CPM
   MVI   C, RESET$DISK   ;Reset all disks
   JMP   0FF00H      ;Reboot   CPM
  ELSE
   JMP   0F800H      ;Transfer control to Monitor ROM   
  ENDIF


;--------------------------------- MAIN LOOP ----------------------------------   


MAINLOOP:         ;Print main menu
   LDA   @CURRENT$DRIVE
   ORA   A
   JNZ   DRIVE$1$MENU
   LXI   D, DRIVE$0$MSG
   CALL   PSTRING
   JMP   Display0
DRIVE$1$MENU:
   LXI   D, DRIVE$1$MSG
   CALL   PSTRING
Display0:
   LDA   @DisplayFlag   ;Sector data display flag on or off
   ORA   A      ;NZ = on (Initially 0FFH so display on)
   JNZ     Display1
   LXI     D, CMD$STRING1   ;List command options (Turn display option on)
   JP   Display2
Display1:
   LXI     D, CMD$STRING2   ;List command options (Turn display option off)
Display2:
   CALL   PSTRING
   
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current Track,sector,head#
   
   LXI   D, Prompt   ;'>'
   CALL   PSTRING
   
   CALL   GETCMD      ;Simple UC character Input
   CPI   ESC      ;Abort if ESC
   JZ   ABORT
   CALL   upper
   CALL   ZCRLF
   
   SBI   '@'      ;Adjust to 0,1AH
   
   ADD   A      ;X2
   LXI   H, TBL      ;Get menu selection
   ADD   L
   MOV   L, A
   MOV   A, M
   INX   HL
   MOV   H, M
   MOV   L, A      ;Jump to table pointer
   PCHL         ;JMP (HL)
   
   
   
;-------------------------------- MENU OPTIONS --------------------------------   


READ$SEC:         ;Read Sector @ LBA to the RAM buffer
   LXI   H,buffer   ;Point to buffer
   SHLD   @DMA

   CALL   READSECTOR

   JZ   main1b      ;Z means the sector read was OK
   CALL   ZCRLF
   JMP   MAINLOOP

main1b:   LXI     D, msgrd   ;Sector read OK
   CALL   PSTRING

   LDA   @DisplayFlag   ;Do we have display flag on or off
   ORA   A      ;NZ = on
   JZ   MAINLOOP
   LXI   H, buffer   ;Point to buffer. Show sector data flag is on
   SHLD   @DMA
   CALL   HEXDUMP      ;Show sector data
   JMP   MAINLOOP

WRITE$SEC:         ;Write data in RAM buffer to sector @ LBA
   LXI     D, msgsure   ;Are you sure?
   CALL   PSTRING
   CALL   ZCI
   CALL   upper
   CPI   'Y'
   JNZ   main2c
   CALL   ZCRLF

   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA

   CALL   WRITESECTOR

   JZ   main2b      ;Z means the sector write was OK
   CALL   ZCRLF
   JMP   MAINLOOP
main2b:   LXI     D, msgwr   ;Sector written OK
   CALL   PSTRING
main2c: JMP   MAINLOOP

SET$LBA:         ;Set the logical block address
   LXI     D, GET$LBA   
   CALL   PSTRING
   CALL   ghex32lba   ;Get CPM style Track & Sector, put in RAM
   JC   main3b      ;Ret C set if abort/error
   CALL   wrlba      ;Update LBA on drive
main3b:   CALL   ZCRLF
   JMP   MAINLOOP

NEXT$SECT:
   LDA   @SEC
   INR   A      
   CPI   MAXSEC-1
   JNC   RANGE$ERROR
   STA   @SEC
   CALL   wrlba      ;Update LBA on drive
   CALL   ZCRLF
   JMP   MAINLOOP

RANGE$ERROR:
   LXI     D, RANGE$MSG   
   CALL   PSTRING
   JMP   MAINLOOP
   
PREV$SEC:
   LDA   @SEC
   ORA   A
   JZ   RANGE$ERROR
   DCR   A
   STA   @SEC
   CALL   wrlba      ;Update LBA on drive
   CALL   ZCRLF
   JMP   MAINLOOP
   
POWER$UP:         ;Set the drive to spin up
   CALL   spinup
   JMP   MAINLOOP

POWER$DOWN:         ;Set the drive to spin down
   CALL   spindown
   JMP   MAINLOOP

DISPLAY:         ;Do we have display flag on or off
   LDA   @DisplayFlag   
   CMA         ;flip it
   STA   @DisplayFlag
   JMP   MAINLOOP   ;Update display and back to next menu command

SEQ$RD:            ;Do sequential reads
   CALL   SequentialReads
   JMP   MAINLOOP

DRIVE$0:
   MVI   A, IDE0      ;Select Drive 0:
   STA   @CURRENT$DRIVE
   OUT   IDEDrive
   LXI     D, SETA$MSG   
   CALL   PSTRING
   JMP   MAINLOOP

DRIVE$1:
   MVI   A, IDE1      ;Select Drive 1:
   STA   @CURRENT$DRIVE
   OUT   IDEDrive
   LXI     D,SETB$MSG   
   CALL   PSTRING
   JMP   MAINLOOP

RAMCLEAR:         ;Fill RAM buffer with 0's
   LXI   H, buffer   ;Point to buffer
   LXI   D, 512
   MVI   A, 0      ;Fill area with 0's
CLEAR1:   MOV   M, A
   INX   H
   DCX   D
   MOV   A, E
   ANA   D
   JNZ   CLEAR1
   LXI     D, FILL$MSG   
   CALL   PSTRING
   JMP   MAINLOOP
   
CPMBOOT:         ;Boot CPM from IDE system tracks -- if present
   MVI   A, 0      ;Load from track 0, sec 1, head 0 (always)
   STA   @SEC      ;Remember sectors are numbered +1
   XRA   A
   STA   @TRK+1
   STA   @TRK

   MVI   A, CPM$BOOT$COUNT ;Count of CPMLDR sectors  (12)
   STA   @SECTOR$COUNT
   LXI   H, CPMLDR$ADDRESS ;DMA address where the CPMLDR resides in RAM
   SHLD   @DMA

NextRCPM:
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current track, sector, head
   CALL   ZCRLF
   
   LHLD   @DMA
   CALL   READSECTOR   ;Read a sector
   SHLD   @DMA

   LDA   @SECTOR$COUNT
   DCR   A
   STA   @SECTOR$COUNT
   JZ   LOAD$DONE

   LHLD   @SEC
   INX   H
   SHLD   @SEC      ;Stay on track 0 in this special case
   JMP   NextRCPM

LOAD$DONE:
   MVI   E, REGstatus   ;Check the R/W status when done
   CALL   IDErd8D
   BIT   0, D
   JNZ   CPMLoadErr   ;Zero if no errors
   LXI   H, CPMLDR$ADDRESS
   MOV   A, M
   CPI   31H      ;EXPECT TO HAVE 31H @80H IE. LD SP,80H
   JNZ   CPMLoadErr1   ;Zero if no errors
   
   LXI   D, MOVE$REQUEST   ;Ask if we can move data to 100H
   CALL   PSTRING
   CALL   ZCI
   CALL   upper
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, CPM$MOVE$CODE ;Need to move code out of the way.
   LXI   D, 0H
   LXI   B, (CPM$MOVE$CODE$END-CPM$MOVE$CODE)
   LDIR
   JMP   0H      ;Now jump here to move the CPMLDR (@3000H) to 100H
   
CPMLoadErr1:
   LXI   D, CPM$ERROR1   ;Drive data error
   CALL   PSTRING
   JMP   MAINLOOP
   
CPMLoadErr:
   LXI   D, CPM$ERROR   ;Drive Read Error
   CALL   PSTRING
   JMP   MAINLOOP

N$RD$SEC:         ;Read N sectors
   LXI   D, ReadN$MSG   ;No check for possible high RAM (CPM) overwrite
   CALL   PSTRING
   CALL   GETHEX
   JC   MAINLOOP   ;Abort if ESC (C flag set)
   
   STA   @SECTOR$COUNT   ;Store sector count
   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA

NextRSec:   
   LXI   D, ReadingN$MSG
   CALL   PSTRING
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current track, sector, head
   
   LHLD   @DMA
   CALL   READSECTOR
   SHLD   @DMA

   LDA   @SECTOR$COUNT
   DCR   A
   STA   @SECTOR$COUNT
   JZ   MAINLOOP
   
   LHLD   @SEC
   INX   H
   SHLD   @SEC   
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextRSec

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC
   LHLD   @TRK      ;Bump to next track
   INX   H
   SHLD   @TRK
   MOV   A, L      ;0-FFH tracks (only)
   JNZ   NextRSec
   
   LXI   D, AtEnd   ;Tell us we are at end of disk
   CALL   PSTRING
   JMP   MAINLOOP

N$WR$SEC:         ;Write N sectors
   LXI     D, msgsure   ;Are you sure?
   CALL   PSTRING
   CALL   ZCI
   CALL   upper
   CPI   'Y'
   JNZ   main2c

   LXI   D, WriteN$MSG
   CALL   PSTRING
   CALL   GETHEX
   JC   MAINLOOP   ;Abort if ESC (C flag set)

   STA   @SECTOR$COUNT   ;Store sector count
   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA

NextWSec:   
   LXI   D, WritingN$MSG
   CALL   PSTRING
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current track, sector, head
   
   LHLD   @DMA      ;Actully, Sector/track values are already updated
   CALL   WRITESECTOR   ;in wrlba, but WRITESECTOR is used in multiple places.
   SHLD   @DMA      ;A repeat does no harm -- speed is not an issue here

   LDA   @SECTOR$COUNT
   DCR   A
   STA   @SECTOR$COUNT
   JZ   MAINLOOP
   
   LHLD   @SEC
   INX   H
   SHLD   @SEC   
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextWSec

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC
   LHLD   @TRK      ;Bump to next track
   INX   H
   SHLD   @TRK
   MOV   A, L      ;0-FFH tracks (only)
   ORA   A
   JNZ   NextWSec
   
   LXI   D, AtEnd   ;Tell us we are at end of disk
   CALL   PSTRING
   JMP   MAINLOOP

FORMAT:            ;Format (Fill sectors with E5)
   LXI   D, FORMAT$MSG
   CALL   PSTRING
   LXI     D, msgsure   ;Are you sure?
   CALL   PSTRING
   CALL   ZCI
   CALL   upper
   CPI   'Y'
   JNZ   MAINLOOP
   LXI   H, buffer   ;Fill buffer with 0E5's (512 of them)
   MVI   B, 0
Fill0:   MVI   A, 0E5H      ;<-- Sector fill character (E5 for CPM)
   MOV   M, A
   INX   H
   MOV   M, A
   INX   H
   DCR   B
   JNZ   Fill0
   CALL   ZCRLF

NEXT$FORMAT:
   LXI   H, buffer
   SHLD   @DMA
   CALL   WRITESECTOR   ;Will return error if there was one
   JZ   main9b      ;Z means the sector write was OK
   CALL   ZCRLF
   JMP   MAINLOOP
main9b:   CALL   ZEOL      ;Clear line cursor is on
   CALL   DISPLAYposition   ;Display actual current Track,sector,head#
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   WRNEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JZ   MAINLOOP
   CALL   ZCRLF
WRNEXTSEC1:
   LHLD   @SEC
   INX   H
   SHLD   @SEC      ;0 to MAXSEC CPM Sectors
   MOV   A, L
   CPI   MAXSEC
   JNZ   NEXT$FORMAT

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC
   LHLD   @TRK      ;Bump to next track
   INX   H
   SHLD   @TRK
   MOV   A, L      ;0-FFH tracks (only)
   CPI   MAXTRK
   JNZ   NEXT$FORMAT   

   LXI   D, FormatDone   ;Tell us we are all done.
   CALL   PSTRING
   JMP   MAINLOOP
            
BACKUP:            ;Backup the CPM partition to another area
   LXI   D, CopyMsg
   CALL   PSTRING
   CALL   ZCI
   CALL   upper
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   @SEC
   SHLD   @SEC1
   SHLD   @SEC2      ;and on second partition
   SHLD   @TRK      ;and track 0
   SHLD   @TRK1
   LXI   H, MAXTRK+0200H+1
   SHLD   @TRK2

   CALL   ZCRLF
   CALL   ZCRLF
   
NextCopy1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, RBackup$MSG   ;for each track update display
   CALL   PSTRING
   LDA   @TRK1+1      ;High TRK byte
   CALL   phex
   LDA   @TRK1      ;Low TRK byte
   CALL   phex
   LXI   D, WBackup$MSG
   CALL   PSTRING
   LDA   @TRK2+1      ;High TRK byte
   CALL   phex
   LDA   @TRK2      ;Low TRK byte
   CALL   phex
   LXI   D, H$Msg
   CALL   PSTRING

NextCopy:   
   LDA   @SEC1
   STA   @SEC
   LHLD   @TRK1
   SHLD   @TRK
   CALL   wrlba      ;Update LBA on "1st" drive

   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA
   CALL   READSECTOR   ;Get sector data to buffer
   
   LDA   @SEC2
   STA   @SEC
   LHLD   @TRK2
   SHLD   @TRK
   CALL   wrlba      ;Update LBA on "2nd" drive
   
   LXI   H,buffer   ;Point to buffer
   SHLD   @DMA
   CALL   WRITESECTOR   ;Write buffer data to sector
   
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   BKNEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JZ   MAINLOOP

BKNEXTSEC1:
   LHLD   @SEC
   INX   H
   SHLD   @SEC1
   SHLD   @SEC2   
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextCopy

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC1
   SHLD   @SEC2
   
   LHLD   @TRK1      ;Bump to next track
   INX   H
   SHLD   @TRK1
   
   LHLD   @TRK2      ;Bump to next track
   INX   H
   SHLD   @TRK2
   
   LHLD   @TRK1      ;Check if we are done
   MOV   A, L      ;0-FFH tracks (only)
   CPI   MAXTRK
   JNZ   NextCopy1
   
   LXI   D, BackupDone   ;Tell us we are all done.
   CALL   PSTRING
   JMP   MAINLOOP

RESTORE:         ;Restore disk from backup partition
   LXI   D, RestoreMsg
   CALL   PSTRING
   CALL   ZCI
   CALL   upper
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   @SEC
   SHLD   @SEC1
   SHLD   @SEC2      ;and on second partition
   SHLD   @TRK      ;and track 0
   SHLD   @TRK1
   LXI   H, MAXTRK+0200H+1
   SHLD   @TRK2

   CALL   ZCRLF
   CALL   ZCRLF
   
NextRestore1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, RBackup$MSG   ;for each track update display
   CALL   PSTRING
   LDA   @TRK2+1      ;High TRK byte
   CALL   phex
   LDA   @TRK2      ;Low TRK byte
   CALL   phex
   LXI   D, WBackup$MSG
   CALL   PSTRING
   LDA   @TRK1+1      ;High TRK byte
   CALL   phex
   LDA   @TRK1      ;Low TRK byte
   CALL   phex
   LXI   D,H$Msg
   CALL   PSTRING

NextRestore:   
   LDA   @SEC2      ;Point to backup partition
   STA   @SEC
   LHLD   @TRK2
   SHLD   @TRK
   CALL   wrlba      ;Update LBA on "1st" drive

   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA
   CALL   READSECTOR   ;Get sector data to buffer
   
   LDA   @SEC1
   STA   @SEC
   LHLD   @TRK1
   SHLD   @TRK
   CALL   wrlba      ;Update LBA on "2nd" drive
   
   LXI   H,buffer   ;Point to buffer
   SHLD   @DMA
   CALL   WRITESECTOR   ;Write buffer data to sector
   
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   RESNEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JZ   MAINLOOP

RESNEXTSEC1:
   LHLD   @SEC
   INX   H
   SHLD   @SEC1
   SHLD   @SEC2   
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextRestore

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC1
   SHLD   @SEC2
   
   LHLD   @TRK1      ;Bump to next track
   INX   H
   SHLD   @TRK1
   
   LHLD   @TRK2      ;Bump to next track
   INX   H
   SHLD   @TRK2
   
   LHLD   @TRK2      ;Check if we are done
   MOV   A, L      ;0-FFH tracks (only)
   CPI   MAXTRK
   JNZ   NextRestore1
   
   LXI   D, RestoreDone   ;Inform user restore complete
   CALL   PSTRING
   JMP   MAINLOOP

ERROR:   LXI     D, msgErr   ;CMD error msg
   CALL   PSTRING
   JMP   MAINLOOP

COPY$AB:         ;Copy Drive 0: to Drive 1:
   LXI   D, DiskCopyMsg
   CALL   PSTRING
   CALL   ZCI
   CALL   upper
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   @SEC
   SHLD   @TRK      ;and track 0
   
   CALL   ZCRLF
   CALL   ZCRLF
   
NextDCopy1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, CopyTrk$MSG   ;for each track update display
   CALL   PSTRING
   LDA   @TRK+1      ;High TRK byte
   CALL   phex
   LDA   @TRK      ;Low TRK byte
   CALL   phex
   LXI   D, H$Msg
   CALL   PSTRING

NextDCopy:   
   MVI   A, IDE0      ;Login drive 0:
   STA   @CURRENT$DRIVE
   OUT   IDEDrive

   CALL   wrlba      ;Update LBA on "0:" drive

   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA
   CALL   READSECTOR   ;Get sector data from 0: drive to buffer
   
   MVI   A, IDE1      ;Login drive 1:
   STA   @CURRENT$DRIVE
   OUT   IDEDrive

   CALL   wrlba      ;Update LBA on "1:" drive
   
   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA
   CALL   WRITESECTOR   ;Write buffer data to sector on 1: drive
   
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM says something is there
   JNZ   BK$D$NEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JNZ   BK$D$NEXTSEC1
   MVI   A,IDE0      ;Login drive 1:
   STA   @CURRENT$DRIVE
   OUT   IDEDrive
   JMP   MAINLOOP

BK$D$NEXTSEC1:
   LHLD   @SEC
   INX   H
   SHLD   @SEC
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextDCopy

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC
   
   LHLD   @TRK      ;Bump to next track
   INX   H
   SHLD   @TRK
            ;Check if we are done
   MOV   A, L      ;0-FFH tracks (only)
   CPI   MAXTRK
   JNZ   NextDCopy1
   
   LXI   D, CopyDone   ;Inform user copy complete
   CALL   PSTRING
   MVI   A, IDE0      ;Login drive 0:
   STA   @CURRENT$DRIVE
   OUT   IDEDrive
   JMP   MAINLOOP

VERIFY$AB:         ;Verify Drive 0: = 1:
   LXI   D, DiskVerifyMsg
   CALL   PSTRING
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   @SEC
   SHLD   @TRK      ;and track 0
   
   CALL   ZCRLF
   CALL   ZCRLF
   
NextVCopy1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, VerifyTrk$MSG ;for each track update display
   CALL   PSTRING
   LDA   @TRK+1      ;High TRK byte
   CALL   phex
   LDA   @TRK      ;Low TRK byte
   CALL   phex
   LXI   D, H$Msg
   CALL   PSTRING

NextVCopy:   
   MVI   A, IDE0      ;Login drive 0:
   STA   @CURRENT$DRIVE
   OUT   IDEDrive

   CALL   wrlba      ;Update LBA on "0:" drive

   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA
   CALL   READSECTOR   ;Get sector data from buffer 0: drive
   
   MVI   A, IDE1      ;Login drive 1:
   STA   @CURRENT$DRIVE
   OUT   IDEDrive

   CALL   wrlba      ;Update LBA on "1:" drive
   
   LXI   H, buffer2   ;Point to buffer2
   SHLD   @DMA
   CALL   READSECTOR   ;Read buffer data from sector of 1 drive
   
   LXI   BC, 512      ;Now check both buffers are identical
   LXI   H, buffer
   LXI   D, buffer2
NEXTV:   LDAX   D
   CMP   M      ;Is [DE] = [HL]
   JNZ   COMPARE$ERROR
   INX   H
   INX   D
   DCX   B
   MOV   A,C
   ANA   B
   JZ   VERIFY$OK
   JMP   NEXTV

COMPARE$ERROR:
   LXI   D, VERIFY$ERR   ;Indicate an error
   CALL   PSTRING
   LDA   @TRK+1      ;High TRK byte
   CALL   phex
   LDA   @TRK      ;Low TRK byte
   CALL   phex
   LXI   D, SEC$Msg
   CALL   PSTRING
   LDA   @SEC      ;Sector byte
   CALL   phex
   LXI   D, H$Msg
   CALL   PSTRING
   JMP   VER$OK1
   
VERIFY$OK:
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM says something is there
   JNZ   BK$V$NEXTSEC1
   CALL   ZCI      ;Flush character
VER$OK1:
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JNZ   BK$V$NEXTSEC1
   MVI   A,IDE0      ;Login drive 0:
   STA   @CURRENT$DRIVE
   OUT   IDEDrive
   JMP   MAINLOOP

BK$V$NEXTSEC1:
   LHLD   @SEC
   INX   H
   SHLD   @SEC
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextVCopy

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC
   
   LHLD   @TRK      ;Bump to next track
   INX   H
   SHLD   @TRK
            ;Check if we are done
   MOV   A, L      ;0-FFH tracks (only)
   CPI   MAXTRK
   JNZ   NextVCopy1
   
   LXI   D, VerifyDone   ;Tell us we are all done.
   CALL   PSTRING
   MVI   A, IDE0      ;Login drive 0:
   STA   @CURRENT$DRIVE
   OUT   IDEDrive
   JMP   MAINLOOP
   

;----------------------------- SUPPORT FUNCTIONS ------------------------------   

            
driveid:CALL   IDEwaitnotbusy   ;Retrieve drive info
   RC
   MVI   D, COMMANDid
   MVI   E, REGcommand
   CALL   IDEwr8D      ;Issue the ID command
  IF VERBOSE
   LXI   D, READING$ID
   CALL   PSTRING
   LXI   D, DISKSTATUS   ;Print status message
   CALL   PSTRING
  ENDIF
   MVI   E, REGstatus   ;Get status after ID command
   CALL   IDErd8D      ;Check Status (info in [D])
  IF VERBOSE
   MOV   A, D
   CALL   PHEX      ;Print status
   CALL   ZPERCRLF
  ENDIF
   CALL   IDEwaitdrq   ;Wait for Busy=0, DRQ=1
   JC   SHOWerrors
  IF VERBOSE
   LXI   D, GETTING$ID
   CALL   PSTRING
  ENDIF
   MVI   B, 0      ;256 words
   LXI     H, IDbuffer   ;Store data here
   CALL   MoreRD16   ;Get 256 words of data from REGdata port to [HL]
   RET

spinup:            ;Start the drive
   MVI   D, COMMANDspinup
spup2:   MVI   E, REGcommand
   CALL   IDEwr8D
   CALL   IDEwaitnotbusy
   JC   SHOWerrors
   ORA   A      ;Clear carry
   RET
            
spindown:         ;Tell the drive to spin down
   CALL   IDEwaitnotbusy
   JC   SHOWerrors
   MVI   D,COMMANDspindown
   JMP   spup2

SequentialReads:      ;Sequentially read sectors from current position
   CALL   IDEwaitnotbusy
   JC   SHOWerrors
   CALL   ZCRLF
NEXTSEC:
   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA

   CALL   READSECTOR   ;Errors will show in READSECTOR
   JZ   SEQOK
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC      ;Abort if ESC
   RZ
   
SEQOK:   CALL   ZEOL      ;Clear line cursor is on
   CALL   DISPLAYposition   ;Display current track, sector, head

   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA

   LDA   @DisplayFlag   ;Do we have display flag on or off
   ORA   A      ;NZ = on
   CNZ   HEXDUMP
   CALL   ZCRLF
   CALL   ZCRLF
   CALL   ZCRLF

   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   NEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   RZ
   CALL   ZCRLF
NEXTSEC1:
   LHLD   @SEC
   INX   H
   SHLD   @SEC   
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NEXTSEC

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC
   LHLD   @TRK      ;Bump to next track
   INX   H
   SHLD   @TRK
   JMP   NEXTSEC      ;Note will go to last sec on disk unless stopped

DISPLAYposition:      ;Display current track, sector & head position
   LXI     D, msgCPMTRK   ;Display in LBA format
   CALL   PSTRING      ;---- CPM FORMAT ----
   LDA   @TRK+1      ;High TRK byte
   CALL   phex
   LDA   @TRK      ;Low TRK byte
   CALL   phex
      
   LXI     D, msgCPMSEC
   CALL   PSTRING      ;SEC = (16 bits)
   LDA   @SEC+1      ;High Sec
   CALL   phex
   LDA   @SEC      ;Low sec
   CALL   phex
            ;---- LBA FORMAT ----
   LXI     D, msgLBA
   CALL   PSTRING      ;LBA = 00 ("Heads" = 0 for these drives)
   LDA   @DRIVE$TRK+1   ;High "cylinder" byte
   CALL   phex
   LDA   @DRIVE$TRK   ;Low "cylinder" byte
   CALL   phex   
   LDA   @DRIVE$SEC
   CALL   phex
   LXI     D, MSGBracket
   CALL   PSTRING      
   RET

printText:         ;Print text up to [B] (16-bit word) byte-pairs
   MOV   C, M      ;Text is contiguous byte array
   CALL   ZCO   
   INX   H
   MOV   C, M
   CALL   ZCO   
   INX   H
   DCR   B
   JNZ   printText
   RET

printSwap:         ;Print text up to [B] (16-bit word) byte-pairs   
   INX   H      ;Swap byte pairs - low byte, high byte
   MOV   C, M
   CALL   ZCO   
   DCX   H
   MOV   C, M
   CALL   ZCO
   INX   H
   INX   H
   DCR   B
   JNZ   printSwap
   RET

ZCRLF:
   PUSH   PSW
   MVI   C, CR
   CALL   ZCO
   MVI   C, LF
   CALL   ZCO
   POP   PSW
   RET

ZPERCRLF:
   PUSH   PSW
   MVI   C, PERIOD
   CALL   ZCO
   MVI   C, CR
   CALL   ZCO
   MVI   C, LF
   CALL   ZCO
   POP   PSW
   RET

ZEOL:            ;CR and clear current line
   MVI   C, CR
   CALL   ZCO
   MVI   C, CLEAR   ;Use 80 spaces if necessary
   CALL   ZCO
   RET

ZCSTS:
  IF CPM
   PUSH   B
   PUSH   D
   PUSH   H
   MVI   C, CONST
   CALL   BDOS      ;Returns with 1 in [A] if character at keyboard
   POP   H
   POP   D
   POP   B
   CPI   1
   RET
  ELSE   
   IN   COMI      ;Get Character in [A]
   ANI   02H
   RZ
   MVI   A, 01H
   ORA   A
   RET
  ENDIF

ZCO:            ;Write character that is in [C]
  IF CPM
   PUSH   PSW
   PUSH   B
   PUSH   D
   PUSH   H
   MOV   E, C
   MVI   C, WRCON
   CALL   BDOS
   POP   H
   POP   D
   POP   B
   POP   PSW
   RET
  ELSE   
   PUSH   PSW   
ZCO1:   IN      CONI      ;Show Character
   ANI   04H
   JZ   ZCO1
   MOV   A, C
   OUT   CONO
   POP   PSW
   RET
  ENDIF

ZCI:            ;Return keyboard character in [A]
  IF CPM
   PUSH   B
   PUSH   D
   PUSH   H
   MVI   C, RDCON
   CALL   BDOS
   POP   H
   POP   D
   POP   B
   RET
  ELSE
ZCI1:   IN   CONI      ;Get Character in [A]
   ANI   02H
   JZ   ZCI1
   IN   CONO
   RET
  ENDIF

;------------------------------------------------------------------------------   
;Print a string in [DE] up to '$'
;------------------------------------------------------------------------------   

PSTRING:
  IF CPM
   MVI   C, PRINT
   JMP   BDOS      ;PRINT MESSAGE,
  ELSE
   PUSH   B
   PUSH   D
   PUSH   H
   XCHG
PSTRX:   MOV   A, M
   CPI   '$'
   JZ   DONEP
   MOV   C, A
   CALL   ZCO
   INX   H
   JMP   PSTRX
DONEP:   POP   H
   POP   D
   POP   B
   RET
  ENDIF

SHOWerrors:
  IF NOT DEBUG
   ORA   A      ;Set NZ flag
   STC         ;Set Carry Flag
   RET
  ELSE
   CALL   ZCRLF
   MVI   E, REGstatus   ;Get status in status register
   CALL   IDErd8D
   MOV   A, D
   ANI   1H
   JNZ   MoreError   ;Go to  REGerr register for more info
            ;All OK if 01000000
   PUSH   PSW      ;save for return below
   ANI   80H
   JZ   NOT7
   LXI   D, DRIVE$BUSY   ;Drive Busy (bit 7) stuck high
   CALL   PSTRING
   JMP   DONEERR
NOT7:   ANI   40H
   JNZ   NOT6
   LXI   D, DRIVE$NOT$READY ;Drive Not Ready (bit 6) stuck low
   CALL   PSTRING
   JMP   DONEERR
NOT6:   ANI   20H
   JNZ   NOT5
   LXI   D, DRIVE$WR$FAULT ;Drive write fault
   CALL   PSTRING
   JMP   DONEERR
NOT5   LXI   D, UNKNOWN$ERROR
   CALL   PSTRING
   JMP   DONEERR

MoreError:         ;Bit 0 of the status register indicates problem
   MVI   E, REGerr   ;Get error code in REGerr
   CALL   IDErd8D
   MOV   A, D
   PUSH   PSW

   ANI   10H
   JZ   NOTE4
   LXI   D, SEC$NOT$FOUND
   CALL   PSTRING
   JMP   DONEERR

NOTE4:   ANI   80H
   JZ   NOTE7
   LXI   D, BAD$BLOCK
   CALL   PSTRING
   JMP   DONEERR
NOTE7:   ANI   40H
   JZ   NOTE6
   LXI   D, UNRECOVER$ERR
   CALL   PSTRING
   JMP   DONEERR
NOTE6:   ANI   4H
   JZ   NOTE2
   LXI   D, INVALID$CMD
   CALL   PSTRING
   JMP   DONEERR
NOTE2:   ANI   2H
   JZ   NOTE1
   LXI   D, TRK0$ERR
   CALL   PSTRING
   JMP   DONEERR
NOTE1:   LXI   D, UNKNOWN$ERROR1
   CALL   PSTRING
   JMP   DONEERR

DONEERR:POP   PSW
   PUSH   PSW
   CALL   ZBITS
   CALL   ZCRLF
   POP   PSW
   ORA   A      ;Set Z flag
   STC         ;Set Carry flag
   RET
   ENDIF

;------------------------------------------------------------------------------   
;Print a 16-bit number in RAM located @ [HL], low-byte first for Drive ID
;------------------------------------------------------------------------------   

printparm:
   INX   H   ;Index to high byte first
   MOV   A, M
   CALL   PHEX
   DCX   H   ;Now low byte
   MOV   A, M
   CALL   PHEX
   RET

;------------------------------------------------------------------------------   
;Print an 8 bit number located in [A]
;------------------------------------------------------------------------------   

PHEX:   PUSH   PSW
   PUSH   B
   PUSH   PSW
   RRC
   RRC
   RRC
   RRC
   CALL   ZCONV
   POP   PSW
   CALL   ZCONV
   POP   B
   POP   PSW
   RET

ZCONV:   ANI   0FH      ;HEX to ASCII and print it
   ADI   90H
   DAA
   ACI   40H
   DAA
   MOV   C, A
   CALL   ZCO
   RET

;------------------------------------------------------------------------------   
;Display binary in [A]
;------------------------------------------------------------------------------   

ZBITS:   PUSH   PSW
   PUSH   B
   PUSH   D
   MOV   E, A      
   MVI   B, 8
BQ2:   DB   0CBH, 23H   
   SLAR   E
   MVI   A, 18H
   ADC   A
   MOV   C, A
   CALL   ZCO
   DCR   B
   JNZ   BQ2
   POP   D
   POP   B
   POP   PSW
   RET

ghex32lba:         ;Convert CPM Track & Sector to LBA format
   LXI     D,ENTER$SECL   ;Enter sector number
   CALL   PSTRING
   CALL   GETHEX      ;Get 2 HEX digits
   RC
   STA   @SEC
   CALL   ZCRLF

   LXI   D, ENTER$TRKH   ;Enter high byte track number
   CALL   PSTRING
   CALL   GETHEX      ;Get 2 HEX digits
   RC
   STA   @TRK+1
   CALL   ZCRLF

   LXI     D, ENTER$TRKL   ;Enter low byte track number
   CALL   PSTRING
   CALL   GETHEX      ;Get 2 more HEX digits
   RC
   STA   @TRK
   CALL   ZCRLF
   XRA   A
   ORA   A      ;To return NC
   RET

;------------------------------------------------------------------------------   
;Get a HEX character from the keyboard and echo it
;------------------------------------------------------------------------------   

GETHEX:
   CALL   GETCMD      ;Get character
   CPI   ESC
   JZ   HEXABORT
   CPI   '/'      ;check 0-9, A-F
   JC   HEXABORT
   CPI   'F'+1
   JNC   HEXABORT
   CALL   ASBIN      ;Convert to binary
   RLC         ;Shift to high nibble
   RLC
   RLC
   RLC
   MOV   B, A      ;Store it
   CALL   GETCMD      ;Get 2nd character from keyboard & ECHO
   CPI   ESC
   JZ   HEXABORT
   CPI   '/'      ;check 0-9, A-F
   JC   HEXABORT
   CPI   'F'+1
   JNC   HEXABORT
   CALL   ASBIN      ;Convert to binary
   ORA   B      ;add in the first digit
   ORA   A      ;To return NC
   RET
HEXABORT:
   STC         ;Set Carry flag
   RET

;------------------------------------------------------------------------------   
;Get a character from the keyboard, convert to uppercase and echo it
;------------------------------------------------------------------------------   

GETCMD:   CALL   ZCI      ;Get character
   CALL   UPPER
   CPI   ESC
   RZ         ;Don't echo an ESC
  IF NOT CPM
   PUSH   PSW      ;Save it
   PUSH   B
    MOV   C, A
   CALL   ZCO      ;Echo it
   POP   B
   POP   PSW      ;get it back
  ENDIF
   RET

;------------------------------------------------------------------------------   
;Convert lowercase to uppercase
;------------------------------------------------------------------------------   

UPPER:   CPI   'a'      ;must be >= lowercase a
   RC         ;else go back...
   CPI   'z'+1      ;must be <= lowercase z
   RNC         ;else go back...
   SUI   'a'-'A'      ;subtract lowercase bias
   RET

ASBIN:   SUI   30H       ;ASCII to binary conversion
   CPI   0AH
   RM
   SUI   07H
   RET

;------------------------------------------------------------------------------   
;Print a hexdump of the data in the 512 byte buffer @[HL]
;------------------------------------------------------------------------------   

HEXDUMP:
   PUSH   PSW      ;Save everything
   PUSH   B
   PUSH   D         
   PUSH   H
   
   CALL   ZCRLF      ;CR/LF first
   MVI   D, 32      ;Print 32 lines total
   MVI   B, 16      ;16 characters across
   SHLD   @StartLineHex   ;Save buffer location for ASCII display below
   LXI   H, 0
   SHLD   @BYTE$COUNT
   
SF172:   CALL   ZCRLF
   LHLD   @BYTE$COUNT
   MOV   A, H
   CALL   PHEX      ;Print byte count in sector
   MOV   A, L
   CALL   PHEX      
   PUSH   D
   LXI   D, 16
   DAD   D
   POP   D
   SHLD   @BYTE$COUNT   ;Store for next time
   CALL   BLANK
   LHLD   @StartLineHex
   SHLD   @StartLineASCII   ;Store for ASCII display below

SF175:   MOV   A, M
   CALL   LBYTE      ;Display [A] on CRT/LCD
   INX   H
   DCR   B
   JNZ   SF175
   SHLD   @StartLineHex   ;Save for next line later
   CALL   ShowAscii   ;Now translate to ASCII and display
   MVI   B, 16      ;16 characters across for next line
   DCR   D
   JNZ   SF172      ;Have we done all 32 lines

   CALL   ZCRLF
   POP   H      ;Get back original registers
   POP   D
   POP   B
   POP   PSW
   RET
   
ShowAscii:         ;Show as ASCII info
   LHLD   @StartLineASCII
   MVI   B, 16      ;16 ASCII characters across
XF172:   CALL   BLANK      ;Send a space character
   CALL   BLANK
XF175:   MOV   A, M
   ANI   7FH
   CPI   ' '       ;Filter out control characters
   JNC   XT33
XT22:   MVI   A, '.'
XT33:   CPI   07CH
   JNC   XT22
   MOV   C, A      ;Setup to send
   PUSH   B
   CALL   ZCO
   POP   B
   INX   H      ;Next position in buffer
   DCR   B
   JNZ   XF175
   RET

BLANK:   PUSH   B
   PUSH   H
   MVI   C, ' '
   CALL   ZCO
   POP   H
   POP   B
   RET

LBYTE:   PUSH   PSW
   RRC
   RRC
   RRC
   RRC
   CALL   SF598
   POP   PSW

SF598:   CALL   ZCONV
   RET

;------------------------------------------------------------------------------   
;IDE Drive BIOS Routines written in a format that can be used directly with CPM
;------------------------------------------------------------------------------   

IDEinit:         ;Initialize the 8255 and drive then do a hard reset
  IF VERBOSE
   LXI   D, INITDRIVE
   CALL   PSTRING
  ENDIF
   MVI   A, READcfg8255   ;Config 8255 chip (10010010B)
   OUT   IDEportCtrl   ;for READ mode
            
   MVI   A, IDErstline   ;Hard reset the disk drive
   OUT   IDEportC   ;Some CF cards are sensitive to reset pulse width
   MVI   B, 20H      ;Symptom is incorrect data back from a sector read

ResetDelay:
   DCR   B
   JNZ   ResetDelay   ;Delay (reset pulse width)

   XRA   A
   OUT   IDEportC   ;No control lines asserted (just bit 7 of port C)
   CALL   DELAY$SHORT   ;Short Delay

   MVI   D,11100000b   ;Data for IDE SDH reg (512byte, LBA, single drive, hd 0)
            ;For Trk, Sec, Head (non LBA) use 10100000

   MVI   E,REGshd   ;00001110,(0EH) for CS0,A2,A1, 
   CALL   IDEwr8D      ;Write byte to select the MASTER device

   MVI   B, 02H      ;Delay time for hard disks to get up to speed (2s)

WaitInit:
  IF VERBOSE
   LXI   D, DISKSTATUS   ;Print initialization status message
   CALL   PSTRING
  ENDIF
   MVI   E, REGstatus   ;Get status after initilization
   CALL   IDErd8D      ;Check Status (info in [D])
   MOV   A, D
  IF VERBOSE
   CALL   PHEX      ;Print drive initialization status
   CALL   ZPERCRLF
  ENDIF
   ANI   80H
   RZ         ;Return. We'll check for errors when we get back
   MVI   A, 2
   CALL   DELAY$LONG   ;Long delay, drive has to get up to speed
   DCR   B
   JNZ   WaitInit
   XRA   A
   DCR   A
   RET         ;Return NZ. We'll check for errors when we get back
   
DELAY$LONG:         ;Long delay (Seconds)
   STA   @DELAYStore
   PUSH   B
   LXI   B, 0FFFFH
DELAY2:   LDA   @DELAYStore
DELAY1:   DCR   A
   JNZ   DELAY1
   DCX   B
   MOV   A, C
   ORA   B
   JNZ   DELAY2
   POP   B
   RET

DELAY$SHORT:         ;Short delay (32ms)
   MVI   A, 40
DELAY3:   MVI   B, 0
M0:   DCR   B
   JNZ   M0
   DCR   A
   JNZ     DELAY3
   RET

;------------------------------------------------------------------------------   
;Sector Read
;------------------------------------------------------------------------------   

READSECTOR:         ;Read a sector, specified by the 3 bytes in LBA
            ;Z on success, NZ call error routine if problem

   CALL   wrlba      ;Tell which sector we want to read from.
            ;Translate first in case of an error, otherewise
            ;we will get stuck on bad sector
 
   CALL   IDEwaitnotbusy   ;Make sure drive is ready
   JC   SHOWerrors   ;Returned with NZ set if error

   MVI   D, COMMANDread
   MVI   E, REGcommand
   CALL   IDEwr8D      ;Send sec read command to drive.
   CALL   IDEwaitdrq   ;Wait until it's got the data
   JC   SHOWerrors
      
   LHLD     @DMA      ;DMA address
   MVI   B, 0      ;Read 512 bytes to [HL]

MoreRD16:
   MVI   A, REGdata   ;REG register address
   OUT   IDEportC   

   ORI   IDErdline   ;08H+40H, Pulse RD line
   OUT   IDEportC   

   IN   IDEportA   ;Read the lower byte first
   MOV   M, A
   INX   H
   IN   IDEportB   ;Then read the upper byte
   MOV   M, A
   INX   H
   
   MVI   A, REGdata   ;Deassert RD line
   OUT   IDEportC
   DCR   B
   JNZ   MoreRD16

   MVI   E, REGstatus
   CALL   IDErd8D
   MOV   A, D
   ANI   1H
   CNZ   SHOWerrors   ;If error display status
   RET

;------------------------------------------------------------------------------   
;Sector Write
;------------------------------------------------------------------------------   

WRITESECTOR:         ;Write a sector, specified by the 3 bytes in LBA
            ;Z on success, NZ to error routine if problem

   CALL   wrlba      ;Tell which sector we want to read from.
            ;Translate first in case of an error, otherewise
            ;we will get stuck on bad sector

   CALL   IDEwaitnotbusy   ;Make sure drive is ready
   JC   SHOWerrors

   MVI   D, COMMANDwrite
   MVI   E, REGcommand
   CALL   IDEwr8D      ;Tell drive to write a sector
   CALL   IDEwaitdrq   ;Wait unit it wants the data
   JC   SHOWerrors

   LHLD    @DMA
   MVI   B, 0

   MVI   A, WRITEcfg8255
   OUT   IDEportCtrl
   
WRSEC1:   MOV   A, M
   INX   H
   OUT   IDEportA   ;Write the lower byte first
   MOV   A, M
   INX   H
   OUT   IDEportB   ;Then high byte on B

   MVI   A, REGdata
   PUSH   PSW
   OUT   IDEportC   ;Send write command
   ORI   IDEwrline   ;Send WR pulse
   OUT   IDEportC
   POP   PSW
   OUT   IDEportC
   DCR   B
   JNZ   WRSEC1
   
   MVI   A, READcfg8255   ;Set 8255 back to read mode
   OUT   IDEportCtrl   

   MVI   E, REGstatus
   CALL   IDErd8D
   MOV   A, D
   ANI   1H
   CNZ   SHOWerrors   ;If error display status
   RET

;------------------------------------------------------------------------------   
;Write Logical Block Address (LBA) mode
;------------------------------------------------------------------------------   

wrlba:
   LDA   @SEC      ;LBA mode low sectors go directly
   INR   A      ;Sectors are numbered 1 -- MAXSEC
   STA   @DRIVE$SEC   ;For Diagnostic Display Only
   MOV   D, A
   MVI   E, REGsector   ;Send info to drive
   CALL   IDEwr8D
            
   LHLD   @TRK      
   MOV   A, L
   STA   @DRIVE$TRK
   MOV   D, L      ;Send Low TRK#
   MVI   E, REGcylinderLSB
   CALL   IDEwr8D

   MOV   A, H
   STA   @DRIVE$TRK+1
   MOV   D, H      ;Send High TRK#
   MVI   E, REGcylinderMSB
   CALL   IDEwr8D

   MVI   D, 1      ;For now, one sector at a time
   MVI   E, REGseccnt
   CALL   IDEwr8D
   RET

;------------------------------------------------------------------------------   
;Wait for drive to come ready
;------------------------------------------------------------------------------   

IDEwaitnotbusy:         ;Drive READY if status = 01000000
   MVI   B, 0FFH
   MVI   A, 0FFH      ;Delay must be above 80H, longer for slow drives
   STA   @DELAYStore

MoreWait:
   MVI   E, REGstatus   ;Wait for RDY bit to be set
   CALL   IDErd8D
   MOV   A, D
   ANI   11000000B
   XRI   01000000B
   JZ   DoneNotbusy
   DCR   B   
   JNZ   MoreWait
   LDA   @DELAYStore   ;Check timeout delay
   DCR   A
   STA   @DELAYStore
   JNZ   MoreWait

   STC         ;Set carry to indicate an error
   ret
DoneNotBusy:
   ORA   A      ;Clear carry it indicate no error
   RET

;------------------------------------------------------------------------------   
;Wait for drive to assert data request (DRQ) line is ready
;------------------------------------------------------------------------------   

IDEwaitdrq:
   MVI   B, 0FFH
   MVI   A, 0FFH      ;Delay must be above 80H, longer for slow drives
   STA   @DELAYStore

MoreDRQ:
   MVI   E, REGstatus   ;Wait for DRQ bit to be set
   CALL   IDErd8D
   MOV   A, D
   ANI   10001000B
   CPI   00001000B
   JZ   DoneDRQ
   DCR   B
   JNZ   MoreDRQ
   LDA   @DELAYStore   ;Check timeout delay
   DCR   A
   STA   @DELAYStore
   JNZ   MoreDRQ
   STC         ;Set carry to indicate error
   RET
DoneDRQ:
   ORA   A      ;Clear carry
   RET         ;Return drive status in A

;------------------------------------------------------------------------------   
;Clear the ID buffer
;------------------------------------------------------------------------------   

CLEAR$ID$BUFFER:
   LXI   H, IDBuffer
   LXI   B, 512
CLEAR2:   MVI   A, ' '
   MOV   M, A
   INX   H
   DCX   B
   MOV   A, C
   ORA   B
   JNZ   CLEAR2
   
   LXI   H, IDBuffer   ;Zero for cylinder, heads, sectors
   LXI   B, 14
CLEAR3:   MVI   A, 0
   MOV   M, A
   INX   H
   DCX   B
   MOV   A, C
   ORA   B
   JNZ   CLEAR3
   RET

;------------------------------------------------------------------------------   
; Low Level 8 bit R/W to the drive controller. These are the routines that talk
; directly to the drive controller registers, via the 8255 chip. 
; Note the 16 bit I/O to the drive (which is only for SEC R/W) is done directly
; in the routines READSECTOR & WRITESECTOR for speed reasons.
;------------------------------------------------------------------------------   

;------------------------------------------------------------------------------   
;Read One Byte
;------------------------------------------------------------------------------   

IDErd8D:            ;Read 8 bits from IDE register in [E],
   MOV   A, E         ;and return info in [D]
   OUT   IDEportC      ;Drive address onto control lines

   ORI   IDErdline      ;RD pulse pin (40H)
   OUT   IDEportC      ;Assert read pin

   IN   IDEportA
   MOV   D, A         ;Return with data in [D]

   MOV   A, E
   OUT   IDEportC      ;Deassert RD pin

   XRA   A
   OUT   IDEportC      ;Zero all port C lines
   RET

;------------------------------------------------------------------------------   
;Write One Byte
;------------------------------------------------------------------------------   

IDEwr8D:            ;Write Data in [D] to IDE register [E]
   MVI   A, WRITEcfg8255      ;Set 8255 to write mode
   OUT   IDEportCtrl

   MOV   A, D         ;Get data put it in 8255 A port
   OUT   IDEportA

   MOV   A, E         ;Select IDE register
   OUT   IDEportC

   ORI   IDEwrline      ;Lower WR line
   OUT   IDEportC
   
   MOV   A, E         ;Raise WR line
   OUT   IDEportC      ;Deassert RD pin

   XRA   A         ;Deselect all lines including WR line
   OUT   IDEportC

   MVI   A, READcfg8255      ;Config 8255 chip, read mode on return
   OUT   IDEportCtrl
   RET

;------------------------------------------------------------------------------   
;This code is written to reside and run from 0H.  To re-introduce the CPMLDR,
;it must be copied from where it is stored in high memory and relocated to 100H
;in RAM, which overwrites this program.
;------------------------------------------------------------------------------   

CPM$MOVE$CODE
   LXI   H,BUFFER
   LXI   D,100H
   LXI   B,(12*512)
   LDIR
   JMP   100H
CPM$MOVE$CODE$END:

;------------------------------------------------------------------------------   
;
;COMMAND BRANCH TABLE
;
;------------------------------------------------------------------------------   

TBL:   DW  DRIVE$0   ; "A"  Select Drive 0
   DW  DRIVE$1   ; "B"  Select Drive 1
   DW  CPMBOOT   ; "C"  LOAD CPM (if present)
   DW  DISPLAY   ; "D"  Sector contents display:- ON/OFF
   DW  RAMCLEAR  ; "E"  Clear RAM buffer
   DW  FORMAT    ; "F"  Format current disk
   DW  RESTORE   ; "G"  Restore backup
   DW  BACKUP    ; "H"  Backup partition
   DW  NEXT$SECT ; "I"  Next Sector
   DW  PREV$SEC  ; "J"  Previous sector
   DW  ERROR     ; "K" 
   DW  SET$LBA   ; "L"  Set LBA value (Set Track, sector) 
   DW  ERROR     ; "M" 
   DW  POWER$DOWN; "N"  Power down hard disk command
   DW  ERROR     ; "O" 
   DW  ERROR     ; "P" 
   DW  ERROR     ; "Q" 
   DW  READ$SEC  ; "R"  Read sector to data buffer
   DW  SEQ$RD    ; "S"  Sequental sec read and display contents
   DW  ERROR     ; "T" 
   DW  POWER$UP  ; "U"  Power up hard disk command
   DW  N$RD$SEC  ; "V"  Read N sectors
   DW  WRITE$SEC ; "W"  Write data buffer to current sector
   DW  N$WR$SEC  ; "X"  Write N sectors
   DW  COPY$AB   ; "Y"  Copy Drive 0 to Drive 1
   DW  VERIFY$AB ; "Z"  Verify Drive 0:= Drive 1:

;------------------------------------------------------------------------------   
;
;String constants - Messages generated by this program
;
;------------------------------------------------------------------------------

SIGN$ON:   DB   CR,LF,'IDE Disk Drive Utility Program 12/01/2022 (v2.9b)',CR,LF,LF,'$'
SEL0MSG      DB   'Selecting first IDE drive.',CR,LF,'$'
SEL1MSG      DB   'Selecting second IDE drive.',CR,LF,'$'
INITDRIVE   DB   'Initializing drive.  $'
READING$ID   DB   'Reading drive ID.  $'
GETTING$ID   DB   'Getting drive ID...',CR,LF,'$'
DISKSTATUS   DB   'Status is $'
INIT$0$ERROR:   DB   'Initialization of First Drive failed. Aborting Program.',BELL,CR,LF,LF,'$'
INIT$1$ERROR   DB   'Initialization of Second Drive failed. (Possibly not present).',BELL,CR,LF,LF,'$'
ID$ERROR:   DB   'Error obtaining Drive ID.',BELL,CR,LF,'$'
INIT$DR$OK:   DB   'Drive Initialized OK.',CR,LF,LF,'$'
BAD$DRIVE:   DB   CR,LF,'First Drive ID Information appears invalid.',CR,LF
      DB   'Aborting program.',BELL,CR,LF,LF,'$'
DRIVE0$INFO:   DB   '------------ Drive 0 -------------',CR,LF,'$'
DRIVE1$INFO:   DB   '------------ Drive 1 -------------',CR,LF,'$'
msgmdl:      DB   'Model: $'
msgsn:      DB   'S/N:   $'
msgrev:      DB   'Rev:   $'
msgcy:      DB   'Cyl: $'
msghd:      DB   ', Hd: $'
msgsc:      DB   ', Sec: $'
msgCPMTRK:   DB   'CPM TRK = $'
msgCPMSEC:   DB   ' CPM SEC = $'
msgLBA:      DB   '  (LBA = 00$'
MSGBracket   DB   ')$'
DRIVE$0$MSG   DB   CR,LF,LF,'  >>> DRIVE #0 <<<$'
DRIVE$1$MSG   DB   CR,LF,LF,'  >>> DRIVE #1 <<<$'
CMD$STRING1:    DB   '  IDE Board Diagnostic MAIN MENU',CR,LF,LF
      DB   '(L) Set LBA value    (R) Read Sector to Buffer (W) Write Buffer '
      DB   'to Sector',CR,LF
      DB   '(D) Set Display ON   (S) Sequental Sec Read    (F) Format Disk',CR,LF
      DB   '(V) Read N Sectors   (X) Write N Sectors       (H) Backup disk',CR,LF
      DB   '(G) Restore Backup   (I) Next Sector           '
      DB   '(J) Previous Sector',CR,LF
      DB   '(U) Power Up         (N) Power Down            (C) Boot CPM',CR,LF
      DB   '(A) Select Drive 0   (B) Select Drive 1        '
      DB   '(E) Clear Sector Buffer',CR,LF
      DB   '(Y) Copy d0 to d1    (Z) Verify d0 = d1        (ESC) Quit',CR,LF
      DB   LF,'Current settings: $'
CMD$STRING2:    DB   '      IDE Board Diagnostic MAIN MENU',CR,LF,LF
      DB   '(L) Set LBA value    (R) Read Sector to Buffer (W) Write Buffer '
      DB   'to Sector',CR,LF
      DB   '(D) Set Display OFF  (S) Sequental Sec Read    (F) Format Disk',CR,LF
      DB   '(V) Read N Sectors   (X) Write N Sectors       (H) Backup disk',CR,LF
      DB   '(G) Restore Backup   (I) Next Sector           '
      DB   '(J) Previous Sector',CR,LF
      DB   '(U) Power Up         (N) Power Down            (C) Boot CPM',CR,LF
      DB   '(A) Select Drive 0   (B) Select Drive 1        '
      DB   '(E) Clear Sector Buffer',CR,LF
      DB   '(Y) Copy d0 to d1    (Z) Verify d0 = d1        (ESC) Quit',CR,LF
      DB   LF,'Current settings:- $'
Prompt:      DB   CR,LF,LF,'Please enter command > $'
msgsure:   DB   CR,LF,'Warning: this will change data on the drive, '
      DB   'are you sure? (Y/N)...$'
msgrd:      DB   CR,LF,'Sector Read OK',CR,LF,'$'
msgwr:      DB   CR,LF,'Sector Write OK',CR,LF,'$'
GET$LBA:   DB   'Enter CPM style TRK & SEC values (in hex).',CR,LF,'$'
SEC$RW$ERROR   DB   'Drive Error, Status Register = $'
ERR$REG$DATA   DB   'Drive Error, Error Register = $'
ENTER$SECL   DB   'Starting sector number,(xxH) = $'
ENTER$TRKL   DB   'Track number (LOW byte, xxH) = $'
ENTER$TRKH   DB   'Track number (HIGH byte, xxH) = $'
ENTER$HEAD   DB   'Head number (01-0F) = $'
ENTER$COUNT   DB   'Number of sectors to R/W = $'
DRIVE$BUSY   DB   'Drive Busy (bit 7) stuck high.   Status = $'
DRIVE$NOT$READY   DB   'Drive Ready (bit 6) stuck low.  Status = $'
DRIVE$WR$FAULT   DB   'Drive write fault.    Status = $'
UNKNOWN$ERROR   DB   'Unknown error in status register.   Status = $'
BAD$BLOCK   DB   'Bad Sector ID.    Error Register = $'
UNRECOVER$ERR   DB   'Uncorrectable data error.  Error Register = $'
READ$ID$ERROR   DB   'Error setting up to read Drive ID',CR,LF,'$'
SEC$NOT$FOUND   DB   'Sector not found. Error Register = $'
INVALID$CMD   DB   'Invalid Command. Error Register = $'
TRK0$ERR   DB   'Track Zero not found. Error Register = $'
UNKNOWN$ERROR1   DB   'Unknown Error. Error Register = $'
CONTINUE$MSG   DB   CR,LF,'To Abort enter ESC. Any other key to continue. $'
FORMAT$MSG   DB   'FORMAT DISK. Fill all sectors with E5'
      DB   60H,'s on the CURRENT drive/CF card.$'
ReadN$MSG   DB   CR,LF,'Read multiple sectors from current disk/CF card to RAM buffer.'
      DB   CR,LF,'How many 512 byte sectores (xx HEX):$'
WriteN$MSG   DB   CR,LF,'Write multiple sectors RAM buffer CURRENT disk/CF card.'
      DB   CR,LF,'How many 512 byte sectores (xx HEX):$'
ReadingN$MSG   DB   CR,LF,'Reading Sector at: $'
WritingN$MSG   DB   CR,LF,'Writing Sector at: $'
msgErr      DB   CR,LF,'Sorry, that was not a valid menu option!$'
FormatDone   DB   CR,LF,'Disk Format Complete.',CR,LF,'$'
BackupDone   DB   CR,LF,'Disk partition copy complete.',CR,LF,'$'
CopyMsg      DB   CR,LF,'Copy disk partition to a second area on disk (CF card).'
      DB   CR,LF,'>>> This assumes that tracks greater than MAXTRK '
      DB   '(for CPM, 0FFH) are unused <<<'
      DB   CR,LF,'>>> on this disk. Be sure you have nothing in this '
      DB   '"Backup partition area". <<<'
      DB   CR,LF,BELL,'Warning: This will change data in the partition area, '
      DB   'are you sure? (Y/N)...$ '
AtEnd      DB   CR,LF,'At end of disk partition!',CR,LF,'$'
RBackup$MSG   DB   'Reading track: $'
WBackup$MSG   DB   'H. Writing track: $'
H$Msg      DB   'H$'
RestoreMsg   DB   CR,LF,'Restore disk with data from backup partition on disk (CF card).'
      DB   CR,LF,BELL,'Warning: This will change data on disk, '
      DB   'are you sure? (Y/N)...$ '
RestoreDone   DB   CR,LF,'Restore of disk data from backup partition complete.',CR,LF,'$'
RANGE$MSG   DB   CR,LF,'Sector value out of range.',CR,LF,'$'
CPM$ERROR   DB   CR,LF,'Error reading CPMLDR.',CR,LF,'$'
CPM$ERROR1   DB   CR,LF,'Data error reading CPMLDR. (The first byte loaded was not 31H).',CR,LF,'$'
MOVE$REQUEST   DB   CR,LF,'The CPMLDR image is now at 3000H in RAM. '
      DB   'To boot CPM you will have to'
      DB   CR,LF,'overwrite this program at 100H. Do you wish to do so (Y/N)...$'
SETA$MSG   DB   CR,LF,'Current Drive is now #0 (Yellow LED)$'
SETB$MSG   DB   CR,LF,'Current Drive is now #1 (Green LED)$'
FILL$MSG   DB   CR,LF,'Sector buffer in RAM filled with 0',27H,'s$'      
DiskCopyMsg   DB   CR,LF,'Copy disk partition of Drive 0 to Drive 1 (CF card).'
      DB   CR,LF,BELL,'Warning: This will delete all data on Drive 1, '
      DB   'are you sure? (Y/N)...$ '
CopyDone   DB   CR,LF,'Disk copy of CPM disk 0 to 1 complete.',CR,LF,'$'
CopyTrk$MSG   DB   'Copying track: $'
DiskVerifyMsg   DB   CR,LF,'Verify disk partition Drive 0 = Drive 1 (CF card).$'
VerifyTrk$MSG   DB   'Verifying track: $'
VerifyDone   DB   CR,LF,'Verify CPM disk 0 = 1 complete.',CR,LF,'$'
Verify$ERR   DB   CR,LF,BELL,'Verify error on Track $'
SEC$Msg      DB   'H  Sector $'

;------------------------------------------------------------------------------   
;RAM usage
;------------------------------------------------------------------------------   

RAMAREA      DB   '           RAM STORE AREA -------->'
@DMA      DW   buffer
@DRIVE$SEC   DB   0H
@DRIVE$TRK   DW   0H
@DisplayFlag   DB   0FFH      ;Display of sector data initially ON
@SEC      DW   0H
@TRK      DW   0H
@SEC1      DW   0H      ;For disk partition copy
@TRK1      DW   0H
@SEC2      DW   0H
@TRK2      DW   0H
@StartLineHex   DW   0H
@StartLineASCII   DW   0H
@BYTE$COUNT   DW   0H
@SECTOR$COUNT   DW   0H
@DELAYStore   DB   0H
@CURRENT$DRIVE   DB   0H

      DB   '          Start of ID buffer-->'
IDbuffer   DS   512
      DB   '<--End of ID buffer            '

      ORG   BUFFER$ORG

BUFFER:      DB   76H
      DB   '<--Start buffer area'
      DS   476
      DB   'End of buffer-->'

BUFFER2:   DB   '<--Start buffer2 area'
      DS   476
      DB   'End of buffer2-->'

      DS   100H
STACK      DW   0H


;END
Wayne Parham
 
Posts: 240
Joined: March 18th, 2022, 3:01 pm

Re: IDE interface for Altair 8800c

Postby toml_12953 » December 1st, 2022, 11:11 pm

Wayne Parham wrote:That worked. I replaced the DJNZ instruction in MoreRD16 with the following:

DCR B
JNZ MoreRD16

The IDE identification struct defines a larger byte array for the name, so I'll probably increase that. But the function to iterate through the data buffer on the IDE device works. So sector reads should work too.

Next I'll find all other occurrences of DJNZ and change them to this two-instruction method. Probably do a little bit of cleanup too. Then I'll have IDE code that works on the Altair!

Once that's done, I'll be sure to post it here in this thread.


When converting from 8080 to Z-80 or vice-versa, don't forget about the conditional parity operations, if any. Here's an article about converting MITS 12K BASIC to run on the Z-80
https://www.dropbox.com/s/79ipadfgvy7qkj1/Z80%20MITS%2012K%20Extended%20BASIC%20Patches.pdf?raw=1
toml_12953
 
Posts: 297
Joined: June 7th, 2013, 12:54 pm

Re: IDE interface for Altair 8800c

Postby TronDD » December 4th, 2022, 5:39 pm

Happy to see someone working with one of these cards. I've had one for over a year and haven't been able to get it working.

I'm trying to get caught up to where you are.
TronDD
 
Posts: 40
Joined: November 20th, 2018, 8:51 pm

Re: IDE interface for Altair 8800c

Postby Wayne Parham » December 4th, 2022, 7:55 pm

Your timing is good then, 'cause here's a version of the IDEutil program that works. We're cookin' with gas now.

But just hotdogs. No steaks yet. The program runs and the menu works. You can see the identification string and that will tell you if the IDE card is working or not. That's a good start. But we're not 100% yet.

I'm pretty sure Monahan must've gotten his code working, so the reason it didn't work for us is probably partially due to hardware differences, but I think it might have also been environmental. Not sure yet. I do know that the DJNZ problem was a Z80/8080 thing, but I think Monahan used a library that contained a macro for that instruction which might have translated it to the DCR/JNZ instruction pair.

I also found a problem in the jump vector code used to handle the command menu. What's in his "myIDE" program just doesn't work for me. Not sure what kind of environment and/or hardware differences explain that, but I replaced his code with what's in this version of my "IDEutil" program - which is just a modified version of myIDE - and fixed the jump table that responds to the command menu.

Next I'll test each of the commands and validate their corresponding functions. I think a few of 'em are working but I think probably some of 'em aren't. And lastly, I want to add a menu item that reads the ID information, just like what now happens when the program is initially run. I think it will be more natural to simply initialize the IDE controller, and start the menu if the drive responds. Showing drive info is useful, but I think it would be more attractive to have a menu item for it than to have it automatically displayed at startup.

Once we're satisfied that this utility program works on the Altair, we can make a BIOS that supports the IDE interface. We might actually be able to do that already, since the code that reads the identification string uses the low-level interface code that would be used in the BIOS. So it's possible we already have everything we need to write a BIOS. But I think I'm going to improve IDEutil a little more first. Baby steps.

Code: Select all
;------------------------------------------------------------------------------
; Utility Program for IDE interface board
;   v2.9c   12/04/2022
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
;Build equates:
;------------------------------------------------------------------------------

FALSE      EQU   0
TRUE      EQU   NOT FALSE

CPM      EQU   TRUE   ;TRUE if using CPM, FALSE if loaded directly
DEBUG      EQU   TRUE   ;TRUE for error messages
VERBOSE      EQU   FALSE   ;TRUE for extended error messages
CPM$TRANSLATE   EQU   TRUE   ;Translate Trk, Sec, Head to CPM TRACK# & SEC#

;------------------------------------------------------------------------------
;Drive number equates:
;------------------------------------------------------------------------------

IDE0      EQU   0   ;Physical disk 0 drive number assignment
IDE1      EQU   1   ;Physical disk 1 drive number assignment

;------------------------------------------------------------------------------
;Console equates:
;------------------------------------------------------------------------------

CONI      EQU   10H   ;Console input port
CONO      EQU   11H   ;Console output port

;------------------------------------------------------------------------------
;CP/M equates:
;------------------------------------------------------------------------------

RDCON      EQU   1   ;For CP/M I/O
WRCON      EQU   2
RESET$DISK   EQU   0DH   ;Reset all CPM disks
PRINT      EQU   9
CONST      EQU   11   ;Console stat
BDOS      EQU   5

;------------------------------------------------------------------------------
;Display control equates:
;------------------------------------------------------------------------------

SCROLL      EQU   01H   ;Set scroll direction UP
LF      EQU   0AH
CR      EQU   0DH
BS      EQU   08H   ;Back space (required for sector display)
PERIOD      EQU   2EH
BELL      EQU   07H
SPACE      EQU   20H
QUIT      EQU   11H   ;Turns off any screen enhancements
NO$ENHANCEMENT   EQU   17H   ;Turns off whatever is on
FAST      EQU   10H   ;High speed scrool
TAB      EQU   09H   ;Tab across
ESC      EQU   1BH
CLEAR      EQU   1CH   ;Clear line (Use 80 spaces if not available)

;------------------------------------------------------------------------------
;IDE Interface equates:
;------------------------------------------------------------------------------
;Ports for 8255 chip. Change these to specify where your 8255 is addressed,
;The first three control which 8255 ports have the control signals,
;upper and lower data bytes.  The last one (IDEportCtrl), is for mode setting
;for the 8255 to configure its actual I/O ports (A,B & C). 
;
;Note most drives these days don't use the old Head,Track, Sector terminology.
;Instead we use "Logical Block Addressing" or LBA. This is what we use below.
;LBA treats the drive as one continous set of sectors, 0,1,2,3,... 3124,...etc.
;However as seen below we need to convert this LBA to heads,tracks and sectors
;to be compatible with CPM & MSDOS.
;
;NOTE: If you have only one drive/CF card, be sure it is in drive #0.
;The IDE hardware gets confused if there is only a drive in slot #1.
;------------------------------------------------------------------------------

IDEportA   EQU   030H   ;Lower 8 bits of IDE interface (8255)
IDEportB   EQU   031H   ;Upper 8 bits of IDE interface
IDEportC   EQU   032H   ;Control lines for IDE interface
IDEportCtrl   EQU   033H   ;8255 configuration port
IDEDrive   EQU   034H   ;Bit 0 - 0 for drive 0 and 1 for drive 1

READcfg8255   EQU   10010010b ;Set 8255 IDEportC to out, IDEportA/B input
WRITEcfg8255   EQU   10000000b ;Set all three 8255 ports to output mode

;------------------------------------------------------------------------------
;IDE control lines for use with IDEportC. 
;------------------------------------------------------------------------------

IDEa0line   EQU   01H   ;direct from 8255 to IDE interface
IDEa1line   EQU   02H   ;direct from 8255 to IDE interface
IDEa2line   EQU   04H   ;direct from 8255 to IDE interface
IDEcs0line   EQU   08H   ;inverter between 8255 and IDE interface
IDEcs1line   EQU   10H   ;inverter between 8255 and IDE interface
IDEwrline   EQU   20H   ;inverter between 8255 and IDE interface
IDErdline   EQU   40H   ;inverter between 8255 and IDE interface
IDErstline   EQU   80H   ;inverter between 8255 and IDE interface

;------------------------------------------------------------------------------
;Symbolic constants for the IDE drive registers
;------------------------------------------------------------------------------

REGdata      EQU   IDEcs0line
REGerr      EQU   IDEcs0line + IDEa0line
REGseccnt   EQU   IDEcs0line + IDEa1line
REGsector   EQU   IDEcs0line + IDEa1line + IDEa0line
REGcylinderLSB   EQU   IDEcs0line + IDEa2line
REGcylinderMSB   EQU   IDEcs0line + IDEa2line + IDEa0line
REGshd      EQU   IDEcs0line + IDEa2line + IDEa1line
REGcommand   EQU   IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGstatus   EQU   IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGcontrol   EQU   IDEcs1line + IDEa2line + IDEa1line
REGastatus   EQU   IDEcs1line + IDEa2line + IDEa1line + IDEa0line

;------------------------------------------------------------------------------
;IDE Command Constants.  These should never change.
;------------------------------------------------------------------------------

COMMANDrecal   EQU   10H
COMMANDread   EQU   20H
COMMANDwrite   EQU   30H
COMMANDinit   EQU   91H
COMMANDid   EQU   0ECH
COMMANDspindown   EQU   0E0H
COMMANDspinup   EQU   0E1H

;------------------------------------------------------------------------------
;IDE Status Register:
;------------------------------------------------------------------------------

;  bit 7: Busy   1=busy, 0=not busy
;  bit 6: Ready 1=ready for command, 0=not ready yet
;  bit 5: DF   1=fault occurred
;  bit 4: DSC   1=seek complete
;  bit 3: DRQ   1=data request ready, 0=not ready to xfer yet
;  bit 2: CORR   1=correctable error occurred
;  bit 1: IDX   vendor specific
;  bit 0: ERR   1=error occured

;------------------------------------------------------------------------------
;Disk equates:
;------------------------------------------------------------------------------

SEC$SIZE   EQU   512   ;Bytes per sector
MAXSEC      EQU   3DH   ;Sectors per track
MAXTRK      EQU   0FFH   ;CPM3 allows up to 8MG so 0-256 "tracks"
BUFFER$ORG   EQU   3000H   ;<----- Will place all sector data here

CPM$BOOT$COUNT   EQU   12   ;Allow up to 12 CPM sectors for CPMLDR
CPMLDR$ADDRESS   EQU   BUFFER$ORG

;------------------------------- INITIALIZATION -------------------------------   

   ORG   100H      ;<--- For CPM

begin:
   LXI   SP, STACK
   LXI     D, SIGN$ON   ;Print welcome message
   CALL   PSTRING
  IF VERBOSE
   LXI   D, SEL0MSG   ;Print select drive 0 message
   CALL   PSTRING
  ENDIF
   MVI   A, IDE0
   STA   @CURRENT$DRIVE
   OUT   IDEDrive   ;Select first drive
   
   CALL   CLEAR$ID$BUFFER   ;Clear ID Buffer
  IF VERBOSE
   LXI   D, INITDRIVE   ;Print initialization message
   CALL   PSTRING
  ENDIF
   CALL   IDEinit      ;Initialize the board and first drive
   JZ   INIT$OK      ;Continue on Zero
   
   LXI   D, INIT$0$ERROR   ;Non-zero is error, probably no drive
   CALL   PSTRING
   JMP   ABORT
   
INIT$OK:         ;Get drive 0 identification info         
   CALL   driveid
   JZ   INIT$OK1

   LXI   D, ID$ERROR   ;End program on error
   CALL   PSTRING
   JMP   ABORT

INIT$OK1:         ;Check sector count
   LXI     H, IDbuffer + 12
   MOV   A, M      ;(High Byte)
   ORA   A
   JNZ   INIT$OK2
   INX   H
   MOV   A, M      ;(Low Byte)
   ORA   A
   JNZ   INIT$OK2   ;Looks like we have a valid IDE drive
   
   LXI   D, BAD$DRIVE   ;Zero sectors means something's wrong
   CALL   PSTRING
   JMP   ABORT      ;No drive #0 so abort

INIT$OK2:         ;Print drive 0 info
   LXI   D, DRIVE0$INFO
   CALL   PSTRING
   LXI     D, msgmdl   ;Drive name   
   CALL   PSTRING
   LXI     H, IDbuffer + 54
   MVI   B, 20      ;Character count in words
   CALL   printSwap   ;Print [HL], [B] X 2 characters
   CALL   ZCRLF

   LXI     D, msgsn   ;Serial number
   CALL   PSTRING
   LXI     H, IDbuffer + 20
   MVI   B, 10      ;Character count in words
   CALL   printText
   CALL   ZCRLF

   LXI     D, msgrev   ;Firmware revision string
   CALL   PSTRING
   LXI     H, IDbuffer + 46
   MVI   B, 4      ;Character count in words
   CALL   printSwap
   CALL   ZCRLF

   LXI     D, msgcy   ;Drive specs (cyl/hd/sect)
   CALL   PSTRING
   LXI     H, IDbuffer + 2
   CALL   printparm
   LXI     D, msghd
   CALL   PSTRING
   LXI     H, IDbuffer + 6
   CALL   printparm
   LXI     D, msgsc
   CALL   PSTRING
   LXI     H, IDbuffer + 12
   CALL   printparm
   CALL   ZCRLF

INIT$OK3:         ;Move to second drive
   CALL   ZCRLF
  IF VERBOSE
   LXI   D, SEL1MSG   ;Print select drive 1 message
   CALL   PSTRING
  ENDIF
   MVI   A, IDE1      
   STA   @CURRENT$DRIVE
   OUT   IDEDrive   

   CALL   CLEAR$ID$BUFFER   ;Clear ID Buffer
   IF VERBOSE
   LXI   D, INITDRIVE   ;Print initialization message
   CALL   PSTRING
   ENDIF
   CALL   IDEinit      ;Initialize the second drive
   JZ   INIT$OK4

   LXI   D, INIT$1$ERROR   ;Non-zero is error, so print warning
   CALL   PSTRING
   JMP   MAINLOOP

INIT$OK4:         ;Get drive 1 identification info         
   CALL   driveid
   JZ   INIT$OK5

   LXI   D, ID$ERROR   ;On error, display message
   CALL   PSTRING
   JMP   MAINLOOP   ;Continue to main menu

INIT$OK5:         ;Check sector count
   LXI     H, IDbuffer + 12
   MOV   A, M      ;(High Byte)
   ORA   A
   JNZ   INIT$OK6
   INX   H
   MOV   A, M      ;(Low Byte)
   ORA   A
   JNZ   INIT$OK6   ;Looks like we have a valid IDE drive
BAD$DR1:   
   LXI   D, BAD$DRIVE   ;Zero sectors, so display error
   CALL   PSTRING
   JMP   MAINLOOP   ;Continue to main menu

INIT$OK6:         ;Print drive 1 info
   LXI   D, DRIVE1$INFO
   CALL   PSTRING
   LXI     D, msgmdl   ;Drive name   
   CALL   PSTRING
   LXI     H,IDbuffer + 54
   MVI   B, 20      ;Character count in words
   CALL   printSwap   ;Print [HL], [B] X 2 characters
   CALL   ZCRLF

   LXI     D, msgsn   ;Serial number
   CALL   PSTRING
   LXI     H, IDbuffer + 20
   MVI   B, 10      ;Character count in words
   CALL   printText
   CALL   ZCRLF

   LXI     D, msgrev   ;Firmware revision string
   CALL   PSTRING
   LXI     H, IDbuffer + 46
   MVI   B, 4      ;Character count in words
   CALL   printSwap
   CALL   ZCRLF

   LXI     D, msgcy   ;Drive specs (cyl/hd/sect)
   CALL   PSTRING
   LXI     H, IDbuffer + 2
   CALL   printparm
   LXI     D, msghd
   CALL   PSTRING
   LXI     H, IDbuffer + 6
   CALL   printparm
   LXI     D, msgsc
   CALL   PSTRING
   LXI     H, IDbuffer + 12
   CALL   printparm
   CALL   ZCRLF

INIT$DONE:         ;Cleanup and enter main menu
   CALL   CLEAR$ID$BUFFER
   CALL   IDEinit      ;Re-initialize drive 1
   MVI   A, IDE0
   STA   @CURRENT$DRIVE   ;Select drive 0
   OUT   IDEDrive
   CALL   IDEinit      ;Re-initialize drive 0
   LXI   H, 0
   SHLD   @SEC      ;Default to track 0 and sector 0
   SHLD   @TRK
   LXI   H, buffer   ;Set DMA address to buffer
   SHLD   @DMA
   JMP   MAINLOOP   ;Display Main Menu

;------------------------------------------------------------------------------   

TERMINATE:         ;End program from ESC command
   ZCO
ABORT:            ;Controlled termination
  IF CPM
   MVI   C, RESET$DISK   ;Reset all disks
   JMP   0FF00H      ;Reboot   CPM
  ELSE
   JMP   0F800H      ;Transfer control to Monitor ROM   
  ENDIF

;-------------------------------- MENU OPTIONS --------------------------------   

MAINLOOP:         ;Print main menu
   LDA   @CURRENT$DRIVE
   ORA   A
   JNZ   DRIVE$1$MENU
   LXI   D, DRIVE$0$MSG
   CALL   PSTRING
   JMP   Display0
DRIVE$1$MENU:
   LXI   D, DRIVE$1$MSG
   CALL   PSTRING
Display0:
   LDA   @DisplayFlag   ;Sector data display flag on or off
   ORA   A      ;NZ = on (Initially 0FFH so display on)
   JNZ     Display1
   LXI     D, CMD$STRING1   ;List command options (Turn display option on)
   JP   Display2
Display1:
   LXI     D, CMD$STRING2   ;List command options (Turn display option off)
Display2:
   CALL   PSTRING
   
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current track, sector, head
   
   LXI   D, Prompt   ;'>'
   CALL   PSTRING

;--------------------------------- USER INPUT ---------------------------------   
   
   CALL   GETCMD      ;Character Input
   CPI   ESC
   JZ   TERMINATE   ;End program if ESC
   CALL   ZCRLF

   SBI   'A'      ;Adjust to make 'A' = 0
   ADD   A

   LXI   H, TBL      ;Offset into vector table
   ADD   L
   MOV   L, A
   MOV   A, M
   INX   HL

   MOV   E, M      ;Get selected function address
   INX   H
   MOV   D, M
   XCHG

   PCHL         ;Jump to command function address
   
;------------------------------ COMMAND FUNCTIONS -----------------------------   

READ$SEC:         ;Read Sector @ LBA to the RAM buffer
   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA

   CALL   READSECTOR

   JZ   main1b      ;Z means the sector read was OK
   CALL   ZCRLF
   JMP   MAINLOOP

main1b:   LXI     D, msgrd   ;Sector read OK
   CALL   PSTRING

   LDA   @DisplayFlag   ;Do we have display flag on or off
   ORA   A      ;NZ = on
   JZ   MAINLOOP
   LXI   H, buffer   ;Point to buffer. Show sector data flag is on
   SHLD   @DMA
   CALL   HEXDUMP      ;Show sector data
   JMP   MAINLOOP

WRITE$SEC:         ;Write data in RAM buffer to sector @ LBA
   LXI     D, msgsure   ;Are you sure?
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   main2c
   CALL   ZCRLF

   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA

   CALL   WRITESECTOR

   JZ   main2b      ;Z means the sector write was OK
   CALL   ZCRLF
   JMP   MAINLOOP
main2b:   LXI     D, msgwr   ;Sector written OK
   CALL   PSTRING
main2c: JMP   MAINLOOP

SET$LBA:         ;Set the logical block address
   LXI     D, GET$LBA   
   CALL   PSTRING
   CALL   ghex32lba   ;Get CPM style Track & Sector, put in RAM
   JC   main3b      ;Ret C set if abort/error
   CALL   wrlba      ;Update LBA on drive
main3b:   CALL   ZCRLF
   JMP   MAINLOOP

NEXT$SECT:
   LDA   @SEC
   INR   A      
   CPI   MAXSEC-1
   JNC   RANGE$ERROR
   STA   @SEC
   CALL   wrlba      ;Update LBA on drive
   CALL   ZCRLF
   JMP   MAINLOOP

RANGE$ERROR:
   LXI     D, RANGE$MSG   
   CALL   PSTRING
   JMP   MAINLOOP
   
PREV$SEC:
   LDA   @SEC
   ORA   A
   JZ   RANGE$ERROR
   DCR   A
   STA   @SEC
   CALL   wrlba      ;Update LBA on drive
   CALL   ZCRLF
   JMP   MAINLOOP
   
POWER$UP:         ;Set the drive to spin up
   CALL   spinup
   JMP   MAINLOOP

POWER$DOWN:         ;Set the drive to spin down
   CALL   spindown
   JMP   MAINLOOP

DISPLAY:         ;Do we have display flag on or off
   LDA   @DisplayFlag   
   CMA         ;flip it
   STA   @DisplayFlag
   JMP   MAINLOOP   ;Update display and back to next menu command

SEQ$RD:            ;Do sequential reads
   CALL   SequentialReads
   JMP   MAINLOOP

DRIVE$0:
   MVI   A, IDE0      ;Select Drive 0:
   STA   @CURRENT$DRIVE
   OUT   IDEDrive
   LXI     D, SETA$MSG   
   CALL   PSTRING
   JMP   MAINLOOP

DRIVE$1:
   MVI   A, IDE1      ;Select Drive 1:
   STA   @CURRENT$DRIVE
   OUT   IDEDrive
   LXI     D,SETB$MSG   
   CALL   PSTRING
   JMP   MAINLOOP

RAMCLEAR:         ;Fill RAM buffer with 0's
   LXI   H, buffer   ;Point to buffer
   LXI   D, 512
   MVI   A, 0      ;Fill area with 0's
CLEAR1:   MOV   M, A
   INX   H
   DCX   D
   MOV   A, E
   ANA   D
   JNZ   CLEAR1
   LXI     D, FILL$MSG   
   CALL   PSTRING
   JMP   MAINLOOP

CPMBOOT:         ;Boot CPM from IDE system tracks -- if present
   MVI   A, 0      ;Load from track 0, sec 1, head 0 (always)
   STA   @SEC      ;Remember sectors are numbered +1
   XRA   A
   STA   @TRK+1
   STA   @TRK

   MVI   A, CPM$BOOT$COUNT ;Count of CPMLDR sectors  (12)
   STA   @SECTOR$COUNT
   LXI   H, CPMLDR$ADDRESS ;DMA address where the CPMLDR resides in RAM
   SHLD   @DMA

NextRCPM:
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current track, sector, head
   CALL   ZCRLF
   
   LHLD   @DMA
   CALL   READSECTOR   ;Read a sector
   SHLD   @DMA

   LDA   @SECTOR$COUNT
   DCR   A
   STA   @SECTOR$COUNT
   JZ   LOAD$DONE

   LHLD   @SEC
   INX   H
   SHLD   @SEC      ;Stay on track 0 in this special case
   JMP   NextRCPM

LOAD$DONE:
   MVI   E, REGstatus   ;Check the R/W status when done
   CALL   IDErd8D
   BIT   0, D
   JNZ   CPMLoadErr   ;Zero if no errors
   LXI   H, CPMLDR$ADDRESS
   MOV   A, M
   CPI   31H      ;EXPECT TO HAVE 31H @80H IE. LD SP,80H
   JNZ   CPMLoadErr1   ;Zero if no errors
   
   LXI   D, MOVE$REQUEST   ;Ask if we can move data to 100H
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, CPM$MOVE$CODE ;Need to move code out of the way.
   LXI   D, 0H
   LXI   B, (CPM$MOVE$CODE$END-CPM$MOVE$CODE)
   LDIR
   JMP   0H      ;Now jump here to move the CPMLDR (@3000H) to 100H
   
CPMLoadErr1:
   LXI   D, CPM$ERROR1   ;Drive data error
   CALL   PSTRING
   JMP   MAINLOOP
   
CPMLoadErr:
   LXI   D, CPM$ERROR   ;Drive Read Error
   CALL   PSTRING
   JMP   MAINLOOP

N$RD$SEC:         ;Read N sectors
   LXI   D, ReadN$MSG   ;No check for possible high RAM (CPM) overwrite
   CALL   PSTRING
   CALL   GETHEX
   JC   MAINLOOP   ;Abort if ESC (C flag set)
   
   STA   @SECTOR$COUNT   ;Store sector count
   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA

NextRSec:   
   LXI   D, ReadingN$MSG
   CALL   PSTRING
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current track, sector, head
   
   LHLD   @DMA
   CALL   READSECTOR
   SHLD   @DMA

   LDA   @SECTOR$COUNT
   DCR   A
   STA   @SECTOR$COUNT
   JZ   MAINLOOP
   
   LHLD   @SEC
   INX   H
   SHLD   @SEC   
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextRSec

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC
   LHLD   @TRK      ;Bump to next track
   INX   H
   SHLD   @TRK
   MOV   A, L      ;0-FFH tracks (only)
   JNZ   NextRSec
   
   LXI   D, AtEnd   ;Tell us we are at end of disk
   CALL   PSTRING
   JMP   MAINLOOP

N$WR$SEC:         ;Write N sectors
   LXI     D, msgsure   ;Are you sure?
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   main2c

   LXI   D, WriteN$MSG
   CALL   PSTRING
   CALL   GETHEX
   JC   MAINLOOP   ;Abort if ESC (C flag set)

   STA   @SECTOR$COUNT   ;Store sector count
   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA

NextWSec:   
   LXI   D, WritingN$MSG
   CALL   PSTRING
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current track, sector, head
   
   LHLD   @DMA      ;Actully, Sector/track values are already updated
   CALL   WRITESECTOR   ;in wrlba, but WRITESECTOR is used in multiple places.
   SHLD   @DMA      ;A repeat does no harm -- speed is not an issue here

   LDA   @SECTOR$COUNT
   DCR   A
   STA   @SECTOR$COUNT
   JZ   MAINLOOP
   
   LHLD   @SEC
   INX   H
   SHLD   @SEC   
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextWSec

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC
   LHLD   @TRK      ;Bump to next track
   INX   H
   SHLD   @TRK
   MOV   A, L      ;0-FFH tracks (only)
   ORA   A
   JNZ   NextWSec
   
   LXI   D, AtEnd   ;Tell us we are at end of disk
   CALL   PSTRING
   JMP   MAINLOOP

FORMAT:            ;Format (Fill sectors with E5)
   LXI   D, FORMAT$MSG
   CALL   PSTRING
   LXI     D, msgsure   ;Are you sure?
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP
   LXI   H, buffer   ;Fill buffer with E5's (512 of them)
   MVI   B, 0
Fill0:   MVI   A, 0E5H      ;<-- Sector fill character (E5 for CPM)
   MOV   M, A
   INX   H
   MOV   M, A
   INX   H
   DCR   B
   JNZ   Fill0
   CALL   ZCRLF

NEXT$FORMAT:
   LXI   H, buffer
   SHLD   @DMA
   CALL   WRITESECTOR   ;Will return error if there was one
   JZ   main9b      ;Z means the sector write was OK
   CALL   ZCRLF
   JMP   MAINLOOP
main9b:   CALL   ZEOL      ;Clear line cursor is on
   CALL   DISPLAYposition   ;Display actual current track, sector and head
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   WRNEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JZ   MAINLOOP
   CALL   ZCRLF
WRNEXTSEC1:
   LHLD   @SEC
   INX   H
   SHLD   @SEC      ;0 to MAXSEC CPM Sectors
   MOV   A, L
   CPI   MAXSEC
   JNZ   NEXT$FORMAT

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC
   LHLD   @TRK      ;Bump to next track
   INX   H
   SHLD   @TRK
   MOV   A, L      ;0-FFH tracks (only)
   CPI   MAXTRK
   JNZ   NEXT$FORMAT   

   LXI   D, FormatDone   ;Tell us we are all done.
   CALL   PSTRING
   JMP   MAINLOOP
            
BACKUP:            ;Backup the CPM partition to another area
   LXI   D, CopyMsg
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   @SEC
   SHLD   @SEC1
   SHLD   @SEC2      ;and on second partition
   SHLD   @TRK      ;and track 0
   SHLD   @TRK1
   LXI   H, MAXTRK+0200H+1
   SHLD   @TRK2

   CALL   ZCRLF
   CALL   ZCRLF
   
NextCopy1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, RBackup$MSG   ;for each track update display
   CALL   PSTRING
   LDA   @TRK1+1      ;High TRK byte
   CALL   PHEX
   LDA   @TRK1      ;Low TRK byte
   CALL   PHEX
   LXI   D, WBackup$MSG
   CALL   PSTRING
   LDA   @TRK2+1      ;High TRK byte
   CALL   PHEX
   LDA   @TRK2      ;Low TRK byte
   CALL   PHEX
   LXI   D, H$Msg
   CALL   PSTRING

NextCopy:   
   LDA   @SEC1
   STA   @SEC
   LHLD   @TRK1
   SHLD   @TRK
   CALL   wrlba      ;Update LBA on "1st" drive

   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA
   CALL   READSECTOR   ;Get sector data to buffer
   
   LDA   @SEC2
   STA   @SEC
   LHLD   @TRK2
   SHLD   @TRK
   CALL   wrlba      ;Update LBA on "2nd" drive
   
   LXI   H,buffer   ;Point to buffer
   SHLD   @DMA
   CALL   WRITESECTOR   ;Write buffer data to sector
   
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   BKNEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JZ   MAINLOOP

BKNEXTSEC1:
   LHLD   @SEC
   INX   H
   SHLD   @SEC1
   SHLD   @SEC2   
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextCopy

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC1
   SHLD   @SEC2
   
   LHLD   @TRK1      ;Bump to next track
   INX   H
   SHLD   @TRK1
   
   LHLD   @TRK2      ;Bump to next track
   INX   H
   SHLD   @TRK2
   
   LHLD   @TRK1      ;Check if we are done
   MOV   A, L      ;0-FFH tracks (only)
   CPI   MAXTRK
   JNZ   NextCopy1
   
   LXI   D, BackupDone   ;Tell us we are all done.
   CALL   PSTRING
   JMP   MAINLOOP

RESTORE:         ;Restore disk from backup partition
   LXI   D, RestoreMsg
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   @SEC
   SHLD   @SEC1
   SHLD   @SEC2      ;and on second partition
   SHLD   @TRK      ;and track 0
   SHLD   @TRK1
   LXI   H, MAXTRK+0200H+1
   SHLD   @TRK2

   CALL   ZCRLF
   CALL   ZCRLF
   
NextRestore1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, RBackup$MSG   ;for each track update display
   CALL   PSTRING
   LDA   @TRK2+1      ;High TRK byte
   CALL   PHEX
   LDA   @TRK2      ;Low TRK byte
   CALL   PHEX
   LXI   D, WBackup$MSG
   CALL   PSTRING
   LDA   @TRK1+1      ;High TRK byte
   CALL   PHEX
   LDA   @TRK1      ;Low TRK byte
   CALL   PHEX
   LXI   D,H$Msg
   CALL   PSTRING

NextRestore:   
   LDA   @SEC2      ;Point to backup partition
   STA   @SEC
   LHLD   @TRK2
   SHLD   @TRK
   CALL   wrlba      ;Update LBA on "1st" drive

   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA
   CALL   READSECTOR   ;Get sector data to buffer
   
   LDA   @SEC1
   STA   @SEC
   LHLD   @TRK1
   SHLD   @TRK
   CALL   wrlba      ;Update LBA on "2nd" drive
   
   LXI   H,buffer   ;Point to buffer
   SHLD   @DMA
   CALL   WRITESECTOR   ;Write buffer data to sector
   
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   RESNEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JZ   MAINLOOP

RESNEXTSEC1:
   LHLD   @SEC
   INX   H
   SHLD   @SEC1
   SHLD   @SEC2   
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextRestore

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC1
   SHLD   @SEC2
   
   LHLD   @TRK1      ;Bump to next track
   INX   H
   SHLD   @TRK1
   
   LHLD   @TRK2      ;Bump to next track
   INX   H
   SHLD   @TRK2
   
   LHLD   @TRK2      ;Check if we are done
   MOV   A, L      ;0-FFH tracks (only)
   CPI   MAXTRK
   JNZ   NextRestore1
   
   LXI   D, RestoreDone   ;Inform user restore complete
   CALL   PSTRING
   JMP   MAINLOOP

ERROR:   LXI     D, msgErr   ;CMD error msg
   CALL   PSTRING
   JMP   MAINLOOP

COPY$AB:         ;Copy drive 0 to drive 1
   LXI   D, DiskCopyMsg
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   @SEC
   SHLD   @TRK      ;and track 0
   
   CALL   ZCRLF
   CALL   ZCRLF
   
NextDCopy1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, CopyTrk$MSG   ;for each track update display
   CALL   PSTRING
   LDA   @TRK+1      ;High TRK byte
   CALL   PHEX
   LDA   @TRK      ;Low TRK byte
   CALL   PHEX
   LXI   D, H$Msg
   CALL   PSTRING

NextDCopy:   
   MVI   A, IDE0      ;Login drive 0
   STA   @CURRENT$DRIVE
   OUT   IDEDrive

   CALL   wrlba      ;Update LBA on drive 0

   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA
   CALL   READSECTOR   ;Get sector data from drive 0 to buffer
   
   MVI   A, IDE1      ;Login drive 1
   STA   @CURRENT$DRIVE
   OUT   IDEDrive

   CALL   wrlba      ;Update LBA on drive 1
   
   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA
   CALL   WRITESECTOR   ;Write buffer data to sector on drive 1
   
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM says something is there
   JNZ   BK$D$NEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JNZ   BK$D$NEXTSEC1
   MVI   A,IDE0      ;Login drive 1
   STA   @CURRENT$DRIVE
   OUT   IDEDrive
   JMP   MAINLOOP

BK$D$NEXTSEC1:
   LHLD   @SEC
   INX   H
   SHLD   @SEC
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextDCopy

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC
   
   LHLD   @TRK      ;Bump to next track
   INX   H
   SHLD   @TRK
            ;Check if we are done
   MOV   A, L      ;0-FFH tracks (only)
   CPI   MAXTRK
   JNZ   NextDCopy1
   
   LXI   D, CopyDone   ;Inform user copy complete
   CALL   PSTRING
   MVI   A, IDE0      ;Login drive 0
   STA   @CURRENT$DRIVE
   OUT   IDEDrive
   JMP   MAINLOOP

VERIFY$AB:         ;Verify Drive 0 = 1
   LXI   D, DiskVerifyMsg
   CALL   PSTRING
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   @SEC
   SHLD   @TRK      ;and track 0
   
   CALL   ZCRLF
   CALL   ZCRLF
   
NextVCopy1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, VerifyTrk$MSG ;for each track update display
   CALL   PSTRING
   LDA   @TRK+1      ;High TRK byte
   CALL   PHEX
   LDA   @TRK      ;Low TRK byte
   CALL   PHEX
   LXI   D, H$Msg
   CALL   PSTRING

NextVCopy:   
   MVI   A, IDE0      ;Login drive 0
   STA   @CURRENT$DRIVE
   OUT   IDEDrive

   CALL   wrlba      ;Update LBA on 0 drive

   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA
   CALL   READSECTOR   ;Get sector data from buffer 0 drive
   
   MVI   A, IDE1      ;Login drive 1
   STA   @CURRENT$DRIVE
   OUT   IDEDrive

   CALL   wrlba      ;Update LBA on 1 drive
   
   LXI   H, buffer2   ;Point to buffer2
   SHLD   @DMA
   CALL   READSECTOR   ;Read buffer data from sector of drive 1
   
   LXI   BC, 512      ;Now check both buffers are identical
   LXI   H, buffer
   LXI   D, buffer2
NEXTV:   LDAX   D
   CMP   M      ;Is [DE] = [HL]?
   JNZ   COMPARE$ERROR
   INX   H
   INX   D
   DCX   B
   MOV   A,C
   ANA   B
   JZ   VERIFY$OK
   JMP   NEXTV

COMPARE$ERROR:
   LXI   D, VERIFY$ERR   ;Indicate an error
   CALL   PSTRING
   LDA   @TRK+1      ;High TRK byte
   CALL   PHEX
   LDA   @TRK      ;Low TRK byte
   CALL   PHEX
   LXI   D, SEC$Msg
   CALL   PSTRING
   LDA   @SEC      ;Sector byte
   CALL   PHEX
   LXI   D, H$Msg
   CALL   PSTRING
   JMP   VER$OK1
   
VERIFY$OK:
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM says something is there
   JNZ   BK$V$NEXTSEC1
   CALL   ZCI      ;Flush character
VER$OK1:
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JNZ   BK$V$NEXTSEC1
   MVI   A,IDE0      ;Login drive 0
   STA   @CURRENT$DRIVE
   OUT   IDEDrive
   JMP   MAINLOOP

BK$V$NEXTSEC1:
   LHLD   @SEC
   INX   H
   SHLD   @SEC
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextVCopy

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC
   
   LHLD   @TRK      ;Bump to next track
   INX   H
   SHLD   @TRK
            ;Check if we are done
   MOV   A, L      ;0-FFH tracks (only)
   CPI   MAXTRK
   JNZ   NextVCopy1
   
   LXI   D, VerifyDone   ;Tell us we are all done.
   CALL   PSTRING
   MVI   A, IDE0      ;Login drive 0
   STA   @CURRENT$DRIVE
   OUT   IDEDrive
   JMP   MAINLOOP

;----------------------------- SUPPORT FUNCTIONS ------------------------------   
            
driveid:CALL   IDEwaitnotbusy   ;Retrieve drive info
   RC
   MVI   D, COMMANDid
   MVI   E, REGcommand
   CALL   IDEwr8D      ;Issue the ID command
  IF VERBOSE
   LXI   D, READING$ID
   CALL   PSTRING
   LXI   D, DISKSTATUS   ;Print status message
   CALL   PSTRING
  ENDIF
   MVI   E, REGstatus   ;Get status after ID command
   CALL   IDErd8D      ;Check Status (info in [D])
  IF VERBOSE
   MOV   A, D
   CALL   PHEX      ;Print status
   CALL   ZPERCRLF
  ENDIF
   CALL   IDEwaitdrq   ;Wait for Busy=0, DRQ=1
   JC   SHOWerrors
  IF VERBOSE
   LXI   D, GETTING$ID
   CALL   PSTRING
  ENDIF
   MVI   B, 0      ;256 words
   LXI     H, IDbuffer   ;Store data here
   CALL   MoreRD16   ;Get 256 words of data from REGdata port to [HL]
   RET

spinup:            ;Start the drive
   MVI   D, COMMANDspinup
spup2:   MVI   E, REGcommand
   CALL   IDEwr8D
   CALL   IDEwaitnotbusy
   JC   SHOWerrors
   ORA   A      ;Clear carry
   RET
            
spindown:         ;Tell the drive to spin down
   CALL   IDEwaitnotbusy
   JC   SHOWerrors
   MVI   D,COMMANDspindown
   JMP   spup2

SequentialReads:      ;Sequentially read sectors from current position
   CALL   IDEwaitnotbusy
   JC   SHOWerrors
   CALL   ZCRLF
NEXTSEC:
   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA

   CALL   READSECTOR   ;Errors will show in READSECTOR
   JZ   SEQOK
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC      ;Abort if ESC
   RZ
   
SEQOK:   CALL   ZEOL      ;Clear line cursor is on
   CALL   DISPLAYposition   ;Display current track, sector and head

   LXI   H, buffer   ;Point to buffer
   SHLD   @DMA

   LDA   @DisplayFlag   ;Do we have display flag on or off
   ORA   A      ;NZ = on
   CNZ   HEXDUMP
   CALL   ZCRLF
   CALL   ZCRLF
   CALL   ZCRLF

   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   NEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   RZ
   CALL   ZCRLF
NEXTSEC1:
   LHLD   @SEC
   INX   H
   SHLD   @SEC   
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NEXTSEC

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   @SEC
   LHLD   @TRK      ;Bump to next track
   INX   H
   SHLD   @TRK
   JMP   NEXTSEC      ;Note will go to last sec on disk unless stopped

DISPLAYposition:      ;Display current track, sector & head position
   LXI     D, msgCPMTRK   ;Display in LBA format
   CALL   PSTRING      ;---- CPM FORMAT ----
   LDA   @TRK+1      ;High TRK byte
   CALL   PHEX
   LDA   @TRK      ;Low TRK byte
   CALL   PHEX
      
   LXI     D, msgCPMSEC
   CALL   PSTRING      ;SEC = (16 bits)
   LDA   @SEC+1      ;High Sec
   CALL   PHEX
   LDA   @SEC      ;Low sec
   CALL   PHEX
            ;---- LBA FORMAT ----
   LXI     D, msgLBA
   CALL   PSTRING      ;LBA = 00 ("Heads" = 0 for these drives)
   LDA   @DRIVE$TRK+1   ;High "cylinder" byte
   CALL   PHEX
   LDA   @DRIVE$TRK   ;Low "cylinder" byte
   CALL   PHEX   
   LDA   @DRIVE$SEC
   CALL   PHEX
   LXI     D, MSGBracket
   CALL   PSTRING      
   RET

printText:         ;Print text up to [B] (16-bit word) byte-pairs
   MOV   C, M      ;Text is contiguous byte array
   CALL   ZCO   
   INX   H
   MOV   C, M
   CALL   ZCO   
   INX   H
   DCR   B
   JNZ   printText
   RET

printSwap:         ;Print text up to [B] (16-bit word) byte-pairs   
   INX   H      ;Swap byte pairs - low byte, high byte
   MOV   C, M
   CALL   ZCO   
   DCX   H
   MOV   C, M
   CALL   ZCO
   INX   H
   INX   H
   DCR   B
   JNZ   printSwap
   RET

ZCRLF:
   PUSH   PSW
   MVI   C, CR
   CALL   ZCO
   MVI   C, LF
   CALL   ZCO
   POP   PSW
   RET

ZPERCRLF:
   PUSH   PSW
   MVI   C, PERIOD
   CALL   ZCO
   MVI   C, CR
   CALL   ZCO
   MVI   C, LF
   CALL   ZCO
   POP   PSW
   RET

ZEOL:            ;CR and clear current line
   MVI   C, CR
   CALL   ZCO
   MVI   C, CLEAR   ;Use 80 spaces if necessary
   CALL   ZCO
   RET

ZCSTS:
  IF CPM
   PUSH   B
   PUSH   D
   PUSH   H
   MVI   C, CONST
   CALL   BDOS      ;Returns with 1 in [A] if character at keyboard
   POP   H
   POP   D
   POP   B
   CPI   1
   RET
  ELSE   
   IN   COMI      ;Get Character in [A]
   ANI   02H
   RZ
   MVI   A, 01H
   ORA   A
   RET
  ENDIF

ZCO:            ;Write character that is in [C]
  IF CPM
   PUSH   PSW
   PUSH   B
   PUSH   D
   PUSH   H
   MOV   E, C
   MVI   C, WRCON
   CALL   BDOS
   POP   H
   POP   D
   POP   B
   POP   PSW
   RET
  ELSE   
   PUSH   PSW   
ZCO1:   IN      CONI      ;Show Character
   ANI   04H
   JZ   ZCO1
   MOV   A, C
   OUT   CONO
   POP   PSW
   RET
  ENDIF

ZCI:            ;Return keyboard character in [A]
  IF CPM
   PUSH   B
   PUSH   D
   PUSH   H
   MVI   C, RDCON
   CALL   BDOS
   POP   H
   POP   D
   POP   B
   RET
  ELSE
ZCI1:   IN   CONI      ;Get Character in [A]
   ANI   02H
   JZ   ZCI1
   IN   CONO
   RET
  ENDIF

;------------------------------------------------------------------------------   
;Print a string in [DE] up to '$'
;------------------------------------------------------------------------------   

PSTRING:
  IF CPM
   MVI   C, PRINT
   JMP   BDOS      ;PRINT MESSAGE
  ELSE
   PUSH   B
   PUSH   D
   PUSH   H
   XCHG
PSTRX:   MOV   A, M
   CPI   '$'
   JZ   DONEP
   MOV   C, A
   CALL   ZCO
   INX   H
   JMP   PSTRX
DONEP:   POP   H
   POP   D
   POP   B
   RET
  ENDIF

SHOWerrors:
  IF NOT DEBUG
   ORA   A      ;Set NZ flag
   STC         ;Set Carry Flag
   RET
  ELSE
   CALL   ZCRLF
   MVI   E, REGstatus   ;Get status in status register
   CALL   IDErd8D
   MOV   A, D
   ANI   1H
   JNZ   MoreError   ;Go to  REGerr register for more info
            ;All OK if 01000000
   PUSH   PSW      ;Save for return below
   ANI   80H
   JZ   NOT7
   LXI   D, DRIVE$BUSY   ;Drive Busy (bit 7) stuck high
   CALL   PSTRING
   JMP   DONEERR
NOT7:   ANI   40H
   JNZ   NOT6
   LXI   D, DRIVE$NOT$READY ;Drive Not Ready (bit 6) stuck low
   CALL   PSTRING
   JMP   DONEERR
NOT6:   ANI   20H
   JNZ   NOT5
   LXI   D, DRIVE$WR$FAULT ;Drive write fault
   CALL   PSTRING
   JMP   DONEERR
NOT5   LXI   D, UNKNOWN$ERROR
   CALL   PSTRING
   JMP   DONEERR

MoreError:         ;Bit 0 of the status register indicates problem
   MVI   E, REGerr   ;Get error code in REGerr
   CALL   IDErd8D
   MOV   A, D
   PUSH   PSW

   ANI   10H
   JZ   NOTE4
   LXI   D, SEC$NOT$FOUND
   CALL   PSTRING
   JMP   DONEERR

NOTE4:   ANI   80H
   JZ   NOTE7
   LXI   D, BAD$BLOCK
   CALL   PSTRING
   JMP   DONEERR
NOTE7:   ANI   40H
   JZ   NOTE6
   LXI   D, UNRECOVER$ERR
   CALL   PSTRING
   JMP   DONEERR
NOTE6:   ANI   4H
   JZ   NOTE2
   LXI   D, INVALID$CMD
   CALL   PSTRING
   JMP   DONEERR
NOTE2:   ANI   2H
   JZ   NOTE1
   LXI   D, TRK0$ERR
   CALL   PSTRING
   JMP   DONEERR
NOTE1:   LXI   D, UNKNOWN$ERROR1
   CALL   PSTRING
   JMP   DONEERR

DONEERR:POP   PSW
   PUSH   PSW
   CALL   ZBITS
   CALL   ZCRLF
   POP   PSW
   ORA   A      ;Set Z flag
   STC         ;Set Carry flag
   RET
   ENDIF

;------------------------------------------------------------------------------   
;Print a 16-bit number in RAM located @ [HL], low-byte first for Drive ID
;------------------------------------------------------------------------------   

printparm:
   INX   H   ;Index to high byte first
   MOV   A, M
   CALL   PHEX
   DCX   H   ;Now low byte
   MOV   A, M
   CALL   PHEX
   RET

;------------------------------------------------------------------------------   
;Print an 8 bit number located in [A]
;------------------------------------------------------------------------------   

PHEX:   PUSH   PSW
   PUSH   B
   PUSH   PSW
   RRC
   RRC
   RRC
   RRC
   CALL   ZCONV
   POP   PSW
   CALL   ZCONV
   POP   B
   POP   PSW
   RET

ZCONV:   ANI   0FH      ;HEX to ASCII and print it
   ADI   90H
   DAA
   ACI   40H
   DAA
   MOV   C, A
   CALL   ZCO
   RET

;------------------------------------------------------------------------------   
;Display binary in [A]
;------------------------------------------------------------------------------   

ZBITS:   PUSH   PSW
   PUSH   B
   PUSH   D
   MOV   E, A      
   MVI   B, 8
BQ2:   DB   0CBH, 23H   
   SLAR   E
   MVI   A, 18H
   ADC   A
   MOV   C, A
   CALL   ZCO
   DCR   B
   JNZ   BQ2
   POP   D
   POP   B
   POP   PSW
   RET

ghex32lba:         ;Convert CPM Track & Sector to LBA format
   LXI     D,ENTER$SECL   ;Enter sector number
   CALL   PSTRING
   CALL   GETHEX      ;Get 2 HEX digits
   RC
   STA   @SEC
   CALL   ZCRLF

   LXI   D, ENTER$TRKH   ;Enter high byte track number
   CALL   PSTRING
   CALL   GETHEX      ;Get 2 HEX digits
   RC
   STA   @TRK+1
   CALL   ZCRLF

   LXI     D, ENTER$TRKL   ;Enter low byte track number
   CALL   PSTRING
   CALL   GETHEX      ;Get 2 more HEX digits
   RC
   STA   @TRK
   CALL   ZCRLF
   XRA   A
   ORA   A      ;To return NC
   RET

;------------------------------------------------------------------------------   
;Get a HEX character from the keyboard and echo it
;------------------------------------------------------------------------------   

GETHEX:
   CALL   GETCMD      ;Get character
   CPI   ESC
   JZ   HEXABORT
   CPI   '/'      ;check 0-9, A-F
   JC   HEXABORT
   CPI   'F'+1
   JNC   HEXABORT
   CALL   ASBIN      ;Convert to binary
   RLC         ;Shift to high nibble
   RLC
   RLC
   RLC
   MOV   B, A      ;Store it
   CALL   GETCMD      ;Get 2nd character from keyboard & ECHO
   CPI   ESC
   JZ   HEXABORT
   CPI   '/'      ;check 0-9, A-F
   JC   HEXABORT
   CPI   'F'+1
   JNC   HEXABORT
   CALL   ASBIN      ;Convert to binary
   ORA   B      ;add in the first digit
   ORA   A      ;To return NC
   RET
HEXABORT:
   STC         ;Set Carry flag
   RET

;------------------------------------------------------------------------------   
;Get a character from the keyboard, convert to uppercase and echo it
;------------------------------------------------------------------------------   

GETCMD:   CALL   ZCI      ;Get character
   CALL   UPPER
   CPI   ESC
   RZ         ;Don't echo an ESC
  IF NOT CPM
   PUSH   PSW      ;Save state of registers
   PUSH   B
    MOV   C, A
   CALL   ZCO      ;Echo it
   POP   B
   POP   PSW      ;Retrieve original state
  ENDIF
   RET

;------------------------------------------------------------------------------   
;Convert lowercase to uppercase
;------------------------------------------------------------------------------   

UPPER:   CPI   'a'      ;Must be >= lowercase a
   RC         ;else return as-is
   CPI   'z'+1      ;Must be <= lowercase z
   RNC         ;else return as-is
   SUI   'a'-'A'      ;Subtract lowercase bias
   RET

ASBIN:   SUI   30H       ;ASCII to binary conversion
   CPI   0AH
   RM
   SUI   07H
   RET

;------------------------------------------------------------------------------   
;Print a hexdump of the data in the 512 byte buffer starting at [HL]
;------------------------------------------------------------------------------   

HEXDUMP:
   PUSH   PSW      ;Save everything
   PUSH   B
   PUSH   D         
   PUSH   H
   
   CALL   ZCRLF      ;CR/LF first
   MVI   D, 32      ;Print 32 lines total
   MVI   B, 16      ;16 characters across
   SHLD   @StartLineHex   ;Save buffer location for ASCII display below
   LXI   H, 0
   SHLD   @BYTE$COUNT
   
SF172:   CALL   ZCRLF
   LHLD   @BYTE$COUNT
   MOV   A, H
   CALL   PHEX      ;Print byte count in sector
   MOV   A, L
   CALL   PHEX      
   PUSH   D
   LXI   D, 16
   DAD   D
   POP   D
   SHLD   @BYTE$COUNT   ;Store for next time
   CALL   BLANK
   LHLD   @StartLineHex
   SHLD   @StartLineASCII   ;Store for ASCII display below

SF175:   MOV   A, M
   CALL   LBYTE      ;Display [A] on CRT/LCD
   INX   H
   DCR   B
   JNZ   SF175
   SHLD   @StartLineHex   ;Save for next line later
   CALL   ShowAscii   ;Now translate to ASCII and display
   MVI   B, 16      ;16 characters across for next line
   DCR   D
   JNZ   SF172      ;Have we done all 32 lines

   CALL   ZCRLF
   POP   H      ;Get back original registers
   POP   D
   POP   B
   POP   PSW
   RET
   
ShowAscii:         ;Show as ASCII info
   LHLD   @StartLineASCII
   MVI   B, 16      ;16 ASCII characters across
XF172:   CALL   BLANK      ;Send a space character
   CALL   BLANK
XF175:   MOV   A, M
   ANI   7FH
   CPI   ' '       ;Filter out control characters
   JNC   XT33
XT22:   MVI   A, '.'
XT33:   CPI   07CH
   JNC   XT22
   MOV   C, A      ;Setup to send
   PUSH   B
   CALL   ZCO
   POP   B
   INX   H      ;Next position in buffer
   DCR   B
   JNZ   XF175
   RET

BLANK:   PUSH   B
   PUSH   H
   MVI   C, ' '
   CALL   ZCO
   POP   H
   POP   B
   RET

LBYTE:   PUSH   PSW
   RRC
   RRC
   RRC
   RRC
   CALL   SF598
   POP   PSW

SF598:   CALL   ZCONV
   RET

;------------------------------------------------------------------------------   
;IDE Drive BIOS Routines written in a format that can be used directly with CPM
;------------------------------------------------------------------------------   

IDEinit:         ;Initialize the 8255 and drive then do a hard reset
  IF VERBOSE
   LXI   D, INITDRIVE
   CALL   PSTRING
  ENDIF
   MVI   A, READcfg8255   ;Config 8255 chip (10010010B)
   OUT   IDEportCtrl   ;for READ mode
            
   MVI   A, IDErstline   ;Hard reset the disk drive
   OUT   IDEportC   ;Some CF cards are sensitive to reset pulse width
   MVI   B, 20H      ;Symptom is incorrect data back from a sector read

ResetDelay:
   DCR   B
   JNZ   ResetDelay   ;Delay (reset pulse width)

   XRA   A
   OUT   IDEportC   ;No control lines asserted (just bit 7 of port C)
   CALL   DELAY$SHORT   ;Short Delay

   MVI   D,11100000b   ;Data for IDE SDH reg (512byte, LBA, single drive, hd 0)
            ;For Trk, Sec, Head (non LBA) use 10100000

   MVI   E,REGshd   ;00001110,(0EH) for CS0,A2,A1, 
   CALL   IDEwr8D      ;Write byte to select the MASTER device

   MVI   B, 02H      ;Delay time for hard disks to get up to speed (2s)

WaitInit:
  IF VERBOSE
   LXI   D, DISKSTATUS   ;Print initialization status message
   CALL   PSTRING
  ENDIF
   MVI   E, REGstatus   ;Get status after initilization
   CALL   IDErd8D      ;Check Status (info in [D])
   MOV   A, D
  IF VERBOSE
   CALL   PHEX      ;Print drive initialization status
   CALL   ZPERCRLF
  ENDIF
   ANI   80H
   RZ         ;Return. We'll check for errors when we get back
   MVI   A, 2
   CALL   DELAY$LONG   ;Long delay, drive has to get up to speed
   DCR   B
   JNZ   WaitInit
   XRA   A
   DCR   A
   RET         ;Return NZ. We'll check for errors when we get back
   
DELAY$LONG:         ;Long delay (Seconds)
   STA   @DELAYStore
   PUSH   B
   LXI   B, 0FFFFH
DELAY2:   LDA   @DELAYStore
DELAY1:   DCR   A
   JNZ   DELAY1
   DCX   B
   MOV   A, C
   ORA   B
   JNZ   DELAY2
   POP   B
   RET

DELAY$SHORT:         ;Short delay (32ms)
   MVI   A, 40
DELAY3:   MVI   B, 0
M0:   DCR   B
   JNZ   M0
   DCR   A
   JNZ     DELAY3
   RET

;------------------------------------------------------------------------------   
;Sector Read
;------------------------------------------------------------------------------   

READSECTOR:         ;Read a sector, specified by the 3 bytes in LBA
            ;Z on success, NZ call error routine if problem

   CALL   wrlba      ;Tell which sector we want to read from.
            ;Translate first in case of an error, otherewise
            ;we will get stuck on bad sector
 
   CALL   IDEwaitnotbusy   ;Make sure drive is ready
   JC   SHOWerrors   ;Returned with NZ set if error

   MVI   D, COMMANDread
   MVI   E, REGcommand
   CALL   IDEwr8D      ;Send sec read command to drive.
   CALL   IDEwaitdrq   ;Wait until it's got the data
   JC   SHOWerrors
      
   LHLD     @DMA      ;DMA address
   MVI   B, 0      ;Read 512 bytes to [HL]

MoreRD16:
   MVI   A, REGdata   ;REG register address
   OUT   IDEportC   

   ORI   IDErdline   ;08H+40H, Pulse RD line
   OUT   IDEportC   

   IN   IDEportA   ;Read the lower byte first
   MOV   M, A
   INX   H
   IN   IDEportB   ;Then read the upper byte
   MOV   M, A
   INX   H
   
   MVI   A, REGdata   ;Deassert RD line
   OUT   IDEportC
   DCR   B
   JNZ   MoreRD16

   MVI   E, REGstatus
   CALL   IDErd8D
   MOV   A, D
   ANI   1H
   CNZ   SHOWerrors   ;If error display status
   RET

;------------------------------------------------------------------------------   
;Sector Write
;------------------------------------------------------------------------------   

WRITESECTOR:         ;Write a sector, specified by the 3 bytes in LBA
            ;Z on success, NZ to error routine if problem

   CALL   wrlba      ;Tell which sector we want to read from.
            ;Translate first in case of an error, otherewise
            ;we will get stuck on bad sector

   CALL   IDEwaitnotbusy   ;Make sure drive is ready
   JC   SHOWerrors

   MVI   D, COMMANDwrite
   MVI   E, REGcommand
   CALL   IDEwr8D      ;Tell drive to write a sector
   CALL   IDEwaitdrq   ;Wait unit it wants the data
   JC   SHOWerrors

   LHLD    @DMA
   MVI   B, 0

   MVI   A, WRITEcfg8255
   OUT   IDEportCtrl
   
WRSEC1:   MOV   A, M
   INX   H
   OUT   IDEportA   ;Write the lower byte first
   MOV   A, M
   INX   H
   OUT   IDEportB   ;Then high byte on B

   MVI   A, REGdata
   PUSH   PSW
   OUT   IDEportC   ;Send write command
   ORI   IDEwrline   ;Send WR pulse
   OUT   IDEportC
   POP   PSW
   OUT   IDEportC
   DCR   B
   JNZ   WRSEC1
   
   MVI   A, READcfg8255   ;Set 8255 back to read mode
   OUT   IDEportCtrl   

   MVI   E, REGstatus
   CALL   IDErd8D
   MOV   A, D
   ANI   1H
   CNZ   SHOWerrors   ;If error display status
   RET

;------------------------------------------------------------------------------   
;Write Logical Block Address (LBA) mode
;------------------------------------------------------------------------------   

wrlba:
   LDA   @SEC      ;LBA mode low sectors go directly
   INR   A      ;Sectors are numbered 1 -- MAXSEC
   STA   @DRIVE$SEC   ;For Diagnostic Display Only
   MOV   D, A
   MVI   E, REGsector   ;Send info to drive
   CALL   IDEwr8D
            
   LHLD   @TRK      
   MOV   A, L
   STA   @DRIVE$TRK
   MOV   D, L      ;Send Low TRK#
   MVI   E, REGcylinderLSB
   CALL   IDEwr8D

   MOV   A, H
   STA   @DRIVE$TRK+1
   MOV   D, H      ;Send High TRK#
   MVI   E, REGcylinderMSB
   CALL   IDEwr8D

   MVI   D, 1      ;For now, one sector at a time
   MVI   E, REGseccnt
   CALL   IDEwr8D
   RET

;------------------------------------------------------------------------------   
;Wait for drive to come ready
;------------------------------------------------------------------------------   

IDEwaitnotbusy:         ;Drive READY if status = 01000000
   MVI   B, 0FFH
   MVI   A, 0FFH      ;Delay must be above 80H, longer for slow drives
   STA   @DELAYStore

MoreWait:
   MVI   E, REGstatus   ;Wait for RDY bit to be set
   CALL   IDErd8D
   MOV   A, D
   ANI   11000000B
   XRI   01000000B
   JZ   DoneNotbusy
   DCR   B   
   JNZ   MoreWait
   LDA   @DELAYStore   ;Check timeout delay
   DCR   A
   STA   @DELAYStore
   JNZ   MoreWait

   STC         ;Set carry to indicate an error
   ret
DoneNotBusy:
   ORA   A      ;Clear carry it indicate no error
   RET

;------------------------------------------------------------------------------   
;Wait for drive to assert data request (DRQ) line ready
;------------------------------------------------------------------------------   

IDEwaitdrq:
   MVI   B, 0FFH
   MVI   A, 0FFH      ;Delay must be above 80H, longer for slow drives
   STA   @DELAYStore

MoreDRQ:
   MVI   E, REGstatus   ;Wait for DRQ bit to be set
   CALL   IDErd8D
   MOV   A, D
   ANI   10001000B
   CPI   00001000B
   JZ   DoneDRQ
   DCR   B
   JNZ   MoreDRQ
   LDA   @DELAYStore   ;Check timeout delay
   DCR   A
   STA   @DELAYStore
   JNZ   MoreDRQ
   STC         ;Set carry to indicate error
   RET
DoneDRQ:
   ORA   A      ;Clear carry
   RET         ;Return drive status in A

;------------------------------------------------------------------------------   
;Clear the ID buffer
;------------------------------------------------------------------------------   

CLEAR$ID$BUFFER:
   LXI   H, IDBuffer
   LXI   B, 512
CLEAR2:   MVI   A, ' '
   MOV   M, A
   INX   H
   DCX   B
   MOV   A, C
   ORA   B
   JNZ   CLEAR2
   
   LXI   H, IDBuffer   ;Zero for cylinder, heads, sectors
   LXI   B, 14
CLEAR3:   MVI   A, 0
   MOV   M, A
   INX   H
   DCX   B
   MOV   A, C
   ORA   B
   JNZ   CLEAR3
   RET

;------------------------------------------------------------------------------   
; Low Level 8 bit R/W to the drive controller. These are the routines that talk
; directly to the drive controller registers, via the 8255 chip. 
; Note the 16 bit I/O to the drive (which is only for SEC R/W) is done directly
; in the routines READSECTOR & WRITESECTOR for speed reasons.
;------------------------------------------------------------------------------   

;------------------------------------------------------------------------------   
;Read One Byte
;------------------------------------------------------------------------------   

IDErd8D:            ;Read 8 bits from IDE register in [E],
   MOV   A, E         ;and return info in [D]
   OUT   IDEportC      ;Drive address onto control lines

   ORI   IDErdline      ;RD pulse pin (40H)
   OUT   IDEportC      ;Assert read pin

   IN   IDEportA
   MOV   D, A         ;Return with data in [D]

   MOV   A, E
   OUT   IDEportC      ;Deassert RD pin

   XRA   A
   OUT   IDEportC      ;Zero all port C lines
   RET

;------------------------------------------------------------------------------   
;Write One Byte
;------------------------------------------------------------------------------   

IDEwr8D:            ;Write Data in [D] to IDE register [E]
   MVI   A, WRITEcfg8255      ;Set 8255 to write mode
   OUT   IDEportCtrl

   MOV   A, D         ;Get data put it in 8255 A port
   OUT   IDEportA

   MOV   A, E         ;Select IDE register
   OUT   IDEportC

   ORI   IDEwrline      ;Lower WR line
   OUT   IDEportC
   
   MOV   A, E         ;Raise WR line
   OUT   IDEportC      ;Deassert RD pin

   XRA   A         ;Deselect all lines including WR line
   OUT   IDEportC

   MVI   A, READcfg8255      ;Config 8255 chip, read mode on return
   OUT   IDEportCtrl
   RET

;------------------------------------------------------------------------------   
;This code is written to reside and run from 0H.  To re-introduce the CPMLDR,
;it must be copied from where it is stored in high memory and relocated to 100H
;in RAM, which overwrites this program.
;------------------------------------------------------------------------------   

CPM$MOVE$CODE
   LXI   H, BUFFER
   LXI   D, 100H
   LXI   B, (12*512)
   LDIR
   JMP   100H
CPM$MOVE$CODE$END:

;------------------------------------------------------------------------------   
;
;COMMAND BRANCH TABLE
;
;------------------------------------------------------------------------------   

TBL:   DW  DRIVE$0   ; "A"  Select Drive 0
   DW  DRIVE$1   ; "B"  Select Drive 1
   DW  CPMBOOT   ; "C"  LOAD CPM (if present)
   DW  DISPLAY   ; "D"  Sector contents display: ON/OFF
   DW  RAMCLEAR  ; "E"  Clear RAM buffer
   DW  FORMAT    ; "F"  Format current disk
   DW  RESTORE   ; "G"  Restore backup
   DW  BACKUP    ; "H"  Backup partition
   DW  NEXT$SECT ; "I"  Next Sector
   DW  PREV$SEC  ; "J"  Previous sector
   DW  ERROR     ; "K" 
   DW  SET$LBA   ; "L"  Set LBA value (Set Track, sector) 
   DW  ERROR     ; "M" 
   DW  POWER$DOWN; "N"  Power down hard disk command
   DW  ERROR     ; "O" 
   DW  ERROR     ; "P" 
   DW  ERROR     ; "Q" 
   DW  READ$SEC  ; "R"  Read sector to data buffer
   DW  SEQ$RD    ; "S"  Sequental sec read and display contents
   DW  ERROR     ; "T" 
   DW  POWER$UP  ; "U"  Power up hard disk command
   DW  N$RD$SEC  ; "V"  Read N sectors
   DW  WRITE$SEC ; "W"  Write data buffer to current sector
   DW  N$WR$SEC  ; "X"  Write N sectors
   DW  COPY$AB   ; "Y"  Copy Drive 0 to Drive 1
   DW  VERIFY$AB ; "Z"  Verify Drive 0 = Drive 1

;------------------------------------------------------------------------------   
;
;String constants - Messages generated by this program
;
;------------------------------------------------------------------------------

SIGN$ON:   DB   CR,LF,'IDE Disk Drive Utility Program 12/01/2022 (v2.9b)',CR,LF,LF,'$'
SEL0MSG      DB   'Selecting first IDE drive.',CR,LF,'$'
SEL1MSG      DB   'Selecting second IDE drive.',CR,LF,'$'
INITDRIVE   DB   'Initializing drive.  $'
READING$ID   DB   'Reading drive ID.  $'
GETTING$ID   DB   'Getting drive ID...',CR,LF,'$'
DISKSTATUS   DB   'Status is $'
INIT$0$ERROR:   DB   'Initialization of First Drive failed. Aborting Program.',BELL,CR,LF,LF,'$'
INIT$1$ERROR   DB   'Initialization of Second Drive failed. (Possibly not present).',BELL,CR,LF,LF,'$'
ID$ERROR:   DB   'Error obtaining Drive ID.',BELL,CR,LF,'$'
INIT$DR$OK:   DB   'Drive Initialized OK.',CR,LF,LF,'$'
BAD$DRIVE:   DB   CR,LF,'First Drive ID Information appears invalid.',CR,LF
      DB   'Aborting program.',BELL,CR,LF,LF,'$'
DRIVE0$INFO:   DB   '------------ Drive 0 -------------',CR,LF,'$'
DRIVE1$INFO:   DB   '------------ Drive 1 -------------',CR,LF,'$'
msgmdl:      DB   'Model: $'
msgsn:      DB   'S/N:   $'
msgrev:      DB   'Rev:   $'
msgcy:      DB   'Cyl: $'
msghd:      DB   ', Hd: $'
msgsc:      DB   ', Sec: $'
msgCPMTRK:   DB   'CPM TRK = $'
msgCPMSEC:   DB   ' CPM SEC = $'
msgLBA:      DB   '  (LBA = 00$'
MSGBracket   DB   ')$'
DRIVE$0$MSG   DB   CR,LF,LF,'  >>> DRIVE #0 <<<$'
DRIVE$1$MSG   DB   CR,LF,LF,'  >>> DRIVE #1 <<<$'
CMD$STRING1:    DB   '  IDE Board Diagnostic MAIN MENU',CR,LF,LF
      DB   '(L) Set LBA value    (R) Read Sector to Buffer (W) Write Buffer '
      DB   'to Sector',CR,LF
      DB   '(D) Set Display ON   (S) Sequental Sec Read    (F) Format Disk',CR,LF
      DB   '(V) Read N Sectors   (X) Write N Sectors       (H) Backup disk',CR,LF
      DB   '(G) Restore Backup   (I) Next Sector           '
      DB   '(J) Previous Sector',CR,LF
      DB   '(U) Power Up         (N) Power Down            (C) Boot CPM',CR,LF
      DB   '(A) Select Drive 0   (B) Select Drive 1        '
      DB   '(E) Clear Sector Buffer',CR,LF
      DB   '(Y) Copy d0 to d1    (Z) Verify d0 = d1        (ESC) Quit',CR,LF
      DB   LF,'Current settings: $'
CMD$STRING2:    DB   '      IDE Board Diagnostic MAIN MENU',CR,LF,LF
      DB   '(L) Set LBA value    (R) Read Sector to Buffer (W) Write Buffer '
      DB   'to Sector',CR,LF
      DB   '(D) Set Display OFF  (S) Sequental Sec Read    (F) Format Disk',CR,LF
      DB   '(V) Read N Sectors   (X) Write N Sectors       (H) Backup disk',CR,LF
      DB   '(G) Restore Backup   (I) Next Sector           '
      DB   '(J) Previous Sector',CR,LF
      DB   '(U) Power Up         (N) Power Down            (C) Boot CPM',CR,LF
      DB   '(A) Select Drive 0   (B) Select Drive 1        '
      DB   '(E) Clear Sector Buffer',CR,LF
      DB   '(Y) Copy d0 to d1    (Z) Verify d0 = d1        (ESC) Quit',CR,LF
      DB   LF,'Current settings: $'
Prompt:      DB   CR,LF,LF,'Please enter command > $'
Response:   DB   CR,LF,'Command received:      $'
msgsure:   DB   CR,LF,'Warning: this will change data on the drive, '
      DB   'are you sure? (Y/N)...$'
msgrd:      DB   CR,LF,'Sector Read OK',CR,LF,'$'
msgwr:      DB   CR,LF,'Sector Write OK',CR,LF,'$'
GET$LBA:   DB   'Enter CPM style TRK & SEC values (in hex).',CR,LF,'$'
SEC$RW$ERROR   DB   'Drive Error, Status Register = $'
ERR$REG$DATA   DB   'Drive Error, Error Register = $'
ENTER$SECL   DB   'Starting sector number,(xxH) = $'
ENTER$TRKL   DB   'Track number (LOW byte, xxH) = $'
ENTER$TRKH   DB   'Track number (HIGH byte, xxH) = $'
ENTER$HEAD   DB   'Head number (01-0F) = $'
ENTER$COUNT   DB   'Number of sectors to R/W = $'
DRIVE$BUSY   DB   'Drive Busy (bit 7) stuck high.   Status = $'
DRIVE$NOT$READY   DB   'Drive Ready (bit 6) stuck low.  Status = $'
DRIVE$WR$FAULT   DB   'Drive write fault.    Status = $'
UNKNOWN$ERROR   DB   'Unknown error in status register.   Status = $'
BAD$BLOCK   DB   'Bad Sector ID.    Error Register = $'
UNRECOVER$ERR   DB   'Uncorrectable data error.  Error Register = $'
READ$ID$ERROR   DB   'Error setting up to read Drive ID',CR,LF,'$'
SEC$NOT$FOUND   DB   'Sector not found. Error Register = $'
INVALID$CMD   DB   'Invalid Command. Error Register = $'
TRK0$ERR   DB   'Track Zero not found. Error Register = $'
UNKNOWN$ERROR1   DB   'Unknown Error. Error Register = $'
CONTINUE$MSG   DB   CR,LF,'To Abort enter ESC. Any other key to continue. $'
FORMAT$MSG   DB   'FORMAT DISK. Fill all sectors with E5'
      DB   60H,'s on the CURRENT drive/CF card.$'
ReadN$MSG   DB   CR,LF,'Read multiple sectors from current disk/CF card to RAM buffer.'
      DB   CR,LF,'How many 512 byte sectores (xx HEX):$'
WriteN$MSG   DB   CR,LF,'Write multiple sectors RAM buffer CURRENT disk/CF card.'
      DB   CR,LF,'How many 512 byte sectores (xx HEX):$'
ReadingN$MSG   DB   CR,LF,'Reading Sector at: $'
WritingN$MSG   DB   CR,LF,'Writing Sector at: $'
msgErr      DB   CR,LF,'Sorry, that was not a valid menu option!$'
FormatDone   DB   CR,LF,'Disk Format Complete.',CR,LF,'$'
BackupDone   DB   CR,LF,'Disk partition copy complete.',CR,LF,'$'
CopyMsg      DB   CR,LF,'Copy disk partition to a second area on disk (CF card).'
      DB   CR,LF,'>>> This assumes that tracks greater than MAXTRK '
      DB   '(for CPM, 0FFH) are unused <<<'
      DB   CR,LF,'>>> on this disk. Be sure you have nothing in this '
      DB   '"Backup partition area". <<<'
      DB   CR,LF,BELL,'Warning: This will change data in the partition area, '
      DB   'are you sure? (Y/N)...$ '
AtEnd      DB   CR,LF,'At end of disk partition!',CR,LF,'$'
RBackup$MSG   DB   'Reading track: $'
WBackup$MSG   DB   'H. Writing track: $'
H$Msg      DB   'H$'
RestoreMsg   DB   CR,LF,'Restore disk with data from backup partition on disk (CF card).'
      DB   CR,LF,BELL,'Warning: This will change data on disk, '
      DB   'are you sure? (Y/N)...$ '
RestoreDone   DB   CR,LF,'Restore of disk data from backup partition complete.',CR,LF,'$'
RANGE$MSG   DB   CR,LF,'Sector value out of range.',CR,LF,'$'
CPM$ERROR   DB   CR,LF,'Error reading CPMLDR.',CR,LF,'$'
CPM$ERROR1   DB   CR,LF,'Data error reading CPMLDR. (The first byte loaded was not 31H).',CR,LF,'$'
MOVE$REQUEST   DB   CR,LF,'The CPMLDR image is now at 3000H in RAM. '
      DB   'To boot CPM you will have to'
      DB   CR,LF,'overwrite this program at 100H. Do you wish to do so (Y/N)...$'
SETA$MSG   DB   CR,LF,'Current Drive is now #0 (Yellow LED)$'
SETB$MSG   DB   CR,LF,'Current Drive is now #1 (Green LED)$'
FILL$MSG   DB   CR,LF,'Sector buffer in RAM filled with 0',27H,'s$'      
DiskCopyMsg   DB   CR,LF,'Copy disk partition of Drive 0 to Drive 1 (CF card).'
      DB   CR,LF,BELL,'Warning: This will delete all data on Drive 1, '
      DB   'are you sure? (Y/N)...$ '
CopyDone   DB   CR,LF,'Disk copy of CPM disk 0 to 1 complete.',CR,LF,'$'
CopyTrk$MSG   DB   'Copying track: $'
DiskVerifyMsg   DB   CR,LF,'Verify disk partition Drive 0 = Drive 1 (CF card).$'
VerifyTrk$MSG   DB   'Verifying track: $'
VerifyDone   DB   CR,LF,'Verify CPM disk 0 = 1 complete.',CR,LF,'$'
Verify$ERR   DB   CR,LF,BELL,'Verify error on Track $'
SEC$Msg      DB   'H  Sector $'

;------------------------------------------------------------------------------   
;RAM usage
;------------------------------------------------------------------------------   

RAMAREA      DB   '           RAM STORE AREA -------->'
@DMA      DW   buffer
@DRIVE$SEC   DB   0H
@DRIVE$TRK   DW   0H
@DisplayFlag   DB   0FFH      ;Display of sector data initially ON
@SEC      DW   0H
@TRK      DW   0H
@SEC1      DW   0H      ;For disk partition copy
@TRK1      DW   0H
@SEC2      DW   0H
@TRK2      DW   0H
@StartLineHex   DW   0H
@StartLineASCII   DW   0H
@BYTE$COUNT   DW   0H
@SECTOR$COUNT   DW   0H
@DELAYStore   DB   0H
@CURRENT$DRIVE   DB   0H

      DB   '          Start of ID buffer-->'
IDbuffer   DS   512
      DB   '<--End of ID buffer            '

      ORG   BUFFER$ORG

BUFFER:      DB   76H
      DB   '<--Start buffer area'
      DS   476
      DB   'End of buffer-->'

BUFFER2:   DB   '<--Start buffer2 area'
      DS   476
      DB   'End of buffer2-->'

      DS   100H

STACK      DW   0H

;END
Wayne Parham
 
Posts: 240
Joined: March 18th, 2022, 3:01 pm

Re: IDE interface for Altair 8800c

Postby TronDD » December 4th, 2022, 8:07 pm

I had converted the myide assembly code to 8080 and, actually, to my own assembler but couldn't get anything out of the hardware.

I compiled your C version and still get no response from the card. So I'm back to hardware debugging.
TronDD
 
Posts: 40
Joined: November 20th, 2018, 8:51 pm

Re: IDE interface for Altair 8800c

Postby Wayne Parham » December 4th, 2022, 9:27 pm

The C program "IDEtest.c" should really help you get the hardware running because it's really "chatty" with debugging information. You can easily add more of your own too.

One thing I found as a first-try requirement is that you be able to hit the ports and see the results reflected in the drive-select LEDs.

IDEtest will let you do that: Do an "out 33H 80H" and then an "out 32H 00H" to see the LEDs. Then send 80, 40, 20, 10, 8, 4, 2 and 1 out to port 32H. You can directly control the 8255 this way and expect to see results, either with a scope probing pins on the 8255 or by just watching the LEDs.

This will let you verify board addressing is right before going any further. The switches need to be set to the right address and the jumpers need to be set right too. One jumper controls the signal used for reset and the other jumper controls the how the board interprets the top eight bits of the address bus, so it's important you set that right.

Back to the IDEutil/myIDE code, I'm now looking at the "wrlba" function for a couple of reasons. Firstly, it reports some goofy data, which I think might be caused by a problem similar to the one in the jump table code. Secondly, a handful of people have worked on the codebase used by myIDE - specifically on the wrlba function - to provide alternate formats.

The initial format left "holes" on the disk, which, on one hand, didn't matter all that much because we are only using a tiny sliver of it. But on the other hand, if you removed the flash drive and plugged it into something else, software tools to work with the disk (like image utilities) weren't happy with the holes. They're just weird. So a handful of guys explored that problem and have made improvements in the code.

I started my work with Monahan's v.2.9 myIDE, so what's listed in this thread is a modified version of that. But there are a couple later versions of myIDE - v.3.0 and v.3.1 - which address the "holes" problem and a few others. I'll probably incorporate those changes at some time. The IDEutil program I'm working on will ultimately support contiguous sector addressing.

But even more importantly - certainly more fundamentally - the myIDE program is working as-is for a handful of folks. It is odd to me that it just doesn't work on my Altair. Could be that I need to explore other assemblers and libraries. I mean, I'd prefer to have code that builds with Digital Research's ASM assembler without anything extra. But maybe I'm looking at it the wrong way and should try to explore other build mechanisms.
Wayne Parham
 
Posts: 240
Joined: March 18th, 2022, 3:01 pm

Re: IDE interface for Altair 8800c

Postby Wayne Parham » December 5th, 2022, 11:00 pm

Update:

As you might have read, I was growing suspicious that the build problems might be environmental, e.g. different assembly tools. I don't know what Monahan used to assemble his program but I initially assumed he used ASM from Digital Research.

So this evening I took a moment and tried to build with MAC/HEXCOM and RMAC/LINK. I also included the Z80.LIB file contained in the DR archive that has ASM, MAC and RMAC. None of these build environments created an executable that worked fully on my Altairs, but the programs built with MAC and RMAC did show meaningful sector and track values.

That was an excellent clue, and it suggested to me that I should compare the ideutil.prn output file made when building with ASM to the one generated by MAC. What I found was a handful of variables prefixed with the '@' symbol, and ASM generated 0000 for each of those symbols. MAC properly translated those values to the addresses expected, but ASM did not.

What I decided to do was to change each variable that began with an '@' character to one prefixed with 'm' instead. They are all variables that hold memory addresses, and each location is used to store one-byte or two-byte data values. So my 'm' prefix stands for "memory." I considered 'p' for "pointer" too, but that seemed more appropriate for other multi-byte variables, like "buffer." Ultimately, I decided I was giving it more consideration than it was worth, chose the 'm' prefix and called it good.

The resulting code builds with ASM and runs well on my Altair. I haven't tested every function yet, but I have done many of them and they all have worked properly. I can choose any sector on disk, and I can see meaningful data there. I am fairly confident it is accurate data because I see CP/M stuff on one of the disks I had previously placed a CP/M image upon. This version of IDEutil can also format a disk, and read back the E5 bytes in every position. So IDEutil can successfully read, write and format, which confirms all low-level functions are working properly. I'll test other things later, as time permits.

My next step is to update this program with the contiguous sector "no holes" version of wrlba written by David Fry. I'll also examine the other functions for idiosyncrasies and incorporate the improvements from myIDE versions 3.0 and 3.1. Should be able to diff the source code to find where changes were made, and manually bring 'em over if they seem worthwhile. One thing I'm pretty sure I won't incorporate is the option to run "holes" or "no holes" formats. I'll pick one - the contiguous one - and stick with it.

In hindsight, I'm laughing at myself for the huge Rube Goldberg machine I concocted to get where I am now. Started off with myIDE v.2.9. Couldn't get it to work, even after throwing in a few print statements for debugging. Decided to re-write in C. Got it to report its identity string. Went back to the assembly code and found the DJNZ instructions weren't being translated to machine code. Didn't even think to try the macro-assemblers or the libraries, and instead manually changed DJNZ to DCR/JNZ pairs. Found the @XYZZY variables weren't translated to addresses, so replaced 'em all with mXYZZY. In the end, this gave me source code that will build with ASM, but one might observe that it would have been a little bit easier to have started with the right build tools in the first place.

And that might be an attractive solution for others walking this path. You might start out with one of the later versions of myIDE and see if you can get it to run. I still had problems with my MAC and RMAC builds, even when using the Z80.LIB file. But certainly, there must be a solution there. Lots of people are running this IDE board.

As for me, I'm already knee-deep in this thing, and I'm going to finish what I started. I will have an IDEutil program that is based on the Monahan and Fry code, so if anyone makes any updates in the future, I can pretty easily hand-merge them going forward. And I can use the primitive interface functions in this code to create a BIOS that works on the Altair.

Code: Select all
;------------------------------------------------------------------------------
; Utility Program for IDE interface board
;   v2.9d   12/05/2022
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
;Build equates:
;------------------------------------------------------------------------------

FALSE      EQU   0
TRUE      EQU   NOT FALSE

CPM      EQU   TRUE   ;TRUE if using CPM, FALSE if loaded directly
DEBUG      EQU   TRUE   ;TRUE for error messages
VERBOSE      EQU   FALSE   ;TRUE for extended error messages
CPM$TRANSLATE   EQU   TRUE   ;Translate Trk, Sec, Head to CPM TRACK# & SEC#

;------------------------------------------------------------------------------
;Drive number equates:
;------------------------------------------------------------------------------

IDE0      EQU   0   ;Physical disk 0 drive number assignment
IDE1      EQU   1   ;Physical disk 1 drive number assignment

;------------------------------------------------------------------------------
;Console equates:
;------------------------------------------------------------------------------

CONI      EQU   10H   ;Console input port
CONO      EQU   11H   ;Console output port

;------------------------------------------------------------------------------
;CP/M equates:
;------------------------------------------------------------------------------

RDCON      EQU   1   ;For CP/M I/O
WRCON      EQU   2
RESET$DISK   EQU   0DH   ;Reset all CPM disks
PRINT      EQU   9
CONST      EQU   11   ;Console stat
BDOS      EQU   5

;------------------------------------------------------------------------------
;Display control equates:
;------------------------------------------------------------------------------

SCROLL      EQU   01H   ;Set scroll direction UP
LF      EQU   0AH
CR      EQU   0DH
BS      EQU   08H   ;Back space (required for sector display)
PERIOD      EQU   2EH
BELL      EQU   07H
SPACE      EQU   20H
QUIT      EQU   11H   ;Turns off any screen enhancements
NO$ENHANCEMENT   EQU   17H   ;Turns off whatever is on
FAST      EQU   10H   ;High speed scrool
TAB      EQU   09H   ;Tab across
ESC      EQU   1BH
CLEAR      EQU   1CH   ;Clear line (Use 80 spaces if not available)

;------------------------------------------------------------------------------
;IDE Interface equates:
;------------------------------------------------------------------------------
;Ports for 8255 chip. Change these to specify where your 8255 is addressed,
;The first three control which 8255 ports have the control signals,
;upper and lower data bytes.  The last one (IDEportCtrl), is for mode setting
;for the 8255 to configure its actual I/O ports (A,B & C). 
;
;Note most drives these days don't use the old Head,Track, Sector terminology.
;Instead we use "Logical Block Addressing" or LBA. This is what we use below.
;LBA treats the drive as one continous set of sectors, 0,1,2,3,... 3124,...etc.
;However as seen below we need to convert this LBA to heads,tracks and sectors
;to be compatible with CPM & MSDOS.
;
;NOTE: If you have only one drive/CF card, be sure it is in drive #0.
;The IDE hardware gets confused if there is only a drive in slot #1.
;------------------------------------------------------------------------------

IDEportA   EQU   030H   ;Lower 8 bits of IDE interface (8255)
IDEportB   EQU   031H   ;Upper 8 bits of IDE interface
IDEportC   EQU   032H   ;Control lines for IDE interface
IDEportCtrl   EQU   033H   ;8255 configuration port
IDEDrive   EQU   034H   ;Bit 0 - 0 for drive 0 and 1 for drive 1

READcfg8255   EQU   10010010b ;Set 8255 IDEportC to out, IDEportA/B input
WRITEcfg8255   EQU   10000000b ;Set all three 8255 ports to output mode

;------------------------------------------------------------------------------
;IDE control lines for use with IDEportC. 
;------------------------------------------------------------------------------

IDEa0line   EQU   01H   ;direct from 8255 to IDE interface
IDEa1line   EQU   02H   ;direct from 8255 to IDE interface
IDEa2line   EQU   04H   ;direct from 8255 to IDE interface
IDEcs0line   EQU   08H   ;inverter between 8255 and IDE interface
IDEcs1line   EQU   10H   ;inverter between 8255 and IDE interface
IDEwrline   EQU   20H   ;inverter between 8255 and IDE interface
IDErdline   EQU   40H   ;inverter between 8255 and IDE interface
IDErstline   EQU   80H   ;inverter between 8255 and IDE interface

;------------------------------------------------------------------------------
;Symbolic constants for the IDE drive registers
;------------------------------------------------------------------------------

REGdata      EQU   IDEcs0line
REGerr      EQU   IDEcs0line + IDEa0line
REGseccnt   EQU   IDEcs0line + IDEa1line
REGsector   EQU   IDEcs0line + IDEa1line + IDEa0line
REGcylinderLSB   EQU   IDEcs0line + IDEa2line
REGcylinderMSB   EQU   IDEcs0line + IDEa2line + IDEa0line
REGshd      EQU   IDEcs0line + IDEa2line + IDEa1line
REGcommand   EQU   IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGstatus   EQU   IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGcontrol   EQU   IDEcs1line + IDEa2line + IDEa1line
REGastatus   EQU   IDEcs1line + IDEa2line + IDEa1line + IDEa0line

;------------------------------------------------------------------------------
;IDE Command Constants.  These should never change.
;------------------------------------------------------------------------------

COMMANDrecal   EQU   10H
COMMANDread   EQU   20H
COMMANDwrite   EQU   30H
COMMANDinit   EQU   91H
COMMANDid   EQU   0ECH
COMMANDspindown   EQU   0E0H
COMMANDspinup   EQU   0E1H

;------------------------------------------------------------------------------
;IDE Status Register:
;------------------------------------------------------------------------------

;  bit 7: Busy   1=busy, 0=not busy
;  bit 6: Ready 1=ready for command, 0=not ready yet
;  bit 5: DF   1=fault occurred
;  bit 4: DSC   1=seek complete
;  bit 3: DRQ   1=data request ready, 0=not ready to xfer yet
;  bit 2: CORR   1=correctable error occurred
;  bit 1: IDX   vendor specific
;  bit 0: ERR   1=error occured

;------------------------------------------------------------------------------
;Disk equates:
;------------------------------------------------------------------------------

SEC$SIZE   EQU   512   ;Bytes per sector
MAXSEC      EQU   3DH   ;Sectors per track
MAXTRK      EQU   0FFH   ;CPM3 allows up to 8MG so 0-256 "tracks"
BUFFER$ORG   EQU   3000H   ;<----- Will place all sector data here

CPM$BOOT$COUNT   EQU   12   ;Allow up to 12 CPM sectors for CPMLDR
CPMLDR$ADDRESS   EQU   BUFFER$ORG

;------------------------------- INITIALIZATION -------------------------------   

   ORG   100H      ;<--- For CPM

begin:
   LXI   SP, STACK
   LXI     D, SIGN$ON   ;Print welcome message
   CALL   PSTRING
  IF VERBOSE
   LXI   D, SEL0MSG   ;Print select drive 0 message
   CALL   PSTRING
  ENDIF
   MVI   A, IDE0
   STA   mCURRENT$DRIVE
   OUT   IDEDrive   ;Select first drive
   
   CALL   CLEAR$ID$BUFFER   ;Clear ID Buffer
  IF VERBOSE
   LXI   D, INITDRIVE   ;Print initialization message
   CALL   PSTRING
  ENDIF
   CALL   IDEinit      ;Initialize the board and first drive
   JZ   INIT$OK      ;Continue on Zero
   
   LXI   D, INIT$0$ERROR   ;Non-zero is error, probably no drive
   CALL   PSTRING
   JMP   ABORT
   
INIT$OK:         ;Get drive 0 identification info         
   CALL   driveid
   JZ   INIT$OK1

   LXI   D, ID$ERROR   ;End program on error
   CALL   PSTRING
   JMP   ABORT

INIT$OK1:         ;Check sector count
   LXI     H, IDbuffer + 12
   MOV   A, M      ;(High Byte)
   ORA   A
   JNZ   INIT$OK2
   INX   H
   MOV   A, M      ;(Low Byte)
   ORA   A
   JNZ   INIT$OK2   ;Looks like we have a valid IDE drive
   
   LXI   D, BAD$DRIVE   ;Zero sectors means something's wrong
   CALL   PSTRING
   JMP   ABORT      ;No drive #0 so abort

INIT$OK2:         ;Print drive 0 info
   LXI   D, DRIVE0$INFO
   CALL   PSTRING
   LXI     D, msgmdl   ;Drive name   
   CALL   PSTRING
   LXI     H, IDbuffer + 54
   MVI   B, 20      ;Character count in words
   CALL   printSwap   ;Print [HL], [B] X 2 characters
   CALL   ZCRLF

   LXI     D, msgsn   ;Serial number
   CALL   PSTRING
   LXI     H, IDbuffer + 20
   MVI   B, 10      ;Character count in words
   CALL   printText
   CALL   ZCRLF

   LXI     D, msgrev   ;Firmware revision string
   CALL   PSTRING
   LXI     H, IDbuffer + 46
   MVI   B, 4      ;Character count in words
   CALL   printSwap
   CALL   ZCRLF

   LXI     D, msgcy   ;Drive specs (cyl/hd/sect)
   CALL   PSTRING
   LXI     H, IDbuffer + 2
   CALL   printparm
   LXI     D, msghd
   CALL   PSTRING
   LXI     H, IDbuffer + 6
   CALL   printparm
   LXI     D, msgsc
   CALL   PSTRING
   LXI     H, IDbuffer + 12
   CALL   printparm
   CALL   ZCRLF

INIT$OK3:         ;Move to second drive
   CALL   ZCRLF
  IF VERBOSE
   LXI   D, SEL1MSG   ;Print select drive 1 message
   CALL   PSTRING
  ENDIF
   MVI   A, IDE1      
   STA   mCURRENT$DRIVE
   OUT   IDEDrive   

   CALL   CLEAR$ID$BUFFER   ;Clear ID Buffer
   IF VERBOSE
   LXI   D, INITDRIVE   ;Print initialization message
   CALL   PSTRING
   ENDIF
   CALL   IDEinit      ;Initialize the second drive
   JZ   INIT$OK4

   LXI   D, INIT$1$ERROR   ;Non-zero is error, so print warning
   CALL   PSTRING
   JMP   MAINLOOP

INIT$OK4:         ;Get drive 1 identification info         
   CALL   driveid
   JZ   INIT$OK5

   LXI   D, ID$ERROR   ;On error, display message
   CALL   PSTRING
   JMP   MAINLOOP   ;Continue to main menu

INIT$OK5:         ;Check sector count
   LXI     H, IDbuffer + 12
   MOV   A, M      ;(High Byte)
   ORA   A
   JNZ   INIT$OK6
   INX   H
   MOV   A, M      ;(Low Byte)
   ORA   A
   JNZ   INIT$OK6   ;Looks like we have a valid IDE drive
BAD$DR1:   
   LXI   D, BAD$DRIVE   ;Zero sectors, so display error
   CALL   PSTRING
   JMP   MAINLOOP   ;Continue to main menu

INIT$OK6:         ;Print drive 1 info
   LXI   D, DRIVE1$INFO
   CALL   PSTRING
   LXI     D, msgmdl   ;Drive name   
   CALL   PSTRING
   LXI     H,IDbuffer + 54
   MVI   B, 20      ;Character count in words
   CALL   printSwap   ;Print [HL], [B] X 2 characters
   CALL   ZCRLF

   LXI     D, msgsn   ;Serial number
   CALL   PSTRING
   LXI     H, IDbuffer + 20
   MVI   B, 10      ;Character count in words
   CALL   printText
   CALL   ZCRLF

   LXI     D, msgrev   ;Firmware revision string
   CALL   PSTRING
   LXI     H, IDbuffer + 46
   MVI   B, 4      ;Character count in words
   CALL   printSwap
   CALL   ZCRLF

   LXI     D, msgcy   ;Drive specs (cyl/hd/sect)
   CALL   PSTRING
   LXI     H, IDbuffer + 2
   CALL   printparm
   LXI     D, msghd
   CALL   PSTRING
   LXI     H, IDbuffer + 6
   CALL   printparm
   LXI     D, msgsc
   CALL   PSTRING
   LXI     H, IDbuffer + 12
   CALL   printparm
   CALL   ZCRLF

INIT$DONE:         ;Cleanup and enter main menu
   CALL   CLEAR$ID$BUFFER
   CALL   IDEinit      ;Re-initialize drive 1
   MVI   A, IDE0
   STA   mCURRENT$DRIVE   ;Select drive 0
   OUT   IDEDrive
   CALL   IDEinit      ;Re-initialize drive 0
   LXI   H, 0
   SHLD   mSEC      ;Default to track 0 and sector 0
   SHLD   mTRK
   LXI   H, buffer   ;Set DMA address to buffer
   SHLD   mDMA
   JMP   MAINLOOP   ;Display Main Menu

;------------------------------------------------------------------------------   

TERMINATE:         ;End program from ESC command
ABORT:            ;Controlled termination
  IF CPM
   MVI   C, RESET$DISK   ;Reset all disks
   JMP   0FF00H      ;Reboot   CPM
  ELSE
   JMP   0F800H      ;Transfer control to Monitor ROM   
  ENDIF

;-------------------------------- MENU OPTIONS --------------------------------   

MAINLOOP:         ;Print main menu
   LDA   mCURRENT$DRIVE
   ORA   A
   JNZ   DRIVE$1$MENU
   LXI   D, DRIVE$0$MSG
   CALL   PSTRING
   JMP   Display0
DRIVE$1$MENU:
   LXI   D, DRIVE$1$MSG
   CALL   PSTRING
Display0:
   LDA   mDisplayFlag   ;Sector data display flag on or off
   ORA   A      ;NZ = on (Initially 0FFH so display on)
   JNZ     Display1
   LXI     D, CMD$STRING1   ;List command options (Turn display option on)
   JP   Display2
Display1:
   LXI     D, CMD$STRING2   ;List command options (Turn display option off)
Display2:
   CALL   PSTRING
   
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current track, sector, head
   
   LXI   D, Prompt   ;'>'
   CALL   PSTRING

;--------------------------------- USER INPUT ---------------------------------   
   
   CALL   GETCMD      ;Character Input
   CPI   ESC
   JZ   TERMINATE   ;End on ESC
   CPI   'A'
   JC   ERROR      ;Must be >= 'A'
   CPI   'Z'+1
   JNC   ERROR      ;Must be <= 'Z'
   CALL   ZCRLF

   SBI   'A'-1      ;Adjust to make 'A' keypress = 0
   ADD   A

   LXI   H, TBL      ;Offset into vector table
   ADD   L
   MOV   L, A
   MOV   A, M
   INX   HL

   MOV   E, M      ;Get selected function address
   INX   H
   MOV   D, M
   XCHG

   PCHL         ;Jump to command function address
   
;------------------------------ COMMAND FUNCTIONS -----------------------------   

READ$SEC:         ;Read Sector @ LBA to the RAM buffer
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

   CALL   READSECTOR

   JZ   main1b      ;Z means the sector read was OK
   CALL   ZCRLF
   JMP   MAINLOOP

main1b:   LXI     D, msgrd   ;Sector read OK
   CALL   PSTRING

   LDA   mDisplayFlag   ;Do we have display flag on or off
   ORA   A      ;NZ = on
   JZ   MAINLOOP
   LXI   H, buffer   ;Point to buffer. Show sector data flag is on
   SHLD   mDMA
   CALL   HEXDUMP      ;Show sector data
   JMP   MAINLOOP

WRITE$SEC:         ;Write data in RAM buffer to sector @ LBA
   LXI     D, msgsure   ;Are you sure?
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   main2c
   CALL   ZCRLF

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

   CALL   WRITESECTOR

   JZ   main2b      ;Z means the sector write was OK
   CALL   ZCRLF
   JMP   MAINLOOP
main2b:   LXI     D, msgwr   ;Sector written OK
   CALL   PSTRING
main2c: JMP   MAINLOOP

SET$LBA:         ;Set the logical block address
   LXI     D, GET$LBA   
   CALL   PSTRING
   CALL   ghex32lba   ;Get CPM style Track & Sector, put in RAM
   JC   main3b      ;Ret C set if abort/error
   CALL   wrlba      ;Update LBA on drive
main3b:   CALL   ZCRLF
   JMP   MAINLOOP

NEXT$SECT:
   LDA   mSEC
   INR   A      
   CPI   MAXSEC-1
   JNC   RANGE$ERROR
   STA   mSEC
   CALL   wrlba      ;Update LBA on drive
   CALL   ZCRLF
   JMP   MAINLOOP

RANGE$ERROR:
   LXI     D, RANGE$MSG   
   CALL   PSTRING
   JMP   MAINLOOP
   
PREV$SEC:
   LDA   mSEC
   ORA   A
   JZ   RANGE$ERROR
   DCR   A
   STA   mSEC
   CALL   wrlba      ;Update LBA on drive
   CALL   ZCRLF
   JMP   MAINLOOP
   
POWER$UP:         ;Set the drive to spin up
   CALL   spinup
   JMP   MAINLOOP

POWER$DOWN:         ;Set the drive to spin down
   CALL   spindown
   JMP   MAINLOOP

DISPLAY:         ;Do we have display flag on or off
   LDA   mDisplayFlag   
   CMA         ;flip it
   STA   mDisplayFlag
   JMP   MAINLOOP   ;Update display and back to next menu command

SEQ$RD:            ;Do sequential reads
   CALL   SequentialReads
   JMP   MAINLOOP

DRIVE$0:
   MVI   A, IDE0      ;Select Drive 0:
   STA   mCURRENT$DRIVE
   OUT   IDEDrive
   LXI     D, SETA$MSG   
   CALL   PSTRING
   JMP   MAINLOOP

DRIVE$1:
   MVI   A, IDE1      ;Select Drive 1:
   STA   mCURRENT$DRIVE
   OUT   IDEDrive
   LXI     D,SETB$MSG   
   CALL   PSTRING
   JMP   MAINLOOP

RAMCLEAR:         ;Fill RAM buffer with 0's
   LXI   H, buffer   ;Point to buffer
   LXI   D, 512
   MVI   A, 0      ;Fill area with 0's
CLEAR1:   MOV   M, A
   INX   H
   DCX   D
   MOV   A, E
   ANA   D
   JNZ   CLEAR1
   LXI     D, FILL$MSG   
   CALL   PSTRING
   JMP   MAINLOOP

CPMBOOT:         ;Boot CPM from IDE system tracks -- if present
   MVI   A, 0      ;Load from track 0, sec 1, head 0 (always)
   STA   mSEC      ;Remember sectors are numbered +1
   XRA   A
   STA   mTRK+1
   STA   mTRK

   MVI   A, CPM$BOOT$COUNT ;Count of CPMLDR sectors  (12)
   STA   mSECTOR$COUNT
   LXI   H, CPMLDR$ADDRESS ;DMA address where the CPMLDR resides in RAM
   SHLD   mDMA

NextRCPM:
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current track, sector, head
   CALL   ZCRLF
   
   LHLD   mDMA
   CALL   READSECTOR   ;Read a sector
   SHLD   mDMA

   LDA   mSECTOR$COUNT
   DCR   A
   STA   mSECTOR$COUNT
   JZ   LOAD$DONE

   LHLD   mSEC
   INX   H
   SHLD   mSEC      ;Stay on track 0 in this special case
   JMP   NextRCPM

LOAD$DONE:
   MVI   E, REGstatus   ;Check the R/W status when done
   CALL   IDErd8D
   BIT   0, D
   JNZ   CPMLoadErr   ;Zero if no errors
   LXI   H, CPMLDR$ADDRESS
   MOV   A, M
   CPI   31H      ;EXPECT TO HAVE 31H @80H IE. LD SP,80H
   JNZ   CPMLoadErr1   ;Zero if no errors
   
   LXI   D, MOVE$REQUEST   ;Ask if we can move data to 100H
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, CPM$MOVE$CODE ;Need to move code out of the way.
   LXI   D, 0H
   LXI   B, (CPM$MOVE$CODE$END-CPM$MOVE$CODE)
   LDIR
   JMP   0H      ;Now jump here to move the CPMLDR (@3000H) to 100H
   
CPMLoadErr1:
   LXI   D, CPM$ERROR1   ;Drive data error
   CALL   PSTRING
   JMP   MAINLOOP
   
CPMLoadErr:
   LXI   D, CPM$ERROR   ;Drive Read Error
   CALL   PSTRING
   JMP   MAINLOOP

N$RD$SEC:         ;Read N sectors
   LXI   D, ReadN$MSG   ;No check for possible high RAM (CPM) overwrite
   CALL   PSTRING
   CALL   GETHEX
   JC   MAINLOOP   ;Abort if ESC (C flag set)
   
   STA   mSECTOR$COUNT   ;Store sector count
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

NextRSec:   
   LXI   D, ReadingN$MSG
   CALL   PSTRING
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current track, sector, head
   
   LHLD   mDMA
   CALL   READSECTOR
   SHLD   mDMA

   LDA   mSECTOR$COUNT
   DCR   A
   STA   mSECTOR$COUNT
   JZ   MAINLOOP
   
   LHLD   mSEC
   INX   H
   SHLD   mSEC   
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextRSec

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC
   LHLD   mTRK      ;Bump to next track
   INX   H
   SHLD   mTRK
   MOV   A, L      ;0-FFH tracks (only)
   JNZ   NextRSec
   
   LXI   D, AtEnd   ;Tell us we are at end of disk
   CALL   PSTRING
   JMP   MAINLOOP

N$WR$SEC:         ;Write N sectors
   LXI     D, msgsure   ;Are you sure?
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   main2c

   LXI   D, WriteN$MSG
   CALL   PSTRING
   CALL   GETHEX
   JC   MAINLOOP   ;Abort if ESC (C flag set)

   STA   mSECTOR$COUNT   ;Store sector count
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

NextWSec:   
   LXI   D, WritingN$MSG
   CALL   PSTRING
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current track, sector, head
   
   LHLD   mDMA      ;Actully, Sector/track values are already updated
   CALL   WRITESECTOR   ;in wrlba, but WRITESECTOR is used in multiple places.
   SHLD   mDMA      ;A repeat does no harm -- speed is not an issue here

   LDA   mSECTOR$COUNT
   DCR   A
   STA   mSECTOR$COUNT
   JZ   MAINLOOP
   
   LHLD   mSEC
   INX   H
   SHLD   mSEC   
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextWSec

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC
   LHLD   mTRK      ;Bump to next track
   INX   H
   SHLD   mTRK
   MOV   A, L      ;0-FFH tracks (only)
   ORA   A
   JNZ   NextWSec
   
   LXI   D, AtEnd   ;Tell us we are at end of disk
   CALL   PSTRING
   JMP   MAINLOOP

FORMAT:            ;Format (Fill sectors with E5)
   LXI   D, FORMAT$MSG
   CALL   PSTRING
   LXI     D, msgsure   ;Are you sure?
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP
   LXI   H, buffer   ;Fill buffer with E5's (512 of them)
   MVI   B, 0
Fill0:   MVI   A, 0E5H      ;<-- Sector fill character (E5 for CPM)
   MOV   M, A
   INX   H
   MOV   M, A
   INX   H
   DCR   B
   JNZ   Fill0
   CALL   ZCRLF

NEXT$FORMAT:
   LXI   H, buffer
   SHLD   mDMA
   CALL   WRITESECTOR   ;Will return error if there was one
   JZ   main9b      ;Z means the sector write was OK
   CALL   ZCRLF
   JMP   MAINLOOP
main9b:   CALL   ZEOL      ;Clear line cursor is on
   CALL   DISPLAYposition   ;Display actual current track, sector and head
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   WRNEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JZ   MAINLOOP
   CALL   ZCRLF
WRNEXTSEC1:
   LHLD   mSEC
   INX   H
   SHLD   mSEC      ;0 to MAXSEC CPM Sectors
   MOV   A, L
   CPI   MAXSEC
   JNZ   NEXT$FORMAT

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC
   LHLD   mTRK      ;Bump to next track
   INX   H
   SHLD   mTRK
   MOV   A, L      ;0-FFH tracks (only)
   CPI   MAXTRK
   JNZ   NEXT$FORMAT   

   LXI   D, FormatDone   ;Tell us we are all done.
   CALL   PSTRING
   JMP   MAINLOOP
            
BACKUP:            ;Backup the CPM partition to another area
   LXI   D, CopyMsg
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   mSEC
   SHLD   mSEC1
   SHLD   mSEC2      ;and on second partition
   SHLD   mTRK      ;and track 0
   SHLD   mTRK1
   LXI   H, MAXTRK+0200H+1
   SHLD   mTRK2

   CALL   ZCRLF
   CALL   ZCRLF
   
NextCopy1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, RBackup$MSG   ;for each track update display
   CALL   PSTRING
   LDA   mTRK1+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK1      ;Low TRK byte
   CALL   PHEX
   LXI   D, WBackup$MSG
   CALL   PSTRING
   LDA   mTRK2+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK2      ;Low TRK byte
   CALL   PHEX
   LXI   D, H$Msg
   CALL   PSTRING

NextCopy:   
   LDA   mSEC1
   STA   mSEC
   LHLD   mTRK1
   SHLD   mTRK
   CALL   wrlba      ;Update LBA on "1st" drive

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA
   CALL   READSECTOR   ;Get sector data to buffer
   
   LDA   mSEC2
   STA   mSEC
   LHLD   mTRK2
   SHLD   mTRK
   CALL   wrlba      ;Update LBA on "2nd" drive
   
   LXI   H,buffer   ;Point to buffer
   SHLD   mDMA
   CALL   WRITESECTOR   ;Write buffer data to sector
   
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   BKNEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JZ   MAINLOOP

BKNEXTSEC1:
   LHLD   mSEC
   INX   H
   SHLD   mSEC1
   SHLD   mSEC2   
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextCopy

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC1
   SHLD   mSEC2
   
   LHLD   mTRK1      ;Bump to next track
   INX   H
   SHLD   mTRK1
   
   LHLD   mTRK2      ;Bump to next track
   INX   H
   SHLD   mTRK2
   
   LHLD   mTRK1      ;Check if we are done
   MOV   A, L      ;0-FFH tracks (only)
   CPI   MAXTRK
   JNZ   NextCopy1
   
   LXI   D, BackupDone   ;Tell us we are all done.
   CALL   PSTRING
   JMP   MAINLOOP

RESTORE:         ;Restore disk from backup partition
   LXI   D, RestoreMsg
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   mSEC
   SHLD   mSEC1
   SHLD   mSEC2      ;and on second partition
   SHLD   mTRK      ;and track 0
   SHLD   mTRK1
   LXI   H, MAXTRK+0200H+1
   SHLD   mTRK2

   CALL   ZCRLF
   CALL   ZCRLF
   
NextRestore1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, RBackup$MSG   ;for each track update display
   CALL   PSTRING
   LDA   mTRK2+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK2      ;Low TRK byte
   CALL   PHEX
   LXI   D, WBackup$MSG
   CALL   PSTRING
   LDA   mTRK1+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK1      ;Low TRK byte
   CALL   PHEX
   LXI   D,H$Msg
   CALL   PSTRING

NextRestore:   
   LDA   mSEC2      ;Point to backup partition
   STA   mSEC
   LHLD   mTRK2
   SHLD   mTRK
   CALL   wrlba      ;Update LBA on "1st" drive

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA
   CALL   READSECTOR   ;Get sector data to buffer
   
   LDA   mSEC1
   STA   mSEC
   LHLD   mTRK1
   SHLD   mTRK
   CALL   wrlba      ;Update LBA on "2nd" drive
   
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA
   CALL   WRITESECTOR   ;Write buffer data to sector
   
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   RESNEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JZ   MAINLOOP

RESNEXTSEC1:
   LHLD   mSEC
   INX   H
   SHLD   mSEC1
   SHLD   mSEC2   
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextRestore

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC1
   SHLD   mSEC2
   
   LHLD   mTRK1      ;Bump to next track
   INX   H
   SHLD   mTRK1
   
   LHLD   mTRK2      ;Bump to next track
   INX   H
   SHLD   mTRK2
   
   LHLD   mTRK2      ;Check if we are done
   MOV   A, L      ;0-FFH tracks (only)
   CPI   MAXTRK
   JNZ   NextRestore1
   
   LXI   D, RestoreDone   ;Inform user restore complete
   CALL   PSTRING
   JMP   MAINLOOP

ERROR:   LXI     D, msgErr   ;CMD error msg
   CALL   PSTRING
   JMP   MAINLOOP

COPY$AB:         ;Copy drive 0 to drive 1
   LXI   D, DiskCopyMsg
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   mSEC
   SHLD   mTRK      ;and track 0
   
   CALL   ZCRLF
   CALL   ZCRLF
   
NextDCopy1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, CopyTrk$MSG   ;for each track update display
   CALL   PSTRING
   LDA   mTRK+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK      ;Low TRK byte
   CALL   PHEX
   LXI   D, H$Msg
   CALL   PSTRING

NextDCopy:   
   MVI   A, IDE0      ;Login drive 0
   STA   mCURRENT$DRIVE
   OUT   IDEDrive

   CALL   wrlba      ;Update LBA on drive 0

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA
   CALL   READSECTOR   ;Get sector data from drive 0 to buffer
   
   MVI   A, IDE1      ;Login drive 1
   STA   mCURRENT$DRIVE
   OUT   IDEDrive

   CALL   wrlba      ;Update LBA on drive 1
   
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA
   CALL   WRITESECTOR   ;Write buffer data to sector on drive 1
   
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM says something is there
   JNZ   BK$D$NEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JNZ   BK$D$NEXTSEC1
   MVI   A,IDE0      ;Login drive 1
   STA   mCURRENT$DRIVE
   OUT   IDEDrive
   JMP   MAINLOOP

BK$D$NEXTSEC1:
   LHLD   mSEC
   INX   H
   SHLD   mSEC
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextDCopy

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC
   
   LHLD   mTRK      ;Bump to next track
   INX   H
   SHLD   mTRK
            ;Check if we are done
   MOV   A, L      ;0-FFH tracks (only)
   CPI   MAXTRK
   JNZ   NextDCopy1
   
   LXI   D, CopyDone   ;Inform user copy complete
   CALL   PSTRING
   MVI   A, IDE0      ;Login drive 0
   STA   mCURRENT$DRIVE
   OUT   IDEDrive
   JMP   MAINLOOP

VERIFY$AB:         ;Verify Drive 0 = 1
   LXI   D, DiskVerifyMsg
   CALL   PSTRING
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   mSEC
   SHLD   mTRK      ;and track 0
   
   CALL   ZCRLF
   CALL   ZCRLF
   
NextVCopy1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, VerifyTrk$MSG ;for each track update display
   CALL   PSTRING
   LDA   mTRK+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK      ;Low TRK byte
   CALL   PHEX
   LXI   D, H$Msg
   CALL   PSTRING

NextVCopy:   
   MVI   A, IDE0      ;Login drive 0
   STA   mCURRENT$DRIVE
   OUT   IDEDrive

   CALL   wrlba      ;Update LBA on 0 drive

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA
   CALL   READSECTOR   ;Get sector data from buffer 0 drive
   
   MVI   A, IDE1      ;Login drive 1
   STA   mCURRENT$DRIVE
   OUT   IDEDrive

   CALL   wrlba      ;Update LBA on 1 drive
   
   LXI   H, buffer2   ;Point to buffer2
   SHLD   mDMA
   CALL   READSECTOR   ;Read buffer data from sector of drive 1
   
   LXI   BC, 512      ;Now check both buffers are identical
   LXI   H, buffer
   LXI   D, buffer2
NEXTV:   LDAX   D
   CMP   M      ;Is [DE] = [HL]?
   JNZ   COMPARE$ERROR
   INX   H
   INX   D
   DCX   B
   MOV   A,C
   ANA   B
   JZ   VERIFY$OK
   JMP   NEXTV

COMPARE$ERROR:
   LXI   D, VERIFY$ERR   ;Indicate an error
   CALL   PSTRING
   LDA   mTRK+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK      ;Low TRK byte
   CALL   PHEX
   LXI   D, SEC$Msg
   CALL   PSTRING
   LDA   mSEC      ;Sector byte
   CALL   PHEX
   LXI   D, H$Msg
   CALL   PSTRING
   JMP   VER$OK1
   
VERIFY$OK:
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM says something is there
   JNZ   BK$V$NEXTSEC1
   CALL   ZCI      ;Flush character
VER$OK1:
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JNZ   BK$V$NEXTSEC1
   MVI   A, IDE0      ;Login drive 0
   STA   mCURRENT$DRIVE
   OUT   IDEDrive
   JMP   MAINLOOP

BK$V$NEXTSEC1:
   LHLD   mSEC
   INX   H
   SHLD   mSEC
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NextVCopy

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC
   
   LHLD   mTRK      ;Bump to next track
   INX   H
   SHLD   mTRK
            ;Check if we are done
   MOV   A, L      ;0-FFH tracks (only)
   CPI   MAXTRK
   JNZ   NextVCopy1
   
   LXI   D, VerifyDone   ;Tell us we are all done.
   CALL   PSTRING
   MVI   A, IDE0      ;Login drive 0
   STA   mCURRENT$DRIVE
   OUT   IDEDrive
   JMP   MAINLOOP

;----------------------------- SUPPORT FUNCTIONS ------------------------------   
            
driveid:CALL   IDEwaitnotbusy   ;Retrieve drive info
   RC
   MVI   D, COMMANDid
   MVI   E, REGcommand
   CALL   IDEwr8D      ;Issue the ID command
  IF VERBOSE
   LXI   D, READING$ID
   CALL   PSTRING
   LXI   D, DISKSTATUS   ;Print status message
   CALL   PSTRING
  ENDIF
   MVI   E, REGstatus   ;Get status after ID command
   CALL   IDErd8D      ;Check Status (info in [D])
  IF VERBOSE
   MOV   A, D
   CALL   PHEX      ;Print status
   CALL   ZPERCRLF
  ENDIF
   CALL   IDEwaitdrq   ;Wait for Busy=0, DRQ=1
   JC   SHOWerrors
  IF VERBOSE
   LXI   D, GETTING$ID
   CALL   PSTRING
  ENDIF
   MVI   B, 0      ;256 words
   LXI     H, IDbuffer   ;Store data here
   CALL   MoreRD16   ;Get 256 words of data from REGdata port to [HL]
   RET

spinup:            ;Start the drive
   MVI   D, COMMANDspinup
spup2:   MVI   E, REGcommand
   CALL   IDEwr8D
   CALL   IDEwaitnotbusy
   JC   SHOWerrors
   ORA   A      ;Clear carry
   RET
            
spindown:         ;Tell the drive to spin down
   CALL   IDEwaitnotbusy
   JC   SHOWerrors
   MVI   D,COMMANDspindown
   JMP   spup2

SequentialReads:      ;Sequentially read sectors from current position
   CALL   IDEwaitnotbusy
   JC   SHOWerrors
   CALL   ZCRLF
NEXTSEC:
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

   CALL   READSECTOR   ;Errors will show in READSECTOR
   JZ   SEQOK
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC      ;Abort if ESC
   RZ
   
SEQOK:   CALL   ZEOL      ;Clear line cursor is on
   CALL   DISPLAYposition   ;Display current track, sector and head

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

   LDA   mDisplayFlag   ;Do we have display flag on or off
   ORA   A      ;NZ = on
   CNZ   HEXDUMP
   CALL   ZCRLF
   CALL   ZCRLF
   CALL   ZCRLF

   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   NEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   RZ
   CALL   ZCRLF
NEXTSEC1:
   LHLD   mSEC
   INX   H
   SHLD   mSEC   
   MOV   A, L      ;0 to 62 CPM Sectors
   CPI   MAXSEC-1
   JNZ   NEXTSEC

   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC
   LHLD   mTRK      ;Bump to next track
   INX   H
   SHLD   mTRK
   JMP   NEXTSEC      ;Note will go to last sec on disk unless stopped

DISPLAYposition:      ;Display current track, sector & head position
   LXI     D, msgCPMTRK   ;Display in LBA format
   CALL   PSTRING      ;---- CPM FORMAT ----
   LDA   mTRK+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK      ;Low TRK byte
   CALL   PHEX
      
   LXI     D, msgCPMSEC
   CALL   PSTRING      ;SEC = (16 bits)
   LDA   mSEC+1      ;High Sec
   CALL   PHEX
   LDA   mSEC      ;Low sec
   CALL   PHEX
            ;---- LBA FORMAT ----
   LXI     D, msgLBA
   CALL   PSTRING      ;LBA = 00 ("Heads" = 0 for these drives)
   LDA   mDRIVE$TRK+1   ;High "cylinder" byte
   CALL   PHEX
   LDA   mDRIVE$TRK   ;Low "cylinder" byte
   CALL   PHEX   
   LDA   mDRIVE$SEC
   CALL   PHEX
   LXI     D, MSGBracket
   CALL   PSTRING      
   RET

printText:         ;Print text up to [B] (16-bit word) byte-pairs
   MOV   C, M      ;Text is contiguous byte array
   CALL   ZCO   
   INX   H
   MOV   C, M
   CALL   ZCO   
   INX   H
   DCR   B
   JNZ   printText
   RET

printSwap:         ;Print text up to [B] (16-bit word) byte-pairs   
   INX   H      ;Swap byte pairs - low byte, high byte
   MOV   C, M
   CALL   ZCO   
   DCX   H
   MOV   C, M
   CALL   ZCO
   INX   H
   INX   H
   DCR   B
   JNZ   printSwap
   RET

ZCRLF:
   PUSH   PSW
   MVI   C, CR
   CALL   ZCO
   MVI   C, LF
   CALL   ZCO
   POP   PSW
   RET

ZPERCRLF:
   PUSH   PSW
   MVI   C, PERIOD
   CALL   ZCO
   MVI   C, CR
   CALL   ZCO
   MVI   C, LF
   CALL   ZCO
   POP   PSW
   RET

ZEOL:            ;CR and clear current line
   MVI   C, CR
   CALL   ZCO
   MVI   C, CLEAR   ;Use 80 spaces if necessary
   CALL   ZCO
   RET

ZCSTS:
  IF CPM
   PUSH   B
   PUSH   D
   PUSH   H
   MVI   C, CONST
   CALL   BDOS      ;Returns with 1 in [A] if character at keyboard
   POP   H
   POP   D
   POP   B
   CPI   1
   RET
  ELSE   
   IN   COMI      ;Get Character in [A]
   ANI   02H
   RZ
   MVI   A, 01H
   ORA   A
   RET
  ENDIF

ZCO:            ;Write character that is in [C]
  IF CPM
   PUSH   PSW
   PUSH   B
   PUSH   D
   PUSH   H
   MOV   E, C
   MVI   C, WRCON
   CALL   BDOS
   POP   H
   POP   D
   POP   B
   POP   PSW
   RET
  ELSE   
   PUSH   PSW   
ZCO1:   IN      CONI      ;Show Character
   ANI   04H
   JZ   ZCO1
   MOV   A, C
   OUT   CONO
   POP   PSW
   RET
  ENDIF

ZCI:            ;Return keyboard character in [A]
  IF CPM
   PUSH   B
   PUSH   D
   PUSH   H
   MVI   C, RDCON
   CALL   BDOS
   POP   H
   POP   D
   POP   B
   RET
  ELSE
ZCI1:   IN   CONI      ;Get Character in [A]
   ANI   02H
   JZ   ZCI1
   IN   CONO
   RET
  ENDIF

;------------------------------------------------------------------------------   
;Print a string in [DE] up to '$'
;------------------------------------------------------------------------------   

PSTRING:
  IF CPM
   MVI   C, PRINT
   JMP   BDOS      ;PRINT MESSAGE
  ELSE
   PUSH   B
   PUSH   D
   PUSH   H
   XCHG
PSTRX:   MOV   A, M
   CPI   '$'
   JZ   DONEP
   MOV   C, A
   CALL   ZCO
   INX   H
   JMP   PSTRX
DONEP:   POP   H
   POP   D
   POP   B
   RET
  ENDIF

SHOWerrors:
  IF NOT DEBUG
   ORA   A      ;Set NZ flag
   STC         ;Set Carry Flag
   RET
  ELSE
   CALL   ZCRLF
   MVI   E, REGstatus   ;Get status in status register
   CALL   IDErd8D
   MOV   A, D
   ANI   1H
   JNZ   MoreError   ;Go to  REGerr register for more info
            ;All OK if 01000000
   PUSH   PSW      ;Save for return below
   ANI   80H
   JZ   NOT7
   LXI   D, DRIVE$BUSY   ;Drive Busy (bit 7) stuck high
   CALL   PSTRING
   JMP   DONEERR
NOT7:   ANI   40H
   JNZ   NOT6
   LXI   D, DRIVE$NOT$READY ;Drive Not Ready (bit 6) stuck low
   CALL   PSTRING
   JMP   DONEERR
NOT6:   ANI   20H
   JNZ   NOT5
   LXI   D, DRIVE$WR$FAULT ;Drive write fault
   CALL   PSTRING
   JMP   DONEERR
NOT5   LXI   D, UNKNOWN$ERROR
   CALL   PSTRING
   JMP   DONEERR

MoreError:         ;Bit 0 of the status register indicates problem
   MVI   E, REGerr   ;Get error code in REGerr
   CALL   IDErd8D
   MOV   A, D
   PUSH   PSW

   ANI   10H
   JZ   NOTE4
   LXI   D, SEC$NOT$FOUND
   CALL   PSTRING
   JMP   DONEERR

NOTE4:   ANI   80H
   JZ   NOTE7
   LXI   D, BAD$BLOCK
   CALL   PSTRING
   JMP   DONEERR
NOTE7:   ANI   40H
   JZ   NOTE6
   LXI   D, UNRECOVER$ERR
   CALL   PSTRING
   JMP   DONEERR
NOTE6:   ANI   4H
   JZ   NOTE2
   LXI   D, INVALID$CMD
   CALL   PSTRING
   JMP   DONEERR
NOTE2:   ANI   2H
   JZ   NOTE1
   LXI   D, TRK0$ERR
   CALL   PSTRING
   JMP   DONEERR
NOTE1:   LXI   D, UNKNOWN$ERROR1
   CALL   PSTRING
   JMP   DONEERR

DONEERR:POP   PSW
   PUSH   PSW
   CALL   ZBITS
   CALL   ZCRLF
   POP   PSW
   ORA   A      ;Set Z flag
   STC         ;Set Carry flag
   RET
   ENDIF

;------------------------------------------------------------------------------   
;Print a 16-bit number in RAM located @ [HL], low-byte first for Drive ID
;------------------------------------------------------------------------------   

printparm:
   INX   H   ;Index to high byte first
   MOV   A, M
   CALL   PHEX
   DCX   H   ;Now low byte
   MOV   A, M
   CALL   PHEX
   RET

;------------------------------------------------------------------------------   
;Print an 8 bit number located in [A]
;------------------------------------------------------------------------------   

PHEX:   PUSH   PSW
   PUSH   B
   PUSH   PSW
   RRC
   RRC
   RRC
   RRC
   CALL   ZCONV
   POP   PSW
   CALL   ZCONV
   POP   B
   POP   PSW
   RET

ZCONV:   ANI   0FH      ;HEX to ASCII and print it
   ADI   90H
   DAA
   ACI   40H
   DAA
   MOV   C, A
   CALL   ZCO
   RET

;------------------------------------------------------------------------------   
;Display binary in [A]
;------------------------------------------------------------------------------   

ZBITS:   PUSH   PSW
   PUSH   B
   PUSH   D
   MOV   E, A      
   MVI   B, 8
BQ2:   DB   0CBH, 23H   
   SLAR   E
   MVI   A, 18H
   ADC   A
   MOV   C, A
   CALL   ZCO
   DCR   B
   JNZ   BQ2
   POP   D
   POP   B
   POP   PSW
   RET

ghex32lba:         ;Convert CPM Track & Sector to LBA format
   LXI     D,ENTER$SECL   ;Enter sector number
   CALL   PSTRING
   CALL   GETHEX      ;Get 2 HEX digits
   RC
   STA   mSEC
   CALL   ZCRLF

   LXI   D, ENTER$TRKH   ;Enter high byte track number
   CALL   PSTRING
   CALL   GETHEX      ;Get 2 HEX digits
   RC
   STA   mTRK+1
   CALL   ZCRLF

   LXI     D, ENTER$TRKL   ;Enter low byte track number
   CALL   PSTRING
   CALL   GETHEX      ;Get 2 more HEX digits
   RC
   STA   mTRK
   CALL   ZCRLF
   XRA   A
   ORA   A      ;To return NC
   RET

;------------------------------------------------------------------------------   
;Get a HEX character from the keyboard and echo it
;------------------------------------------------------------------------------   

GETHEX:
   CALL   GETCMD      ;Get character
   CPI   ESC
   JZ   HEXABORT
   CPI   '/'      ;check 0-9, A-F
   JC   HEXABORT
   CPI   'F'+1
   JNC   HEXABORT
   CALL   ASBIN      ;Convert to binary
   RLC         ;Shift to high nibble
   RLC
   RLC
   RLC
   MOV   B, A      ;Store it
   CALL   GETCMD      ;Get 2nd character from keyboard & ECHO
   CPI   ESC
   JZ   HEXABORT
   CPI   '/'      ;check 0-9, A-F
   JC   HEXABORT
   CPI   'F'+1
   JNC   HEXABORT
   CALL   ASBIN      ;Convert to binary
   ORA   B      ;add in the first digit
   ORA   A      ;To return NC
   RET
HEXABORT:
   STC         ;Set Carry flag
   RET

;------------------------------------------------------------------------------   
;Get a character from the keyboard, convert to uppercase and echo it
;------------------------------------------------------------------------------   

GETCMD:   CALL   ZCI      ;Get character
   CALL   UPPER
   CPI   ESC
   RZ         ;Don't echo an ESC
  IF NOT CPM
   PUSH   PSW      ;Save state of registers
   PUSH   B
    MOV   C, A
   CALL   ZCO      ;Echo it
   POP   B
   POP   PSW      ;Retrieve original state
  ENDIF
   RET

;------------------------------------------------------------------------------   
;Convert lowercase to uppercase
;------------------------------------------------------------------------------   

UPPER:   CPI   'a'      ;Must be >= lowercase a
   RC         ;else return as-is
   CPI   'z'+1      ;Must be <= lowercase z
   RNC         ;else return as-is
   SUI   'a'-'A'      ;Subtract lowercase bias
   RET

ASBIN:   SUI   30H       ;ASCII to binary conversion
   CPI   0AH
   RM
   SUI   07H
   RET

;------------------------------------------------------------------------------   
;Print a hexdump of the data in the 512 byte buffer starting at [HL]
;------------------------------------------------------------------------------   

HEXDUMP:
   PUSH   PSW      ;Save everything
   PUSH   B
   PUSH   D         
   PUSH   H
   
   CALL   ZCRLF      ;CR/LF first
   MVI   D, 32      ;Print 32 lines total
   MVI   B, 16      ;16 characters across
   SHLD   mStartLineHex   ;Save buffer location for ASCII display below
   LXI   H, 0
   SHLD   mBYTE$COUNT
   
SF172:   CALL   ZCRLF
   LHLD   mBYTE$COUNT
   MOV   A, H
   CALL   PHEX      ;Print byte count in sector
   MOV   A, L
   CALL   PHEX      
   PUSH   D
   LXI   D, 16
   DAD   D
   POP   D
   SHLD   mBYTE$COUNT   ;Store for next time
   CALL   BLANK
   LHLD   mStartLineHex
   SHLD   mStartLineASCII   ;Store for ASCII display below

SF175:   MOV   A, M
   CALL   LBYTE      ;Display [A] on CRT/LCD
   INX   H
   DCR   B
   JNZ   SF175
   SHLD   mStartLineHex   ;Save for next line later
   CALL   ShowAscii   ;Now translate to ASCII and display
   MVI   B, 16      ;16 characters across for next line
   DCR   D
   JNZ   SF172      ;Have we done all 32 lines

   CALL   ZCRLF
   POP   H      ;Get back original registers
   POP   D
   POP   B
   POP   PSW
   RET
   
ShowAscii:         ;Show as ASCII info
   LHLD   mStartLineASCII
   MVI   B, 16      ;16 ASCII characters across
XF172:   CALL   BLANK      ;Send a space character
   CALL   BLANK
XF175:   MOV   A, M
   ANI   7FH
   CPI   ' '       ;Filter out control characters
   JNC   XT33
XT22:   MVI   A, '.'
XT33:   CPI   07CH
   JNC   XT22
   MOV   C, A      ;Setup to send
   PUSH   B
   CALL   ZCO
   POP   B
   INX   H      ;Next position in buffer
   DCR   B
   JNZ   XF175
   RET

BLANK:   PUSH   B
   PUSH   H
   MVI   C, ' '
   CALL   ZCO
   POP   H
   POP   B
   RET

LBYTE:   PUSH   PSW
   RRC
   RRC
   RRC
   RRC
   CALL   SF598
   POP   PSW

SF598:   CALL   ZCONV
   RET

;------------------------------------------------------------------------------   
;IDE Drive BIOS Routines written in a format that can be used directly with CPM
;------------------------------------------------------------------------------   

IDEinit:         ;Initialize the 8255 and drive then do a hard reset
  IF VERBOSE
   LXI   D, INITDRIVE
   CALL   PSTRING
  ENDIF
   MVI   A, READcfg8255   ;Config 8255 chip (10010010B)
   OUT   IDEportCtrl   ;for READ mode
            
   MVI   A, IDErstline   ;Hard reset the disk drive
   OUT   IDEportC   ;Some CF cards are sensitive to reset pulse width
   MVI   B, 20H      ;Symptom is incorrect data back from a sector read

ResetDelay:
   DCR   B
   JNZ   ResetDelay   ;Delay (reset pulse width)

   XRA   A
   OUT   IDEportC   ;No control lines asserted (just bit 7 of port C)
   CALL   DELAY$SHORT   ;Short Delay

   MVI   D,11100000b   ;Data for IDE SDH reg (512byte, LBA, single drive, hd 0)
            ;For Trk, Sec, Head (non LBA) use 10100000

   MVI   E,REGshd   ;00001110,(0EH) for CS0,A2,A1, 
   CALL   IDEwr8D      ;Write byte to select the MASTER device

   MVI   B, 02H      ;Delay time for hard disks to get up to speed (2s)

WaitInit:
  IF VERBOSE
   LXI   D, DISKSTATUS   ;Print initialization status message
   CALL   PSTRING
  ENDIF
   MVI   E, REGstatus   ;Get status after initilization
   CALL   IDErd8D      ;Check Status (info in [D])
   MOV   A, D
  IF VERBOSE
   CALL   PHEX      ;Print drive initialization status
   CALL   ZPERCRLF
  ENDIF
   ANI   80H
   RZ         ;Return. We'll check for errors when we get back
   MVI   A, 2
   CALL   DELAY$LONG   ;Long delay, drive has to get up to speed
   DCR   B
   JNZ   WaitInit
   XRA   A
   DCR   A
   RET         ;Return NZ. We'll check for errors when we get back
   
DELAY$LONG:         ;Long delay (Seconds)
   STA   mDELAYStore
   PUSH   B
   LXI   B, 0FFFFH
DELAY2:   LDA   mDELAYStore
DELAY1:   DCR   A
   JNZ   DELAY1
   DCX   B
   MOV   A, C
   ORA   B
   JNZ   DELAY2
   POP   B
   RET

DELAY$SHORT:         ;Short delay (32ms)
   MVI   A, 40
DELAY3:   MVI   B, 0
M0:   DCR   B
   JNZ   M0
   DCR   A
   JNZ     DELAY3
   RET

;------------------------------------------------------------------------------   
;Sector Read
;------------------------------------------------------------------------------   

READSECTOR:         ;Read a sector, specified by the 3 bytes in LBA
            ;Z on success, NZ call error routine if problem

   CALL   wrlba      ;Tell which sector we want to read from.
            ;Translate first in case of an error, otherewise
            ;we will get stuck on bad sector
 
   CALL   IDEwaitnotbusy   ;Make sure drive is ready
   JC   SHOWerrors   ;Returned with NZ set if error

   MVI   D, COMMANDread
   MVI   E, REGcommand
   CALL   IDEwr8D      ;Send sec read command to drive.
   CALL   IDEwaitdrq   ;Wait until it's got the data
   JC   SHOWerrors
      
   LHLD     mDMA      ;DMA address
   MVI   B, 0      ;Read 512 bytes to [HL]

MoreRD16:
   MVI   A, REGdata   ;REG register address
   OUT   IDEportC   

   ORI   IDErdline   ;08H+40H, Pulse RD line
   OUT   IDEportC   

   IN   IDEportA   ;Read the lower byte first
   MOV   M, A
   INX   H
   IN   IDEportB   ;Then read the upper byte
   MOV   M, A
   INX   H
   
   MVI   A, REGdata   ;Deassert RD line
   OUT   IDEportC
   DCR   B
   JNZ   MoreRD16

   MVI   E, REGstatus
   CALL   IDErd8D
   MOV   A, D
   ANI   1H
   CNZ   SHOWerrors   ;If error display status
   RET

;------------------------------------------------------------------------------   
;Sector Write
;------------------------------------------------------------------------------   

WRITESECTOR:         ;Write a sector, specified by the 3 bytes in LBA
            ;Z on success, NZ to error routine if problem

   CALL   wrlba      ;Tell which sector we want to read from.
            ;Translate first in case of an error, otherewise
            ;we will get stuck on bad sector

   CALL   IDEwaitnotbusy   ;Make sure drive is ready
   JC   SHOWerrors

   MVI   D, COMMANDwrite
   MVI   E, REGcommand
   CALL   IDEwr8D      ;Tell drive to write a sector
   CALL   IDEwaitdrq   ;Wait unit it wants the data
   JC   SHOWerrors

   LHLD    mDMA
   MVI   B, 0

   MVI   A, WRITEcfg8255
   OUT   IDEportCtrl
   
WRSEC1:   MOV   A, M
   INX   H
   OUT   IDEportA   ;Write the lower byte first
   MOV   A, M
   INX   H
   OUT   IDEportB   ;Then high byte on B

   MVI   A, REGdata
   PUSH   PSW
   OUT   IDEportC   ;Send write command
   ORI   IDEwrline   ;Send WR pulse
   OUT   IDEportC
   POP   PSW
   OUT   IDEportC
   DCR   B
   JNZ   WRSEC1
   
   MVI   A, READcfg8255   ;Set 8255 back to read mode
   OUT   IDEportCtrl   

   MVI   E, REGstatus
   CALL   IDErd8D
   MOV   A, D
   ANI   1H
   CNZ   SHOWerrors   ;If error display status
   RET

;------------------------------------------------------------------------------   
;Write Logical Block Address (LBA) mode
;------------------------------------------------------------------------------   

wrlba:
   LDA   mSEC      ;LBA mode low sectors go directly
   INR   A      ;Sectors are numbered 1 -- MAXSEC
   STA   mDRIVE$SEC   ;For Diagnostic Display Only
   MOV   D, A
   MVI   E, REGsector   ;Send info to drive
   CALL   IDEwr8D
            
   LHLD   mTRK

   MOV   A, L

   STA   mDRIVE$TRK
   MOV   D, L      ;Send Low TRK#
   MVI   E, REGcylinderLSB
   CALL   IDEwr8D

   MOV   A, H

   STA   mDRIVE$TRK+1
   MOV   D, H      ;Send High TRK#
   MVI   E, REGcylinderMSB
   CALL   IDEwr8D

   MVI   D, 1      ;For now, one sector at a time
   MVI   E, REGseccnt
   CALL   IDEwr8D
   RET

;------------------------------------------------------------------------------   
;Wait for drive to come ready
;------------------------------------------------------------------------------   

IDEwaitnotbusy:         ;Drive READY if status = 01000000
   MVI   B, 0FFH
   MVI   A, 0FFH      ;Delay must be above 80H, longer for slow drives
   STA   mDELAYStore

MoreWait:
   MVI   E, REGstatus   ;Wait for RDY bit to be set
   CALL   IDErd8D
   MOV   A, D
   ANI   11000000B
   XRI   01000000B
   JZ   DoneNotbusy
   DCR   B   
   JNZ   MoreWait
   LDA   mDELAYStore   ;Check timeout delay
   DCR   A
   STA   mDELAYStore
   JNZ   MoreWait

   STC         ;Set carry to indicate an error
   ret
DoneNotBusy:
   ORA   A      ;Clear carry it indicate no error
   RET

;------------------------------------------------------------------------------   
;Wait for drive to assert data request (DRQ) line ready
;------------------------------------------------------------------------------   

IDEwaitdrq:
   MVI   B, 0FFH
   MVI   A, 0FFH      ;Delay must be above 80H, longer for slow drives
   STA   mDELAYStore

MoreDRQ:
   MVI   E, REGstatus   ;Wait for DRQ bit to be set
   CALL   IDErd8D
   MOV   A, D
   ANI   10001000B
   CPI   00001000B
   JZ   DoneDRQ
   DCR   B
   JNZ   MoreDRQ
   LDA   mDELAYStore   ;Check timeout delay
   DCR   A
   STA   mDELAYStore
   JNZ   MoreDRQ
   STC         ;Set carry to indicate error
   RET
DoneDRQ:
   ORA   A      ;Clear carry
   RET         ;Return drive status in A

;------------------------------------------------------------------------------   
;Clear the ID buffer
;------------------------------------------------------------------------------   

CLEAR$ID$BUFFER:
   LXI   H, IDBuffer
   LXI   B, 512
CLEAR2:   MVI   A, ' '
   MOV   M, A
   INX   H
   DCX   B
   MOV   A, C
   ORA   B
   JNZ   CLEAR2
   
   LXI   H, IDBuffer   ;Zero for cylinder, heads, sectors
   LXI   B, 14
CLEAR3:   MVI   A, 0
   MOV   M, A
   INX   H
   DCX   B
   MOV   A, C
   ORA   B
   JNZ   CLEAR3
   RET

;------------------------------------------------------------------------------   
; Low Level 8 bit R/W to the drive controller. These are the routines that talk
; directly to the drive controller registers, via the 8255 chip. 
; Note the 16 bit I/O to the drive (which is only for SEC R/W) is done directly
; in the routines READSECTOR & WRITESECTOR for speed reasons.
;------------------------------------------------------------------------------   

;------------------------------------------------------------------------------   
;Read One Byte
;------------------------------------------------------------------------------   

IDErd8D:            ;Read 8 bits from IDE register in [E],
   MOV   A, E         ;and return info in [D]
   OUT   IDEportC      ;Drive address onto control lines

   ORI   IDErdline      ;RD pulse pin (40H)
   OUT   IDEportC      ;Assert read pin

   IN   IDEportA
   MOV   D, A         ;Return with data in [D]

   MOV   A, E
   OUT   IDEportC      ;Deassert RD pin

   XRA   A
   OUT   IDEportC      ;Zero all port C lines
   RET

;------------------------------------------------------------------------------   
;Write One Byte
;------------------------------------------------------------------------------   

IDEwr8D:            ;Write Data in [D] to IDE register [E]
   MVI   A, WRITEcfg8255      ;Set 8255 to write mode
   OUT   IDEportCtrl

   MOV   A, D         ;Get data put it in 8255 A port
   OUT   IDEportA

   MOV   A, E         ;Select IDE register
   OUT   IDEportC

   ORI   IDEwrline      ;Lower WR line
   OUT   IDEportC
   
   MOV   A, E         ;Raise WR line
   OUT   IDEportC      ;Deassert RD pin

   XRA   A         ;Deselect all lines including WR line
   OUT   IDEportC

   MVI   A, READcfg8255      ;Config 8255 chip, read mode on return
   OUT   IDEportCtrl
   RET

;------------------------------------------------------------------------------   
;This code is written to reside and run from 0H.  To re-introduce the CPMLDR,
;it must be copied from where it is stored in high memory and relocated to 100H
;in RAM, which overwrites this program.
;------------------------------------------------------------------------------   

CPM$MOVE$CODE
   LXI   H, BUFFER
   LXI   D, 100H
   LXI   B, (12*512)
   LDIR
   JMP   100H
CPM$MOVE$CODE$END:

;------------------------------------------------------------------------------   
;
;COMMAND BRANCH TABLE
;
;------------------------------------------------------------------------------   

TBL:   DW  DRIVE$0   ; "A"  Select Drive 0
   DW  DRIVE$1   ; "B"  Select Drive 1
   DW  CPMBOOT   ; "C"  LOAD CPM (if present)
   DW  DISPLAY   ; "D"  Sector contents display: ON/OFF
   DW  RAMCLEAR  ; "E"  Clear RAM buffer
   DW  FORMAT    ; "F"  Format current disk
   DW  RESTORE   ; "G"  Restore backup
   DW  BACKUP    ; "H"  Backup partition
   DW  NEXT$SECT ; "I"  Next Sector
   DW  PREV$SEC  ; "J"  Previous sector
   DW  ERROR     ; "K" 
   DW  SET$LBA   ; "L"  Set LBA value (Set Track, sector) 
   DW  ERROR     ; "M" 
   DW  POWER$DOWN; "N"  Power down hard disk command
   DW  ERROR     ; "O" 
   DW  ERROR     ; "P" 
   DW  ERROR     ; "Q" 
   DW  READ$SEC  ; "R"  Read sector to data buffer
   DW  SEQ$RD    ; "S"  Sequental sec read and display contents
   DW  ERROR     ; "T" 
   DW  POWER$UP  ; "U"  Power up hard disk command
   DW  N$RD$SEC  ; "V"  Read N sectors
   DW  WRITE$SEC ; "W"  Write data buffer to current sector
   DW  N$WR$SEC  ; "X"  Write N sectors
   DW  COPY$AB   ; "Y"  Copy Drive 0 to Drive 1
   DW  VERIFY$AB ; "Z"  Verify Drive 0 = Drive 1

;------------------------------------------------------------------------------   
;
;String constants - Messages generated by this program
;
;------------------------------------------------------------------------------

SIGN$ON:   DB   CR,LF,'IDE Disk Drive Utility Program 12/01/2022 (v2.9b)',CR,LF,LF,'$'
SEL0MSG      DB   'Selecting first IDE drive.',CR,LF,'$'
SEL1MSG      DB   'Selecting second IDE drive.',CR,LF,'$'
INITDRIVE   DB   'Initializing drive.  $'
READING$ID   DB   'Reading drive ID.  $'
GETTING$ID   DB   'Getting drive ID...',CR,LF,'$'
DISKSTATUS   DB   'Status is $'
INIT$0$ERROR:   DB   'Initialization of First Drive failed. Aborting Program.',BELL,CR,LF,LF,'$'
INIT$1$ERROR   DB   'Initialization of Second Drive failed. (Possibly not present).',BELL,CR,LF,LF,'$'
ID$ERROR:   DB   'Error obtaining Drive ID.',BELL,CR,LF,'$'
INIT$DR$OK:   DB   'Drive Initialized OK.',CR,LF,LF,'$'
BAD$DRIVE:   DB   CR,LF,'First Drive ID Information appears invalid.',CR,LF
      DB   'Aborting program.',BELL,CR,LF,LF,'$'
DRIVE0$INFO:   DB   '------------ Drive 0 -------------',CR,LF,'$'
DRIVE1$INFO:   DB   '------------ Drive 1 -------------',CR,LF,'$'
msgmdl:      DB   'Model: $'
msgsn:      DB   'S/N:   $'
msgrev:      DB   'Rev:   $'
msgcy:      DB   'Cyl: $'
msghd:      DB   ', Hd: $'
msgsc:      DB   ', Sec: $'
msgCPMTRK:   DB   'CPM TRK = $'
msgCPMSEC:   DB   ' CPM SEC = $'
msgLBA:      DB   '  (LBA = 00$'
MSGBracket   DB   ')$'
DRIVE$0$MSG   DB   CR,LF,LF,'  >>> DRIVE #0 <<<$'
DRIVE$1$MSG   DB   CR,LF,LF,'  >>> DRIVE #1 <<<$'
CMD$STRING1:    DB   '  IDE Board Diagnostic MAIN MENU',CR,LF,LF
      DB   '(L) Set LBA value    (R) Read Sector to Buffer (W) Write Buffer '
      DB   'to Sector',CR,LF
      DB   '(D) Set Display ON   (S) Sequental Sec Read    (F) Format Disk',CR,LF
      DB   '(V) Read N Sectors   (X) Write N Sectors       (H) Backup disk',CR,LF
      DB   '(G) Restore Backup   (I) Next Sector           '
      DB   '(J) Previous Sector',CR,LF
      DB   '(U) Power Up         (N) Power Down            (C) Boot CPM',CR,LF
      DB   '(A) Select Drive 0   (B) Select Drive 1        '
      DB   '(E) Clear Sector Buffer',CR,LF
      DB   '(Y) Copy d0 to d1    (Z) Verify d0 = d1        (ESC) Quit',CR,LF
      DB   LF,'Current settings: $'
CMD$STRING2:    DB   '      IDE Board Diagnostic MAIN MENU',CR,LF,LF
      DB   '(L) Set LBA value    (R) Read Sector to Buffer (W) Write Buffer '
      DB   'to Sector',CR,LF
      DB   '(D) Set Display OFF  (S) Sequental Sec Read    (F) Format Disk',CR,LF
      DB   '(V) Read N Sectors   (X) Write N Sectors       (H) Backup disk',CR,LF
      DB   '(G) Restore Backup   (I) Next Sector           '
      DB   '(J) Previous Sector',CR,LF
      DB   '(U) Power Up         (N) Power Down            (C) Boot CPM',CR,LF
      DB   '(A) Select Drive 0   (B) Select Drive 1        '
      DB   '(E) Clear Sector Buffer',CR,LF
      DB   '(Y) Copy d0 to d1    (Z) Verify d0 = d1        (ESC) Quit',CR,LF
      DB   LF,'Current settings: $'
Prompt:      DB   CR,LF,LF,'Please enter command > $'
Response:   DB   CR,LF,'Command received:      $'
msgsure:   DB   CR,LF,'Warning: this will change data on the drive, '
      DB   'are you sure? (Y/N)...$'
msgrd:      DB   CR,LF,'Sector Read OK',CR,LF,'$'
msgwr:      DB   CR,LF,'Sector Write OK',CR,LF,'$'
GET$LBA:   DB   'Enter CPM style TRK & SEC values (in hex).',CR,LF,'$'
SEC$RW$ERROR   DB   'Drive Error, Status Register = $'
ERR$REG$DATA   DB   'Drive Error, Error Register = $'
ENTER$SECL   DB   'Starting sector number,(xxH) = $'
ENTER$TRKL   DB   'Track number (LOW byte, xxH) = $'
ENTER$TRKH   DB   'Track number (HIGH byte, xxH) = $'
ENTER$HEAD   DB   'Head number (01-0F) = $'
ENTER$COUNT   DB   'Number of sectors to R/W = $'
DRIVE$BUSY   DB   'Drive Busy (bit 7) stuck high.   Status = $'
DRIVE$NOT$READY   DB   'Drive Ready (bit 6) stuck low.  Status = $'
DRIVE$WR$FAULT   DB   'Drive write fault.    Status = $'
UNKNOWN$ERROR   DB   'Unknown error in status register.   Status = $'
BAD$BLOCK   DB   'Bad Sector ID.    Error Register = $'
UNRECOVER$ERR   DB   'Uncorrectable data error.  Error Register = $'
READ$ID$ERROR   DB   'Error setting up to read Drive ID',CR,LF,'$'
SEC$NOT$FOUND   DB   'Sector not found. Error Register = $'
INVALID$CMD   DB   'Invalid Command. Error Register = $'
TRK0$ERR   DB   'Track Zero not found. Error Register = $'
UNKNOWN$ERROR1   DB   'Unknown Error. Error Register = $'
CONTINUE$MSG   DB   CR,LF,'To Abort enter ESC. Any other key to continue. $'
FORMAT$MSG   DB   'FORMAT DISK. Fill all sectors with E5'
      DB   60H,'s on the CURRENT drive/CF card.$'
ReadN$MSG   DB   CR,LF,'Read multiple sectors from current disk/CF card to RAM buffer.'
      DB   CR,LF,'How many 512 byte sectores (xx HEX):$'
WriteN$MSG   DB   CR,LF,'Write multiple sectors RAM buffer CURRENT disk/CF card.'
      DB   CR,LF,'How many 512 byte sectores (xx HEX):$'
ReadingN$MSG   DB   CR,LF,'Reading Sector at: $'
WritingN$MSG   DB   CR,LF,'Writing Sector at: $'
msgErr      DB   CR,LF,'Sorry, that was not a valid menu option!$'
FormatDone   DB   CR,LF,'Disk Format Complete.',CR,LF,'$'
BackupDone   DB   CR,LF,'Disk partition copy complete.',CR,LF,'$'
CopyMsg      DB   CR,LF,'Copy disk partition to a second area on disk (CF card).'
      DB   CR,LF,'>>> This assumes that tracks greater than MAXTRK '
      DB   '(for CPM, 0FFH) are unused <<<'
      DB   CR,LF,'>>> on this disk. Be sure you have nothing in this '
      DB   '"Backup partition area". <<<'
      DB   CR,LF,BELL,'Warning: This will change data in the partition area, '
      DB   'are you sure? (Y/N)...$ '
AtEnd      DB   CR,LF,'At end of disk partition!',CR,LF,'$'
RBackup$MSG   DB   'Reading track: $'
WBackup$MSG   DB   'H. Writing track: $'
H$Msg      DB   'H$'
RestoreMsg   DB   CR,LF,'Restore disk with data from backup partition on disk (CF card).'
      DB   CR,LF,BELL,'Warning: This will change data on disk, '
      DB   'are you sure? (Y/N)...$ '
RestoreDone   DB   CR,LF,'Restore of disk data from backup partition complete.',CR,LF,'$'
RANGE$MSG   DB   CR,LF,'Sector value out of range.',CR,LF,'$'
CPM$ERROR   DB   CR,LF,'Error reading CPMLDR.',CR,LF,'$'
CPM$ERROR1   DB   CR,LF,'Data error reading CPMLDR. (The first byte loaded was not 31H).',CR,LF,'$'
MOVE$REQUEST   DB   CR,LF,'The CPMLDR image is now at 3000H in RAM. '
      DB   'To boot CPM you will have to'
      DB   CR,LF,'overwrite this program at 100H. Do you wish to do so (Y/N)...$'
SETA$MSG   DB   CR,LF,'Current Drive is now #0 (Yellow LED)$'
SETB$MSG   DB   CR,LF,'Current Drive is now #1 (Green LED)$'
FILL$MSG   DB   CR,LF,'Sector buffer in RAM filled with 0',27H,'s$'      
DiskCopyMsg   DB   CR,LF,'Copy disk partition of Drive 0 to Drive 1 (CF card).'
      DB   CR,LF,BELL,'Warning: This will delete all data on Drive 1, '
      DB   'are you sure? (Y/N)...$ '
CopyDone   DB   CR,LF,'Disk copy of CPM disk 0 to 1 complete.',CR,LF,'$'
CopyTrk$MSG   DB   'Copying track: $'
DiskVerifyMsg   DB   CR,LF,'Verify disk partition Drive 0 = Drive 1 (CF card).$'
VerifyTrk$MSG   DB   'Verifying track: $'
VerifyDone   DB   CR,LF,'Verify CPM disk 0 = 1 complete.',CR,LF,'$'
Verify$ERR   DB   CR,LF,BELL,'Verify error on Track $'
SEC$Msg      DB   'H  Sector $'

;------------------------------------------------------------------------------   
;RAM usage
;------------------------------------------------------------------------------   

RAMAREA      DB   '           RAM STORE AREA -------->'
mDMA      DW   buffer
mDRIVE$SEC   DB   0H
mDRIVE$TRK   DW   0H
mDisplayFlag   DB   0FFH      ;Display of sector data initially ON
mSEC      DW   0H
mTRK      DW   0H
mSEC1      DW   0H      ;For disk partition copy
mTRK1      DW   0H
mSEC2      DW   0H
mTRK2      DW   0H
mStartLineHex   DW   0H
mStartLineASCII   DW   0H
mBYTE$COUNT   DW   0H
mSECTOR$COUNT   DW   0H
mDELAYStore   DB   0H
mCURRENT$DRIVE   DB   0H

      DB   '          Start of ID buffer-->'
IDbuffer   DS   512
      DB   '<--End of ID buffer            '

      ORG   BUFFER$ORG

BUFFER:      DB   76H
      DB   '<--Start buffer area'
      DS   476
      DB   'End of buffer-->'

BUFFER2:   DB   '<--Start buffer2 area'
      DS   476
      DB   'End of buffer2-->'

      DS   100H

STACK      DW   0H

;END
Wayne Parham
 
Posts: 240
Joined: March 18th, 2022, 3:01 pm

Re: IDE interface for Altair 8800c

Postby Wayne Parham » December 10th, 2022, 10:43 am

Getting closer.

See below the latest version of IDEutil. I'm calling it v3.0x to distinguish it from the fully tested version, which will drop the "x." Just think of that as "experimental."

It has several changes from the last one I uploaded, which was a derivative of v2.9. This one has David Fry's wrlba function for contiguous sector access. It also has numerous bugfixes from John Monahan. I diffed myIDE 2.9 and 3.0 to find all the updates, and incorporated them in this version of IDEutil. But I purposely removed the facility to set sector size or to provide the non-contiguous original "holes" format. This code only supports to contiguous "no holes" format with 64 sectors per track.

I also diffed 3.0 and 3.1 but found very few differences. It's mostly re-arranging the menu choices. I'll look at it more closely and if I find more significant improvements, I'll add them. But for now, I'm mostly in a test/break/fix mode with IDEutil.

As far as that's concerned, the breaks that needed fixing were mostly either Z80 and/or assembler/macro related. In addition to the DJNZ instructions I found early on, I have also discovered BIT and LDIR instructions. I "cheated" on those by running MAC and Z80.LIB to see what opcodes it generated, and I inserted them into the source code using DB statements. That removed the errors, of course, but I still need to be sure the opcodes are valid 8080 instructions. I also found IF/ELSE/ENDIF directives that aren't supported by ASM, so I rewrote the directives to work with ASM. That got rid of all the build errors.
Wayne Parham
 
Posts: 240
Joined: March 18th, 2022, 3:01 pm

Re: IDE interface for Altair 8800c

Postby Wayne Parham » December 10th, 2022, 10:44 am

Code: Select all
;------------------------------------------------------------------------------
; Utility Program for IDE interface board
;   v3.0x         12/08/2022
;
; Wayne Parham        wayne@parhamdata.com
;
; The IDE interface board is expected to be configured for I/O base address 30H
; but can be changed by redefining IDE interface equates below.
;
; IDEutil.com can be built using the following commands:
;
; ASM IDEutil
; HEXCOM IDEutil
;
; This program is largely borrowed from John Monahan's "myIDE" used to  support
; the  IDE/CF v4 board sold by S100computers.com.   It is generally  compatible
; with  myIDE except for minor cosmetic changes.  The most significant  changes
; were  made to provide compatibility with the Digital Research ASM  assembler.
;
; Other credits should be given to Peter Faasse and to David Fry.  Peter Faasse
; described the 8255 interface that is implemented herein.  And David Fry wrote
; the wrlba function used here to translate CP/M track and sector addresses  to
; IDE LBA addresses.
;
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
;Build equates:
;------------------------------------------------------------------------------

FALSE      EQU   0
TRUE      EQU   NOT FALSE

CPM      EQU   TRUE   ;TRUE if using CPM, FALSE if loaded directly
DEBUG      EQU   TRUE   ;TRUE for error messages
VERBOSE      EQU   FALSE   ;TRUE for extended error messages
CPM$TRANSLATE   EQU   TRUE   ;Translate Trk, Sec, Head to CPM TRACK# & SEC#

;------------------------------------------------------------------------------
;Console equates:
;------------------------------------------------------------------------------

CONI      EQU   10H   ;Console input port
CONO      EQU   11H   ;Console output port

;------------------------------------------------------------------------------
;CP/M equates:
;------------------------------------------------------------------------------

RDCON      EQU   1   ;For CP/M I/O
WRCON      EQU   2
RESET$DISK   EQU   0DH   ;Reset all CPM disks
PRINT      EQU   9
CONST      EQU   11   ;Console stat
BDOS      EQU   5

;------------------------------------------------------------------------------
;Display control equates:
;------------------------------------------------------------------------------

SCROLL      EQU   01H   ;Set scroll direction UP
LF      EQU   0AH
CR      EQU   0DH
BS      EQU   08H   ;Back space (required for sector display)
PERIOD      EQU   2EH
BELL      EQU   07H
SPACE      EQU   20H
QUIT      EQU   11H   ;Turns off any screen enhancements
NO$ENHANCEMENT   EQU   17H   ;Turns off whatever is on
FAST      EQU   10H   ;High speed scrool
TAB      EQU   09H   ;Tab across
ESC      EQU   1BH
CLEAR      EQU   1CH   ;Clear line (Use 80 spaces if not available)

;------------------------------------------------------------------------------
;IDE interface equates:
;------------------------------------------------------------------------------
;Ports for 8255 chip. Change these to specify where your 8255 is addressed,
;The first three control which 8255 ports have the control signals,
;upper and lower data bytes.  The last one (IDEportCtrl), is for mode setting
;for the 8255 to configure its actual I/O ports (A,B & C). 
;
;Note most drives these days don't use the old Head, Track, Sector terminology.
;Instead we use "Logical Block Addressing" or LBA.  This is what we use below.
;LBA treats the drive as one continous set of sectors, 0,1,2,3,... 3124,...etc.
;However as seen below we need to convert this LBA to heads,tracks and sectors
;to be compatible with CPM & MSDOS.
;
;NOTE: If you have only one drive/CF card, be sure it is in drive #0.
;The IDE hardware gets confused if there is only a drive in slot #1.
;------------------------------------------------------------------------------

IDEportA   EQU   030H   ;Lower 8 bits of IDE interface (8255)
IDEportB   EQU   031H   ;Upper 8 bits of IDE interface
IDEportC   EQU   032H   ;Control lines for IDE interface
IDEportCtrl   EQU   033H   ;8255 configuration port
IDEDrive   EQU   034H   ;Bit 0 - 0 for drive 0 and 1 for drive 1

READcfg8255   EQU   10010010b ;Set 8255 IDEportC to out, IDEportA/B input
WRITEcfg8255   EQU   10000000b ;Set all three 8255 ports to output mode

;------------------------------------------------------------------------------
;IDE control lines for use with IDEportC. 
;------------------------------------------------------------------------------

IDEa0line   EQU   01H   ;direct from 8255 to IDE interface
IDEa1line   EQU   02H   ;direct from 8255 to IDE interface
IDEa2line   EQU   04H   ;direct from 8255 to IDE interface
IDEcs0line   EQU   08H   ;inverter between 8255 and IDE interface
IDEcs1line   EQU   10H   ;inverter between 8255 and IDE interface
IDEwrline   EQU   20H   ;inverter between 8255 and IDE interface
IDErdline   EQU   40H   ;inverter between 8255 and IDE interface
IDErstline   EQU   80H   ;inverter between 8255 and IDE interface

;------------------------------------------------------------------------------
;Symbolic constants for the IDE drive registers
;------------------------------------------------------------------------------

REGdata      EQU   IDEcs0line
REGerr      EQU   IDEcs0line + IDEa0line
REGseccnt   EQU   IDEcs0line + IDEa1line
REGsector   EQU   IDEcs0line + IDEa1line + IDEa0line
REGcylinderLSB   EQU   IDEcs0line + IDEa2line
REGcylinderMSB   EQU   IDEcs0line + IDEa2line + IDEa0line
REGshd      EQU   IDEcs0line + IDEa2line + IDEa1line
REGcommand   EQU   IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGstatus   EQU   IDEcs0line + IDEa2line + IDEa1line + IDEa0line
REGcontrol   EQU   IDEcs1line + IDEa2line + IDEa1line
REGastatus   EQU   IDEcs1line + IDEa2line + IDEa1line + IDEa0line

;------------------------------------------------------------------------------
;IDE Command Constants.  These should never change.
;------------------------------------------------------------------------------

COMMANDrecal   EQU   10H
COMMANDread   EQU   20H
COMMANDwrite   EQU   30H
COMMANDinit   EQU   91H
COMMANDid   EQU   0ECH
COMMANDspindown   EQU   0E0H
COMMANDspinup   EQU   0E1H

;------------------------------------------------------------------------------
;IDE Status Register:
;------------------------------------------------------------------------------

;  bit 7: Busy   1=busy, 0=not busy
;  bit 6: Ready 1=ready for command, 0=not ready yet
;  bit 5: DF   1=fault occurred
;  bit 4: DSC   1=seek complete
;  bit 3: DRQ   1=data request ready, 0=not ready to xfer yet
;  bit 2: CORR   1=correctable error occurred
;  bit 1: IDX   vendor specific
;  bit 0: ERR   1=error occured

;------------------------------------------------------------------------------
;Disk equates:
;------------------------------------------------------------------------------

SEC$SIZE   EQU   512   ;Bytes per sector
MAXSEC      EQU   3FH   ;Sectors per track
MAXTRK      EQU   0FFH   ;CPM3 allows up to 8MG so 256 "tracks"
BUFFER$ORG   EQU   3000H   ;<----- Will place all sector data here

CPM$BOOT$COUNT   EQU   12   ;Allow up to 12 CPM sectors for CPMLDR
CPMLDR$ADDRESS   EQU   BUFFER$ORG

;------------------------------- INITIALIZATION -------------------------------   

   ORG   100H      ;<--- For CPM

begin:
   LXI   SP, STACK
   LXI     D, SIGN$ON   ;Print welcome message
   CALL   PSTRING
  IF VERBOSE
   LXI   D, SEL0MSG   ;Print select drive 0 message
   CALL   PSTRING
  ENDIF
   CALL   SELECT0      ;Select the first drive
  IF VERBOSE
   LXI   D, INITDRIVE   ;Print initialization message
   CALL   PSTRING
  ENDIF
   CALL   IDEinit      ;Initialize the board and drive 0

   JZ   INIT$OK      ;Continue on Zero
   
   LXI   D, INIT$0$ERROR   ;Non-zero is error, probably no drive
   CALL   PSTRING
   JMP   ABORT
   
INIT$OK:         ;Get drive 0 identification info         
   CALL   driveid
   JZ   INIT$OK1

   LXI   D, ID$ERROR   ;End program on error
   CALL   PSTRING
   JMP   ABORT

INIT$OK1:         ;Check sector count
   LXI     H, IDbuffer + 12
   MOV   A, M      ;(High Byte)
   ORA   A
   JNZ   INIT$OK2
   INX   H
   MOV   A, M      ;(Low Byte)
   ORA   A
   JNZ   INIT$OK2   ;Looks like we have a valid IDE drive
   
   LXI   D, BAD$DRIVE   ;Zero sectors means something's wrong
   CALL   PSTRING
   JMP   ABORT      ;No drive #0 so abort

INIT$OK2:         ;Print drive 0 info
  IF VERBOSE
   LXI   D, DRIVE0$INFO
   CALL   PSTRING
   LXI     D, msgmdl   ;Drive name   
   CALL   PSTRING
   LXI     H, IDbuffer + 54
   MVI   B, 20      ;Character count in words
   CALL   printSwap   ;Print [HL], [B] X 2 characters
   CALL   ZCRLF

   LXI     D, msgsn   ;Serial number
   CALL   PSTRING
   LXI     H, IDbuffer + 20
   MVI   B, 10      ;Character count in words
   CALL   printText
   CALL   ZCRLF

   LXI     D, msgrev   ;Firmware revision string
   CALL   PSTRING
   LXI     H, IDbuffer + 46
   MVI   B, 4      ;Character count in words
   CALL   printSwap
   CALL   ZCRLF

   LXI     D, msgcy   ;Drive specs (cyl/hd/sect)
   CALL   PSTRING
   LXI     H, IDbuffer + 2
   CALL   printparm
   LXI     D, msghd
   CALL   PSTRING
   LXI     H, IDbuffer + 6
   CALL   printparm
   LXI     D, msgsc
   CALL   PSTRING
   LXI     H, IDbuffer + 12
   CALL   printparm
   CALL   ZCRLF

   LXI   D, msgLBAsup1   ;First part of LBA support message
   CALL   PSTRING
   LDA   IDbuffer+98+1   ;Bits 15-10 reserved, 9 LBA, 8 DMA
   ANI   02H
   JNZ   ID$SUP0      ;LBA is supported
   LXI   D, msgLBAnot   ;LBA is not supported
   CALL   PSTRING
ID$SUP0:
   LXI   D, msgLBAsup2
   CALL   PSTRING
  ENDIF
INIT$OK3:         ;Move to second drive
  IF VERBOSE
   CALL   ZCRLF
   LXI   D, SEL1MSG   ;Print select drive 1 message
   CALL   PSTRING
  ENDIF
   CALL   SELECT1      ;Select drive 1
  IF VERBOSE
   LXI   D, INITDRIVE   ;Print initialization message
   CALL   PSTRING
  ENDIF
   CALL   IDEinit      ;Initialize the second drive
   JZ   INIT$OK4

   LXI   D, INIT$1$ERROR   ;Non-zero is error, so print warning
   CALL   PSTRING
   XRA   A
   STA   mDriveBPresent   ;Clear flag to indicate drive 1 absense
   JMP   INIT$DONE

INIT$OK4:         ;Get drive 1 identification info
   CALL   driveid
   JZ   INIT$OK5

   LXI   D, ID$ERROR   ;On error, display message
   CALL   PSTRING
   JMP   INIT$DONE

INIT$OK5:         ;Check sector count
   LXI     H, IDbuffer + 12
   MOV   A, M      ;(High Byte)
   ORA   A
   JNZ   INIT$OK6
   INX   H
   MOV   A, M      ;(Low Byte)
   ORA   A
   JNZ   INIT$OK6   ;Looks like we have a valid IDE drive
BAD$DR1:   
   LXI   D, BAD$DRIVE   ;Zero sectors, so display error
   CALL   PSTRING
   JMP   INIT$DONE

INIT$OK6:         ;Print drive 1 info
  IF VERBOSE
   LXI   D, DRIVE1$INFO
   CALL   PSTRING
   LXI     D, msgmdl   ;Drive name   
   CALL   PSTRING
   LXI     H,IDbuffer + 54
   MVI   B, 20      ;Character count in words
   CALL   printSwap   ;Print [HL], [B] X 2 characters
   CALL   ZCRLF

   LXI     D, msgsn   ;Serial number
   CALL   PSTRING
   LXI     H, IDbuffer + 20
   MVI   B, 10      ;Character count in words
   CALL   printText
   CALL   ZCRLF

   LXI     D, msgrev   ;Firmware revision string
   CALL   PSTRING
   LXI     H, IDbuffer + 46
   MVI   B, 4      ;Character count in words
   CALL   printSwap
   CALL   ZCRLF

   LXI     D, msgcy   ;Drive specs (cyl/hd/sect)
   CALL   PSTRING
   LXI     H, IDbuffer + 2
   CALL   printparm
   LXI     D, msghd
   CALL   PSTRING
   LXI     H, IDbuffer + 6
   CALL   printparm
   LXI     D, msgsc
   CALL   PSTRING
   LXI     H, IDbuffer + 12
   CALL   printparm
   CALL   ZCRLF

   LXI   D,msgLBAsup1   ;First part of LBA support message
   CALL   PSTRING
   LDA   IDbuffer+98+1   ;Bits 15-10 reserved, 9 LBA, 8 DMA
   ANI   02H
   JNZ   ID$SUP1      ;LBA is supported
   LXI   D,msgLBAnot   ;LBA is not supported
   CALL   PSTRING
ID$SUP1:
   LXI   D,msgLBAsup2
   CALL   PSTRING
  ENDIF
   MVI   A, 1      ;Set flag to indicate drive 1 present
   STA   mDriveBPresent         

INIT$DONE:         ;Cleanup and enter main menu
   CALL   IDEinit      ;Re-initialize drive 1
   MVI   A, 0
   STA   mCURRENT$DRIVE   ;Select drive 0
   OUT   IDEDrive
   CALL   IDEinit      ;Re-initialize drive 0
   LXI   H, 0
   SHLD   mSEC      ;Default to track 0 and sector 0
   SHLD   mTRK
   LXI   H, buffer   ;Set DMA address to buffer
   SHLD   mDMA
   JMP   MAINLOOP   ;Display Main Menu

;------------------------------------------------------------------------------   

TERMINATE:         ;End program from ESC command
ABORT:            ;Controlled termination
   CALL   SELECT0
   CALL   ZCRLF
  IF CPM
   MVI   C, RESET$DISK   ;Reset all disks
   RET
  ENDIF         ;Return to CPM
  IF NOT CPM
   JMP   0F800H      ;Transfer control to Monitor ROM   
  ENDIF

;-------------------------------- MENU OPTIONS --------------------------------   

MAINLOOP:         ;Print main menu
   LDA   mCURRENT$DRIVE
   ORA   A
   JNZ   DRIVE$1$MENU
   LXI   D, DRIVE$0$MSG
   CALL   PSTRING
   JMP   Display0
DRIVE$1$MENU:
   LXI   D, DRIVE$1$MSG
   CALL   PSTRING
Display0:
   LDA   mDisplayFlag   ;Sector data display flag on or off
   ORA   A      ;NZ = on (Initially 0FFH so display on)
   JNZ     Display1
   LXI     D, CMD$STRING1   ;List command options (Turn display option on)
   JP   Display2
Display1:
   LXI     D, CMD$STRING2   ;List command options (Turn display option off)
Display2:
   CALL   PSTRING
   
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current track, sector, head
   
   LXI   D, Prompt   ;'>'
   CALL   PSTRING

;--------------------------------- USER INPUT ---------------------------------   
   
   CALL   GETCMD      ;Character Input
   CPI   ESC
   JZ   TERMINATE   ;End on ESC
   CPI   'A'
   JC   ERROR      ;Must be >= 'A'
   CPI   'Z'+1
   JNC   ERROR      ;Must be <= 'Z'
   CALL   ZCRLF

   SBI   'A'-1      ;Adjust to make 'A' keypress = 0
   ADD   A

   LXI   H, TBL      ;Offset into vector table
   ADD   L
   MOV   L, A
   MOV   A, M
   DB   03H      ;INX  HL

   MOV   E, M      ;Get selected function address
   INX   H
   MOV   D, M
   XCHG

   PCHL         ;Jump to command function address
   
;------------------------------ COMMAND FUNCTIONS -----------------------------   

READ$SEC:         ;Read Sector @ LBA to the RAM buffer
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

   CALL   READSECTOR

   JZ   main1b      ;Z means the sector read was OK
   CALL   ZCRLF
   JMP   MAINLOOP

main1b:   LXI     D, msgrd   ;Sector read OK
   CALL   PSTRING

   LDA   mDisplayFlag   ;Do we have display flag on or off
   ORA   A      ;NZ = on
   JZ   MAINLOOP
   LXI   H, buffer   ;Point to buffer. Show sector data flag is on
   SHLD   mDMA
   CALL   HEXDUMP      ;Show sector data
   JMP   MAINLOOP

WRITE$SEC:         ;Write data in RAM buffer to sector @ LBA
   LXI     D, msgsure   ;Are you sure?
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   main2c
   CALL   ZCRLF

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

   CALL   WRITESECTOR

   JZ   main2b      ;Z means the sector write was OK
   CALL   ZCRLF
   JMP   MAINLOOP
main2b:   LXI     D, msgwr   ;Sector written OK
   CALL   PSTRING
main2c: JMP   MAINLOOP

SET$LBA:         ;Set the logical block address
   LXI     D, GET$LBA   
   CALL   PSTRING
   CALL   ghex32lba   ;Get CPM style Track & Sector, put in RAM
   JC   main3b      ;Ret C set if abort/error
   CALL   wrlba      ;Update LBA on drive
main3b:   CALL   ZCRLF
   JMP   MAINLOOP

NEXT$SECT:
   LDA   mSEC
   CALL   CHK$SEC      ;Compare current to Max CPM Sector
   JZ   RANGE$ERROR   ;If equal, we are at max already
   INR   A      ;Otherwise, on to the next sector
   STA   mSEC
   CALL   wrlba      ;Update LBA on drive
   CALL   ZCRLF
   JMP   MAINLOOP

RANGE$ERROR:
   LXI     D, RANGE$MSG   
   CALL   PSTRING
   JMP   MAINLOOP
   
PREV$SEC:
   LDA   mSEC
   ORA   A
   JZ   RANGE$ERROR
   DCR   A
   STA   mSEC
   CALL   wrlba      ;Update LBA on drive
   CALL   ZCRLF
   JMP   MAINLOOP
   
POWER$UP:         ;Set the drive to spin up
   CALL   spinup
   JMP   MAINLOOP

POWER$DOWN:         ;Set the drive to spin down
   CALL   spindown
   JMP   MAINLOOP

DISPLAY:         ;Do we have display flag on or off
   LDA   mDisplayFlag   
   CMA         ;flip it
   STA   mDisplayFlag
   JMP   MAINLOOP   ;Update display and back to next menu command

SEQ$RD:            ;Do sequential reads
   CALL   SequentialReads
   JMP   MAINLOOP

DRIVE$0:
   CALL   SELECT0      ;Select drive 0
   LXI     D, SET0$MSG   
   CALL   PSTRING
   JMP   MAINLOOP

DRIVE$1:
   CALL   SELECT1      ;Select drive 1
   LXI     D, SET1$MSG   
   CALL   PSTRING
   JMP   MAINLOOP

RAMCLEAR:         ;Fill RAM buffer with 0's
   LXI   H, buffer   ;Point to buffer
   LXI   D, 512
CLEAR1:
   XRA   A      ;Fill area with 0's
   MOV   M, A
   INX   H
   DCX   D
   MOV   A, E
   ORA   D
   JNZ   CLEAR1
   LXI     D, FILL$MSG
   CALL   PSTRING
   JMP   MAINLOOP

CPMBOOT:         ;Boot CPM from IDE system tracks -- if present
   XRA   A      ;Load from track 0, sec 1, head 0 (always)
   STA   mTRK+1
   STA   mTRK
   MVI   A, 1      ;Sector 1
   STA   mSEC

   MVI   A, CPM$BOOT$COUNT ;Count of CPMLDR sectors  (12)
   STA   mSECTOR$COUNT
   LXI   H, CPMLDR$ADDRESS ;DMA address where the CPMLDR resides in RAM (100H)
   SHLD   mDMA

NextRCPM:
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current track, sector, head
   CALL   ZCRLF
   
   LHLD   mDMA
   CALL   READSECTOR   ;Read a sector
   SHLD   mDMA

   LDA   mSECTOR$COUNT
   DCR   A
   STA   mSECTOR$COUNT
   JZ   LOAD$DONE

   LHLD   mSEC
   INX   H
   SHLD   mSEC      ;Stay on track 0 in this special case
   JMP   NextRCPM

LOAD$DONE:
   MVI   E, REGstatus   ;Check the R/W status when done
   CALL   IDErd8D
   DB   0CBH, 0*8+D+40H   ;BIT  0, D
   JNZ   CPMLoadErr   ;Zero if no errors
   LXI   H, CPMLDR$ADDRESS
   MOV   A, M
   CPI   31H      ;EXPECT TO HAVE 31H @80H IE. LD SP,80H
   JNZ   CPMLoadErr1   ;Zero if no errors
   
   LXI   D, MOVE$REQUEST   ;Ask if we can move data to 100H
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, CPM$MOVE$CODE ;Need to move code out of the way.
   LXI   D, 0H
   LXI   B, (CPM$MOVE$CODE$END-CPM$MOVE$CODE)
   DB   0EDH, 0B0H   ;LDIR
   JMP   0H      ;Now jump here to move the CPMLDR (@3000H) to 100H
   
CPMLoadErr1:
   LXI   D, CPM$ERROR1   ;Drive data error
   CALL   PSTRING
   JMP   MAINLOOP
   
CPMLoadErr:
   LXI   D, CPM$ERROR   ;Drive Read Error
   CALL   PSTRING
   JMP   MAINLOOP

N$RD$SEC:         ;Read N sectors
   LXI   D, ReadN$MSG   ;No check for possible high RAM (CPM) overwrite
   CALL   PSTRING
   CALL   GETHEX
   JC   MAINLOOP   ;Abort if ESC (C flag set)
   
   STA   mSECTOR$COUNT   ;Store sector count
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

NextRSec:   
   LXI   D, ReadingN$MSG
   CALL   PSTRING
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current track, sector, head
   
   LHLD   mDMA
   CALL   READSECTOR
   SHLD   mDMA

   LDA   mSECTOR$COUNT
   DCR   A
   STA   mSECTOR$COUNT
   JZ   MAINLOOP
   
   LHLD   mSEC
   MOV   A, L
   CALL   CHK$SEC      ;Compare A to MAXSEC
   JZ   NextRZero   ;Already at max, reset to 0
   INX   H      ;Otherwise, on to next sector
   SHLD   mSEC   
   JMP   NextRSec

NextRZero:
   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC
   LHLD   mTRK      ;Bump to next track
   INX   H
   SHLD   mTRK
   MOV   A, L      ;0-FFH tracks (only)
   ORA   A      ;Set condition code for A (least 8 bits of track)
   JNZ   NextRSec
   
   LXI   D, AtEnd   ;Tell us we are at end of disk
   CALL   PSTRING
   JMP   MAINLOOP

N$WR$SEC:         ;Write N sectors
   LXI     D, msgsure   ;Are you sure?
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   main2c

   LXI   D, WriteN$MSG
   CALL   PSTRING
   CALL   GETHEX
   JC   MAINLOOP   ;Abort if ESC (C flag set)

   STA   mSECTOR$COUNT   ;Store sector count
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

NextWSec:   
   LXI   D, WritingN$MSG
   CALL   PSTRING
   CALL   wrlba      ;Update LBA on drive
   CALL   DISPLAYposition   ;Display current track, sector, head
   
   LHLD   mDMA      ;Actully, Sector/track values are already updated
   CALL   WRITESECTOR   ;in wrlba, but WRITESECTOR is used in multiple places.
   SHLD   mDMA      ;A repeat does no harm -- speed is not an issue here

   LDA   mSECTOR$COUNT
   DCR   A
   STA   mSECTOR$COUNT
   JZ   MAINLOOP
   
   LHLD   mSEC
   MOV   A, L
   CALL   CHK$SEC      ;Compare sector to MAXSEC
   JZ   NextWZero   ;Already at max sector - reset to 0
   INX   H
   SHLD   mSEC   
   JMP   NextWSec

NextWZero:
   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC
   LHLD   mTRK      ;Bump to next track
   INX   H
   SHLD   mTRK
   MOV   A, L      ;0-FFH tracks (only)
   ORA   A
   JNZ   NextWSec
   
   LXI   D, AtEnd   ;Tell us we are at end of disk
   CALL   PSTRING
   JMP   MAINLOOP

FORMAT:            ;Format (Fill sectors with E5)
   LXI   D, FORMAT$MSG
   CALL   PSTRING
   LXI     D, msgsure   ;Are you sure?
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP
   LXI   H, buffer   ;Fill buffer with E5's (512 of them)
   MVI   B, 0
Fill0:   MVI   A, 0E5H      ;<-- Sector fill character (E5 for CPM)
   MOV   M, A
   INX   H
   MOV   M, A
   INX   H
   DCR   B
   JNZ   Fill0
   CALL   ZCRLF

NEXT$FORMAT:
   LXI   H, buffer
   SHLD   mDMA
   CALL   WRITESECTOR   ;Will return error if there was one
   JZ   main9b      ;Z means the sector write was OK
   CALL   ZCRLF
   JMP   MAINLOOP
main9b:   CALL   ZEOL      ;Clear line cursor is on
   CALL   DISPLAYposition   ;Display actual current track, sector and head
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   WRNEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JZ   MAINLOOP
   CALL   ZCRLF
WRNEXTSEC1:
   LHLD   mSEC
   MOV   A, L      ;Current sector to A
   CALL   CHK$SEC      ;Are we already at max sector?
   JZ   NextFormatZero   ;Yes - set back to 0
   INX   H      ;No - bump the sector
   SHLD   mSEC      ;0 to MAXSEC CPM Sectors
   JMP   NEXT$FORMAT

NextFormatZero:
   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC
   LHLD   mTRK      ;Get current track
   MOV   A, L      ;Current track to A
   CPI   MAXTRK      ;Is it already at max?
   JZ   NextFormatDone   ;Yes - all done
   INX   H      ;Bump to next track
   SHLD   mTRK
   JMP   NEXT$FORMAT

NextFormatDone:
   LXI   D, FormatDone   ;Tell us we are all done.
   CALL   PSTRING
   JMP   MAINLOOP
            
BACKUP:            ;Backup the CPM partition to another area
   LXI   D, CopyMsg
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   mSEC
   SHLD   mSEC1
   SHLD   mSEC2      ;and on second partition
   SHLD   mTRK      ;and track 0
   SHLD   mTRK1
   LXI   H, MAXTRK+1   ;Start of backup partition
   SHLD   mTRK2

   CALL   ZCRLF
   CALL   ZCRLF
   
NextCopy1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, RBackup$MSG   ;for each track update display
   CALL   PSTRING
   LDA   mTRK1+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK1      ;Low TRK byte
   CALL   PHEX
   LXI   D, WBackup$MSG
   CALL   PSTRING
   LDA   mTRK2+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK2      ;Low TRK byte
   CALL   PHEX
   LXI   D, H$Msg
   CALL   PSTRING

NextCopy:   
   LDA   mSEC1
   STA   mSEC
   LHLD   mTRK1
   SHLD   mTRK
   CALL   wrlba      ;Update LBA on source drive

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA
   CALL   READSECTOR   ;Get sector data to buffer
   
   LDA   mSEC2
   STA   mSEC
   LHLD   mTRK2
   SHLD   mTRK
   CALL   wrlba      ;Update LBA on target drive
   
   LXI   H,buffer   ;Point to buffer
   SHLD   mDMA
   CALL   WRITESECTOR   ;Write buffer data to sector
   
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   BKNEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JZ   MAINLOOP

BKNEXTSEC1:
   LHLD   mSEC
   MOV   A, L      ;Sector number in A
   CALL   CHK$SEC      ;Check Sector is not at max
   JZ   BKNEXTZERO   ;It is at max already
   INX   H      ;Otherwise, bump sector
   SHLD   mSEC1
   SHLD   mSEC2   
   JMP   NextCopy

BKNEXTZERO:
   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC1
   SHLD   mSEC2
   
   LHLD   mTRK1      ;Get current track
   MOV   A, L      ;Into A
   CPI   MAXTRK      ;Already at max?
   JZ   BKNextDone   ;If so, we are done
   INX   H
   SHLD   mTRK1
   
   LHLD   mTRK2      ;Bump to next track
   INX   H
   SHLD   mTRK2
   JMP   Nextcopy1
   
BKNextDone:
   LXI   D, BackupDone   ;Tell us we are all done.
   CALL   PSTRING
   JMP   MAINLOOP

RESTORE:         ;Restore disk from backup partition
   LXI   D, RestoreMsg
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   mSEC
   SHLD   mSEC1
   SHLD   mSEC2      ;and on second partition
   SHLD   mTRK      ;and track 0
   SHLD   mTRK1
   LXI   H, MAXTRK+1   ;Start of backup partition
   SHLD   mTRK2

   CALL   ZCRLF
   CALL   ZCRLF
   
NextRestore1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, RBackup$MSG   ;for each track update display
   CALL   PSTRING
   LDA   mTRK2+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK2      ;Low TRK byte
   CALL   PHEX
   LXI   D, WBackup$MSG
   CALL   PSTRING
   LDA   mTRK1+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK1      ;Low TRK byte
   CALL   PHEX
   LXI   D,H$Msg
   CALL   PSTRING

NextRestore:   
   LDA   mSEC2      ;Point to backup partition
   STA   mSEC
   LHLD   mTRK2
   SHLD   mTRK
   CALL   wrlba      ;Update LBA on source drive

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA
   CALL   READSECTOR   ;Get sector data to buffer
   
   LDA   mSEC1
   STA   mSEC
   LHLD   mTRK1
   SHLD   mTRK
   CALL   wrlba      ;Update LBA on target drive
   
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA
   CALL   WRITESECTOR   ;Write buffer data to sector
   
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   RESNEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JZ   MAINLOOP

RESNEXTSEC1:
   LHLD   mSEC
   MOV   A, L      ;Current sector in A
   CALL   CHK$SEC      ;Is sector already at max?
   JZ   RESNextZero   ;Yes - go to sector 0
   INX   H      ;No - bump to next sector
   SHLD   mSEC1
   SHLD   mSEC2   
   JMP   NextRestore

RESNextZero:
   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC1
   SHLD   mSEC2
   
   LHLD   mTRK1      ;Get current Track
   MOV   A, L
   CPI   MAXTRK      ;Are we already at max?
   JZ   ResNextDone   ;If so, we are done
   INX   H      ;Bump to next track
   SHLD   mTRK1
   
   LHLD   mTRK2      ;Bump to next track
   INX   H
   SHLD   mTRK2
   JMP   NextRestore1

ResNextDone:   
   LXI   D, RestoreDone   ;Tell us we are all done.
   CALL   PSTRING
   JMP   MAINLOOP

ERROR:   LXI     D, msgErr   ;Command error msg
   CALL   PSTRING
   JMP   MAINLOOP

COPY$D0D1:         ;Copy drive 0 to drive 1
   LXI   D, DiskCopyMsg
   CALL   PSTRING
   CALL   ZCI
   CALL   UPPER
   CPI   'Y'
   JNZ   MAINLOOP
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   mSEC
   SHLD   mTRK      ;and track 0
   
   CALL   ZCRLF
   CALL   ZCRLF
   
NextDCopy1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, CopyTrk$MSG   ;for each track update display
   CALL   PSTRING
   LDA   mTRK+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK      ;Low TRK byte
   CALL   PHEX
   LXI   D, H$Msg
   CALL   PSTRING

NextDCopy:   
   CALL   SELECT0      ;Select drive 0
   CALL   wrlba      ;Update LBA on drive 0
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA
   CALL   READSECTOR   ;Get sector data from drive 0 to buffer
   
   CALL   SELECT1      ;Select drive 1
   CALL   wrlba      ;Update LBA on drive 1
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA
   CALL   WRITESECTOR   ;Write buffer data to sector on drive 1
   
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM says something is there
   JNZ   BK$D$NEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JNZ   BK$D$NEXTSEC1

   CALL   SELECT0      ;Select drive 0
   JMP   MAINLOOP

BK$D$NEXTSEC1:
   LHLD   mSEC
   MOV   A, L      ;Current sector to A
   CALL   CHK$SEC      ;Are we already at max?
   JZ   BK$D$NextZero   ;Yes - set sector to 0
   INX   H      ;No - continue on
   SHLD   mSEC
   JMP   NextDcopy

BK$D$NextZero:
   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC
   
   LHLD   mTRK      ;Get Current Track
   MOV   A, L
   CPI   MAXTRK      ;Are we already at max track?
   JZ   BK$D$NextDone   ;Yes - done
   INX   H      ;No - bump to next track
   SHLD   mTRK
   JMP   NextDcopy1

BK$D$NextDone:   
   LXI   D, CopyDone   ;Tell us we are all done
   CALL   PSTRING
   CALL   SELECT0      ;Select drive 0
   JMP   MAINLOOP

CMP$D0D1:         ;Verify Drive 0 = 1
   LXI   D, DiskVerifyMsg
   CALL   PSTRING
   
   LXI   H, 0      ;Start with CPM sector 0
   SHLD   mSEC
   SHLD   mTRK      ;and track 0
   
   CALL   ZCRLF
   CALL   ZCRLF
   
NextVCopy1:   
   CALL   ZEOL      ;Clear line cursor is on
   LXI   D, VerifyTrk$MSG ;for each track update display
   CALL   PSTRING
   LDA   mTRK+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK      ;Low TRK byte
   CALL   PHEX
   LXI   D, H$Msg
   CALL   PSTRING

NextVCopy:   
   CALL   SELECT0      ;Select drive 0
   CALL   wrlba      ;Update LBA on 0 drive
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA
   CALL   READSECTOR   ;Get sector data from buffer 0 drive
   
   CALL   SELECT1      ;Select drive 1
   CALL   wrlba      ;Update LBA on 1 drive
   LXI   H, buffer2   ;Point to buffer2
   SHLD   mDMA
   CALL   READSECTOR   ;Read buffer data from sector of drive 1
   
   LXI   B, 512      ;Now check both buffers are identical
   LXI   H, buffer
   LXI   D, buffer2
NEXTV:   LDAX   D
   CMP   M      ;Is [DE] = [HL]?
   JNZ   COMPARE$ERROR
   INX   H
   INX   D
   DCX   B
   MOV   A,C
   ORA   B
   JZ   VERIFY$OK
   JMP   NEXTV

COMPARE$ERROR:
   LXI   D, VERIFY$ERR   ;Indicate an error
   CALL   PSTRING
   LDA   mTRK+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK      ;Low TRK byte
   CALL   PHEX
   LXI   D, SEC$Msg
   CALL   PSTRING
   LDA   mSEC      ;Sector byte
   CALL   PHEX
   LXI   D, H$Msg
   CALL   PSTRING
   JMP   VER$OK1
   
VERIFY$OK:
   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM says something is there
   JNZ   BK$V$NEXTSEC1
   CALL   ZCI      ;Flush character
VER$OK1:
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   JNZ   BK$V$NEXTSEC1
   CALL   SELECT0      ;Select drive 0
   JMP   MAINLOOP

BK$V$NEXTSEC1:
   LHLD   mSEC
   MOV   A, L      ;Current sector to A
   CALL   CHK$SEC      ;Is it already at max
   JZ   BK$V$NEXTZero   ;Yes - back to sector 0
   INX   H      ;No - bump to next sector
   SHLD   mSEC
   JMP   NextVCopy

BK$V$NEXTZero:
   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC
   
   LHLD   mTRK      ;Get Current track
   MOV   A, L
   CPI   MAXTRK      ;Are we already at max
   JZ   BK$V$NEXTDone   ;Yes - all done
   INX   H      ;no - bump to next track
   SHLD   mTRK
   JMP   NextVCopy1

BK$V$NextDone:   
   LXI   D, VerifyDone   ;Tell us we are all done.
   CALL   PSTRING
   CALL   SELECT0      ;Select drive 0
   JMP   MAINLOOP

;----------------------------- SUPPORT FUNCTIONS ------------------------------   
            
driveid:CALL   IDEwaitnotbusy   ;Retrieve drive info
   RC
   MVI   D, COMMANDid
   MVI   E, REGcommand
   CALL   IDEwr8D      ;Issue the ID command
  IF VERBOSE
   LXI   D, READING$ID
   CALL   PSTRING
   LXI   D, DISKSTATUS   ;Print status message
   CALL   PSTRING
  ENDIF
   MVI   E, REGstatus   ;Get status after ID command
   CALL   IDErd8D      ;Check Status (info in [D])
  IF VERBOSE
   MOV   A, D
   CALL   PHEX      ;Print status
   CALL   ZPERCRLF
  ENDIF
   CALL   IDEwaitdrq   ;Wait for Busy=0, DRQ=1
   JC   SHOWerrors
   CALL   CLEAR$ID$BUFFER   ;Clear ID Buffer
  IF VERBOSE
   LXI   D, GETTING$ID
   CALL   PSTRING
  ENDIF
   MVI   B, 0      ;256 words
   LXI     H, IDbuffer   ;Store data here
   CALL   MoreRD16   ;Get 256 words of data from REGdata port to [HL]
   RET

spinup:            ;Start the drive
   MVI   D, COMMANDspinup
spup2:   MVI   E, REGcommand
   CALL   IDEwr8D
   CALL   IDEwaitnotbusy
   JC   SHOWerrors
   ORA   A      ;Clear carry
   RET
            
spindown:         ;Tell the drive to spin down
   CALL   IDEwaitnotbusy
   JC   SHOWerrors
   MVI   D,COMMANDspindown
   JMP   spup2

SequentialReads:      ;Sequentially read sectors from current position
   CALL   IDEwaitnotbusy
   JC   SHOWerrors
   CALL   ZCRLF

NEXTSEC:
   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

   CALL   READSECTOR   ;Errors will show in READSECTOR
   JZ   SEQOK
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC      ;Abort if ESC
   RZ
   
SEQOK:   CALL   ZEOL      ;Clear line cursor is on
   CALL   DISPLAYposition   ;Display current track, sector and head

   LXI   H, buffer   ;Point to buffer
   SHLD   mDMA

   LDA   mDisplayFlag   ;Do we have display flag on or off
   ORA   A      ;NZ = on
   CNZ   HEXDUMP
   CALL   ZCRLF
   CALL   ZCRLF
   CALL   ZCRLF

   CALL   ZCSTS      ;Any keyboard character will stop display
   CPI   01H      ;CPM Says something there
   JNZ   NEXTSEC1
   CALL   ZCI      ;Flush character
   LXI   D, CONTINUE$MSG
   CALL   PSTRING
   CALL   ZCI
   CPI   ESC
   RZ
   CALL   ZCRLF

NEXTSEC1:
   LHLD   mSEC
   MOV   A, L      ;Current sector to A
   CALL   CHK$SEC      ;Are we already at max sector?
   JZ   NextSecZero   ;Yes - back to sector 0
   INX   H      ;No - bump to next sector
   SHLD   mSEC   
   JMP   NEXTSEC

NextSecZero:
   LXI   H, 0      ;Back to CPM sector 0
   SHLD   mSEC
   LHLD   mTRK      ;Bump to next track
   INX   H
   SHLD   mTRK
   JMP   NEXTSEC      ;Note will go to last track on disk unless stopped

PRN$0$INFO:         ;Print Drive 0 identification info
   LXI   D, DRIVE0$INFO
   CALL   PSTRING
   CALL   REM$DRV
   CALL   SELECT0
   CALL   PRN$DRV$INFO
   CALL   IDEinit
   CALL   RET$DRV
   JMP   MAINLOOP

PRN$1$INFO:         ;Print Drive 1 identification info
   LXI   D, DRIVE1$INFO
   CALL   PSTRING
   CALL   REM$DRV
   CALL   SELECT1
   CALL   PRN$DRV$INFO
   CALL   IDEinit
   CALL   RET$DRV
   JMP   MAINLOOP

PRN$DRV$INFO:         ;Print drive identification info         
   CALL   driveid
   JZ   PRN$DETAILS
   LXI   D, ID$ERROR   ;On error, display message
   CALL   PSTRING
   RET

PRN$DETAILS:         ;Get Sector Count
   LXI     H, IDbuffer + 12
   MOV   A, M      ;(High Byte)
   ORA   A
   JNZ   PRN$DET2
   INX   H
   MOV   A, M      ;(Low Byte)
   ORA   A
   JNZ   PRN$DET2   ;Looks like we have a valid IDE drive
   
   LXI   D, BAD$DRIVE   ;Zero sectors means something's wrong
   CALL   PSTRING
   RET

PRN$DET2:         ;Print drive info
   LXI     D, msgmdl   ;Drive name   
   CALL   PSTRING
   LXI     H, IDbuffer + 54
   MVI   B, 20      ;Character count in words
   CALL   printSwap   ;Print [HL], [B] X 2 characters
   CALL   ZCRLF

   LXI     D, msgsn   ;Serial number
   CALL   PSTRING
   LXI     H, IDbuffer + 20
   MVI   B, 10      ;Character count in words
   CALL   printText
   CALL   ZCRLF

   LXI     D, msgrev   ;Firmware revision string
   CALL   PSTRING
   LXI     H, IDbuffer + 46
   MVI   B, 4      ;Character count in words
   CALL   printSwap
   CALL   ZCRLF

   LXI     D, msgcy   ;Drive specs (cyl/hd/sect)
   CALL   PSTRING
   LXI     H, IDbuffer + 2
   CALL   printparm
   LXI     D, msghd
   CALL   PSTRING
   LXI     H, IDbuffer + 6
   CALL   printparm
   LXI     D, msgsc
   CALL   PSTRING
   LXI     H, IDbuffer + 12
   CALL   printparm
   CALL   ZCRLF

   LXI   D, msgLBAsup1   ;First part of LBA support message
   CALL   PSTRING
   LDA   IDbuffer+98+1   ;Bits 15-10 reserved, 9 LBA, 8 DMA
   ANI   02H
   JNZ   PRN$SUP      ;LBA is supported
   LXI   D, msgLBAnot   ;LBA is not supported
   CALL   PSTRING

PRN$SUP:
   LXI   D, msgLBAsup2
   CALL   PSTRING
   RET

DISPLAYposition:      ;Display current track, sector & head position
   LXI     D, msgCPMTRK   ;Display in LBA format
   CALL   PSTRING      ;---- CPM FORMAT ----
   LDA   mTRK+1      ;High TRK byte
   CALL   PHEX
   LDA   mTRK      ;Low TRK byte
   CALL   PHEX
      
   LXI     D, msgCPMSEC
   CALL   PSTRING      ;SEC = (16 bits)
   LDA   mSEC+1      ;High Sec
   CALL   PHEX
   LDA   mSEC      ;Low sec
   CALL   PHEX
            ;---- LBA FORMAT ----
   LXI     D, msgLBA
   CALL   PSTRING      ;LBA = 00 ("Heads" = 0 for these drives)
   LDA   mDRIVE$TRK+1   ;High "cylinder" byte
   CALL   PHEX
   LDA   mDRIVE$TRK   ;Low "cylinder" byte
   CALL   PHEX   
   LDA   mDRIVE$SEC
   CALL   PHEX
   LXI     D, MSGBracket
   CALL   PSTRING      
   RET

printText:         ;Print text up to [B] (16-bit word) byte-pairs
   MOV   C, M      ;Text is contiguous byte array
   CALL   ZCO   
   INX   H
   MOV   C, M
   CALL   ZCO   
   INX   H
   DCR   B
   JNZ   printText
   RET

printSwap:         ;Print text up to [B] (16-bit word) byte-pairs   
   INX   H      ;Swap byte pairs - low byte, high byte
   MOV   C, M
   CALL   ZCO   
   DCX   H
   MOV   C, M
   CALL   ZCO
   INX   H
   INX   H
   DCR   B
   JNZ   printSwap
   RET

ZCRLF:
   PUSH   PSW
   MVI   C, CR
   CALL   ZCO
   MVI   C, LF
   CALL   ZCO
   POP   PSW
   RET

ZPERCRLF:
   PUSH   PSW
   MVI   C, PERIOD
   CALL   ZCO
   MVI   C, CR
   CALL   ZCO
   MVI   C, LF
   CALL   ZCO
   POP   PSW
   RET

ZEOL:            ;CR and clear current line
   MVI   C, CR
   CALL   ZCO
   MVI   C, CLEAR
   CALL   ZCO
   RET

ZCSTS:
  IF CPM
   PUSH   B
   PUSH   D
   PUSH   H
   MVI   C, CONST
   CALL   BDOS      ;Returns with 1 in [A] if character at keyboard
   POP   H
   POP   D
   POP   B
   CPI   1
   RET
  ENDIF
  IF NOT CPM
   IN   CONI      ;Get Character in [A]
   ANI   02H
   RZ
   MVI   A, 01H
   ORA   A
   RET
  ENDIF

ZCO:            ;Write character that is in [C]
  IF CPM
   PUSH   PSW
   PUSH   B
   PUSH   D
   PUSH   H
   MOV   E, C
   MVI   C, WRCON
   CALL   BDOS
   POP   H
   POP   D
   POP   B
   POP   PSW
   RET
  ENDIF
  IF NOT CPM
   PUSH   PSW   
ZCO1:   IN      CONI      ;Show Character
   ANI   04H
   JZ   ZCO1
   MOV   A, C
   OUT   CONO
   POP   PSW
   RET
  ENDIF

ZCI:            ;Return keyboard character in [A]
  IF CPM
   PUSH   B
   PUSH   D
   PUSH   H
   MVI   C, RDCON
   CALL   BDOS
   POP   H
   POP   D
   POP   B
   RET
  ENDIF
  IF NOT CPM
ZCI1:   IN   CONI      ;Get Character in [A]
   ANI   02H
   JZ   ZCI1
   IN   CONO
   RET
  ENDIF

;------------------------------------------------------------------------------   
;Print a string in [DE] up to '$'
;------------------------------------------------------------------------------   

PSTRING:
  IF CPM
   MVI   C, PRINT
   JMP   BDOS      ;PRINT MESSAGE
  ENDIF
  IF NOT CPM
   PUSH   B
   PUSH   D
   PUSH   H
   XCHG
PSTRX:   MOV   A, M
   CPI   '$'
   JZ   DONEP
   MOV   C, A
   CALL   ZCO
   INX   H
   JMP   PSTRX
DONEP:   POP   H
   POP   D
   POP   B
   RET
  ENDIF

SHOWerrors:
  IF NOT DEBUG
   ORA   A      ;Set NZ flag
   STC         ;Set Carry Flag
   RET
  ENDIF
  IF DEBUG
   CALL   ZCRLF
   MVI   E, REGstatus   ;Get status in status register
   CALL   IDErd8D
   MOV   A, D
   ANI   1H
   JNZ   MoreError   ;Go to  REGerr register for more info
            ;All OK if 01000000
   PUSH   PSW      ;Save for return below
   ANI   80H
   JZ   NOT7
   LXI   D, DRIVE$BUSY   ;Drive Busy (bit 7) stuck high
   CALL   PSTRING
   JMP   DONEERR
NOT7:   ANI   40H
   JNZ   NOT6
   LXI   D, DRIVE$NOT$READY ;Drive Not Ready (bit 6) stuck low
   CALL   PSTRING
   JMP   DONEERR
NOT6:   ANI   20H
   JNZ   NOT5
   LXI   D, DRIVE$WR$FAULT ;Drive write fault
   CALL   PSTRING
   JMP   DONEERR
NOT5   LXI   D, UNKNOWN$ERROR
   CALL   PSTRING
   JMP   DONEERR

MoreError:         ;Bit 0 of the status register indicates problem
   MVI   E, REGerr   ;Get error code in REGerr
   CALL   IDErd8D
   MOV   A, D
   PUSH   PSW

   ANI   10H
   JZ   NOTE4
   LXI   D, SEC$NOT$FOUND
   CALL   PSTRING
   JMP   DONEERR

NOTE4:   ANI   80H
   JZ   NOTE7
   LXI   D, BAD$BLOCK
   CALL   PSTRING
   JMP   DONEERR
NOTE7:   ANI   40H
   JZ   NOTE6
   LXI   D, UNRECOVER$ERR
   CALL   PSTRING
   JMP   DONEERR
NOTE6:   ANI   4H
   JZ   NOTE2
   LXI   D, INVALID$CMD
   CALL   PSTRING
   JMP   DONEERR
NOTE2:   ANI   2H
   JZ   NOTE1
   LXI   D, TRK0$ERR
   CALL   PSTRING
   JMP   DONEERR
NOTE1:   LXI   D, UNKNOWN$ERROR1
   CALL   PSTRING
   JMP   DONEERR

DONEERR:POP   PSW
   PUSH   PSW
   CALL   ZBITS
   CALL   ZCRLF
   POP   PSW
   ORA   A      ;Set Z flag
   STC         ;Set Carry flag
   RET
  ENDIF

;------------------------------------------------------------------------------   
;Print a 16-bit number in RAM located @ [HL], low-byte first for Drive ID
;------------------------------------------------------------------------------   

printparm:
   INX   H   ;Index to high byte first
   MOV   A, M
   CALL   PHEX
   DCX   H   ;Now low byte
   MOV   A, M
   CALL   PHEX
   RET

;------------------------------------------------------------------------------   
;Print an 8 bit number located in [A]
;------------------------------------------------------------------------------   

PHEX:   PUSH   PSW
   PUSH   B
   PUSH   PSW
   RRC
   RRC
   RRC
   RRC
   CALL   ZCONV
   POP   PSW
   CALL   ZCONV
   POP   B
   POP   PSW
   RET

ZCONV:   ANI   0FH      ;HEX to ASCII and print it
   ADI   90H
   DAA
   ACI   40H
   DAA
   MOV   C, A
   CALL   ZCO
   RET

;------------------------------------------------------------------------------   
;Display binary in [A]
;------------------------------------------------------------------------------   

ZBITS:   PUSH   PSW
   PUSH   B
   PUSH   D
   MOV   E, A      
   MVI   B, 8
BQ2:   DB   0CBH, 23H   ;SLA A, E
   MVI   A, 18H
   ADC   A
   MOV   C, A
   CALL   ZCO
   DCR   B
   JNZ   BQ2
   POP   D
   POP   B
   POP   PSW
   RET

ghex32lba:         ;Convert CPM Track & Sector to LBA format
   LXI     D,ENTER$SECL   ;Enter sector number
   CALL   PSTRING
   CALL   GETHEX      ;Get 2 HEX digits
   RC
   STA   mSEC
   CALL   ZCRLF

   LXI   D, ENTER$TRKH   ;Enter high byte track number
   CALL   PSTRING
   CALL   GETHEX      ;Get 2 HEX digits
   RC
   STA   mTRK+1
   CALL   ZCRLF

   LXI     D, ENTER$TRKL   ;Enter low byte track number
   CALL   PSTRING
   CALL   GETHEX      ;Get 2 more HEX digits
   RC
   STA   mTRK
   CALL   ZCRLF
   XRA   A
   ORA   A      ;To return NC
   RET

;------------------------------------------------------------------------------   
;Get a HEX character from the keyboard and echo it
;------------------------------------------------------------------------------   

GETHEX:
   CALL   GETCMD      ;Get character
   CPI   ESC
   JZ   HEXABORT
   CPI   '/'      ;check 0-9, A-F
   JC   HEXABORT
   CPI   'F'+1
   JNC   HEXABORT
   CALL   ASBIN      ;Convert to binary
   RLC         ;Shift to high nibble
   RLC
   RLC
   RLC
   MOV   B, A      ;Store it
   CALL   GETCMD      ;Get 2nd character from keyboard & ECHO
   CPI   ESC
   JZ   HEXABORT
   CPI   '/'      ;check 0-9, A-F
   JC   HEXABORT
   CPI   'F'+1
   JNC   HEXABORT
   CALL   ASBIN      ;Convert to binary
   ORA   B      ;add in the first digit
   ORA   A      ;To return NC
   RET
HEXABORT:
   STC         ;Set Carry flag
   RET

;------------------------------------------------------------------------------   
;Get a character from the keyboard, convert to uppercase and echo it
;------------------------------------------------------------------------------   

GETCMD:   CALL   ZCI      ;Get character
   CALL   UPPER
   CPI   ESC
   RZ         ;Don't echo an ESC
  IF NOT CPM
   PUSH   PSW      ;Save state of registers
   PUSH   B
    MOV   C, A
   CALL   ZCO      ;Echo it
   POP   B
   POP   PSW      ;Retrieve original state
  ENDIF
   RET

;------------------------------------------------------------------------------   
;Convert lowercase to uppercase
;------------------------------------------------------------------------------   

UPPER:   CPI   'a'      ;Must be >= lowercase a
   RC         ;else return as-is
   CPI   'z'+1      ;Must be <= lowercase z
   RNC         ;else return as-is
   SUI   'a'-'A'      ;Subtract lowercase bias
   RET

ASBIN:   SUI   30H       ;ASCII to binary conversion
   CPI   0AH
   RM
   SUI   07H
   RET

;------------------------------------------------------------------------------   
;Print a hexdump of the data in the 512 byte buffer starting at [HL]
;------------------------------------------------------------------------------   

HEXDUMP:
   PUSH   PSW      ;Save everything
   PUSH   B
   PUSH   D         
   PUSH   H
   
   CALL   ZCRLF      ;CR/LF first
   MVI   D, 32      ;Print 32 lines total
   MVI   B, 16      ;16 characters across
   SHLD   mStartLineHex   ;Save buffer location for ASCII display below
   LXI   H, 0
   SHLD   mBYTE$COUNT
   
SF172:   CALL   ZCRLF
   LHLD   mBYTE$COUNT
   MOV   A, H
   CALL   PHEX      ;Print byte count in sector
   MOV   A, L
   CALL   PHEX      
   PUSH   D
   LXI   D, 16
   DAD   D
   POP   D
   SHLD   mBYTE$COUNT   ;Store for next time
   CALL   BLANK
   LHLD   mStartLineHex
   SHLD   mStartLineASCII   ;Store for ASCII display below

SF175:   MOV   A, M
   CALL   LBYTE      ;Display [A] on CRT/LCD
   INX   H
   DCR   B
   JNZ   SF175
   SHLD   mStartLineHex   ;Save for next line later
   CALL   ShowAscii   ;Now translate to ASCII and display
   MVI   B, 16      ;16 characters across for next line
   DCR   D
   JNZ   SF172      ;Have we done all 32 lines

   CALL   ZCRLF
   POP   H      ;Get back original registers
   POP   D
   POP   B
   POP   PSW
   RET
   
ShowAscii:         ;Show as ASCII info
   LHLD   mStartLineASCII
   MVI   B, 16      ;16 ASCII characters across
XF172:   CALL   BLANK      ;Send a space character
   CALL   BLANK
XF175:   MOV   A, M
   ANI   7FH
   CPI   ' '       ;Filter out control characters
   JNC   XT33
XT22:   MVI   A, '.'
XT33:   CPI   07CH
   JNC   XT22
   MOV   C, A      ;Setup to send
   PUSH   B
   CALL   ZCO
   POP   B
   INX   H      ;Next position in buffer
   DCR   B
   JNZ   XF175
   RET

BLANK:   PUSH   B
   PUSH   H
   MVI   C, ' '
   CALL   ZCO
   POP   H
   POP   B
   RET

LBYTE:   PUSH   PSW
   RRC
   RRC
   RRC
   RRC
   CALL   SF598
   POP   PSW

SF598:   CALL   ZCONV
   RET

CHK$SEC:         ;Compare current "CPM" sector to max "CPM" sector
   PUSH   B      ;Save
   MOV   C, A      ;C <- Current Sector
   MVI   B, MAXSEC   ;Retrieve max sector number
   MOV   A, C      ;Get current sector back in A for compare (and return with it in A)
   CMP   B      ;Current : Max
   POP   B
   RET         ;Return with compare status. (Carry => Max > Current)

;------------------------------------------------------------------------------   
;IDE Drive BIOS Routines written in a format that can be used directly with CPM
;------------------------------------------------------------------------------   

IDEinit:         ;Initialize the 8255 and drive then do a hard reset
   PUSH   B
   PUSH   D
  IF VERBOSE
   LXI   D, INITDRIVE
   CALL   PSTRING
  ENDIF
   MVI   A, READcfg8255   ;Config 8255 chip (10010010B)
   OUT   IDEportCtrl   ;for READ mode
            
   MVI   A, IDErstline   ;Hard reset the disk drive
   OUT   IDEportC   ;Some CF cards are sensitive to reset pulse width
   MVI   B, 20H      ;Symptom is incorrect data back from a sector read

ResetDelay:
   DCR   B
   JNZ   ResetDelay   ;Delay (reset pulse width)

   XRA   A
   OUT   IDEportC   ;No control lines asserted (just bit 7 of port C)
   CALL   DELAY$SHORT   ;Short Delay

   CALL   IDEwaitnotbusy   ;Wait for drive
   JC   WaitInitErr

   MVI   D,11100000b   ;Data for IDE SDH reg (512byte, LBA, single drive, hd 0)
            ;For Trk, Sec, Head (non LBA) use 10100000

   MVI   E,REGshd   ;00001110,(0EH) for CS0,A2,A1, 
   CALL   IDEwr8D      ;Write byte to select the MASTER device

   MVI   B, 02H      ;Delay time for hard disks to get up to speed (2s)

WaitInit:
  IF VERBOSE
   LXI   D, DISKSTATUS   ;Print initialization status message
   CALL   PSTRING
  ENDIF
   MVI   E, REGstatus   ;Get status after initilization
   CALL   IDErd8D      ;Check Status (info in [D])
   MOV   A, D
  IF VERBOSE
   CALL   PHEX      ;Print drive initialization status
   CALL   ZPERCRLF
  ENDIF
   ANI   80H
   JNZ   WaitInitL   ;Need a longer wait...
   POP   D      ;Restore registers
   POP   B
   RET         ;Return. We'll check for errors when we get back
WaitInitL:
   MVI   A, 2
   CALL   DELAY$LONG   ;Long delay, drive has to get up to speed
   DCR   B
   JNZ   WaitInit
   XRA   A
   DCR   A
   POP   D
   POP   B
   RET         ;Return NZ. We'll check for errors when we get back
WaitInitErr:
   XRA   A
   DCR   A      ;Return NZ (error)
   POP   D      ;Restore Registers
   POP   B
   RET         ;Return and check for errors there
   
DELAY$LONG:         ;Long delay (Seconds)
   STA   mDELAYStore
   PUSH   B
   LXI   B, 0FFFFH
DELAY2:   LDA   mDELAYStore
DELAY1:   DCR   A
   JNZ   DELAY1
   DCX   B
   MOV   A, C
   ORA   B
   JNZ   DELAY2
   POP   B
   RET

DELAY$SHORT:         ;Short delay (32ms)
   MVI   A, 40
DELAY3:   MVI   B, 0
M0:   DCR   B
   JNZ   M0
   DCR   A
   JNZ     DELAY3
   RET

SELECT0:
   XRA   A      ;Select drive 0
   STA   mCURRENT$DRIVE
   OUT   IDEDrive   
   RET

SELECT1:
   MVI   A, 1      ;Select drive 1
   STA   mCURRENT$DRIVE
   OUT   IDEDrive   
   RET

REM$DRV:         ;Remember last drive used
   LDA   mCURRENT$DRIVE
   STA   mLAST$DRIVE
   RET

RET$DRV:         ;Return to last drive used
   LDA   mLAST$DRIVE
   STA   mCURRENT$DRIVE
   OUT   IDEDrive   
   RET

;------------------------------------------------------------------------------   
;Sector Read
;------------------------------------------------------------------------------   

READSECTOR:         ;Read a sector, specified by the 3 bytes in LBA
            ;Z on success, NZ call error routine if problem

   CALL   wrlba      ;Tell which sector we want to read from.
            ;Translate first in case of an error, otherewise
            ;we will get stuck on bad sector
 
   CALL   IDEwaitnotbusy   ;Make sure drive is ready
   JC   SHOWerrors   ;Returned with NZ set if error

   MVI   D, COMMANDread
   MVI   E, REGcommand
   CALL   IDEwr8D      ;Send sec read command to drive.
   CALL   IDEwaitdrq   ;Wait until it's got the data
   JC   SHOWerrors
      
   LHLD     mDMA      ;DMA address
   MVI   B, 0      ;Read 512 bytes to [HL]

MoreRD16:
   MVI   A, REGdata   ;REG register address
   OUT   IDEportC   

   ORI   IDErdline   ;08H+40H, Pulse RD line
   OUT   IDEportC   

   IN   IDEportA   ;Read the lower byte first
   MOV   M, A
   INX   H
   IN   IDEportB   ;Then read the upper byte
   MOV   M, A
   INX   H
   
   MVI   A, REGdata   ;Deassert RD line
   OUT   IDEportC
   DCR   B
   JNZ   MoreRD16

   MVI   E, REGstatus
   CALL   IDErd8D
   MOV   A, D
   ANI   1H
   CNZ   SHOWerrors   ;If error display status
   RET

;------------------------------------------------------------------------------   
;Sector Write
;------------------------------------------------------------------------------   

WRITESECTOR:         ;Write a sector, specified by the 3 bytes in LBA
            ;Z on success, NZ to error routine if problem

   CALL   wrlba      ;Tell which sector we want to read from.
            ;Translate first in case of an error, otherewise
            ;we will get stuck on bad sector

   CALL   IDEwaitnotbusy   ;Make sure drive is ready
   JC   SHOWerrors

   MVI   D, COMMANDwrite
   MVI   E, REGcommand
   CALL   IDEwr8D      ;Tell drive to write a sector
   CALL   IDEwaitdrq   ;Wait unit it wants the data
   JC   SHOWerrors

   LHLD    mDMA
   MVI   B, 0

   MVI   A, WRITEcfg8255
   OUT   IDEportCtrl
   
WRSEC1:   MOV   A, M
   INX   H
   OUT   IDEportA   ;Write the lower byte first
   MOV   A, M
   INX   H
   OUT   IDEportB   ;Then high byte on B

   MVI   A, REGdata
   PUSH   PSW
   OUT   IDEportC   ;Send write command
   ORI   IDEwrline   ;Send WR pulse
   OUT   IDEportC
   POP   PSW
   OUT   IDEportC
   DCR   B
   JNZ   WRSEC1
   
   MVI   A, READcfg8255   ;Set 8255 back to read mode
   OUT   IDEportCtrl   

   MVI   E, REGstatus
   CALL   IDErd8D
   MOV   A, D
   ANI   1H
   CNZ   SHOWerrors   ;If error display status
   RET

;------------------------------------------------------------------------------   
;Write Logical Block Address (LBA) mode
;------------------------------------------------------------------------------   

wrlba:
   CALL   IDEwaitnotbusy   ;Make sure drive isn't busy
   JC   SHOWErrors   ;If error, display status   

   LHLD   mTRK      ;Get the "CPM" requested track High & Low
   MOV   A, L      ;Get Low byte of track
   RRC         ;Get bottom two bits in high bits of A
   RRC
   ANI   0C0H      ;Just what were the bottom two bits (now at the top)
   MOV   C, A      ;Save in C
   LDA   mSEC      ;Sector number in A
   ANI   03FH      ;Take only bottom 6 bits
   ORA   C      ;Add in top 2 bits of track
   STA   mDRIVE$SEC   ;For diagnostic display only
   MOV   D, A      ;Send info to the drive
   MVI   E, REGsector
   CALL   IDEwr8D

   MOV   A, L      ;Get low byte of track again
   RRC
   RRC
   ANI   03FH
   MOV   C, A      ;Save in C
   MOV   A, H      ;Get high byte of track.
   RRC         ;Rotate twice, leaving low 2 bits
   RRC         ;In upper bits of A
   ANI   0C0H      ;Mask all but the two bits we want
   ORA   C      ;Add in the top 6 bits of the first track byte
   STA   mDRIVE$TRK
   MOV   D, A      ;Send Low TRK#
   MVI   E, REGcylinderLSB
   CALL   IDEwr8D
   
   MOV   A, H      ;Get high byte of track
   RRC         ;Just the top 6 bits
   RRC
   ANI   03FH
   STA   mDRIVE$TRK+1
   MOV   D, A      ;Send High TRK#
   MVI   E, REGcylinderMSB
   CALL   IDEwr8D

   MVI   D, 1      ;One sector at a time
   MVI   E, REGseccnt
   CALL   IDEwr8D
   RET

;------------------------------------------------------------------------------   
;Wait for drive to come ready
;------------------------------------------------------------------------------   

IDEwaitnotbusy:         ;Drive READY if status = 01000000
   MVI   B, 0FFH
   MVI   A, 0FFH      ;Delay must be above 80H, longer for slow drives
   STA   mDELAYStore

MoreWait:
   MVI   E, REGstatus   ;Wait for RDY bit to be set
   CALL   IDErd8D
   MOV   A, D
   ANI   11000000B
   XRI   01000000B
   JZ   DoneNotbusy
   DCR   B   
   JNZ   MoreWait
   LDA   mDELAYStore   ;Check timeout delay
   DCR   A
   STA   mDELAYStore
   JNZ   MoreWait

   STC         ;Set carry to indicate an error
   ret
DoneNotBusy:
   ORA   A      ;Clear carry it indicate no error
   RET

;------------------------------------------------------------------------------   
;Wait for drive to assert data request (DRQ) line ready
;------------------------------------------------------------------------------   

IDEwaitdrq:
   MVI   B, 0FFH
   MVI   A, 0FFH      ;Delay must be above 80H, longer for slow drives
   STA   mDELAYStore

MoreDRQ:
   MVI   E, REGstatus   ;Wait for DRQ bit to be set
   CALL   IDErd8D
   MOV   A, D
   ANI   10001000B
   CPI   00001000B
   JZ   DoneDRQ
   DCR   B
   JNZ   MoreDRQ
   LDA   mDELAYStore   ;Check timeout delay
   DCR   A
   STA   mDELAYStore
   JNZ   MoreDRQ
   STC         ;Set carry to indicate error
   RET
DoneDRQ:
   ORA   A      ;Clear carry
   RET         ;Return drive status in A

;------------------------------------------------------------------------------   
;Clear the ID buffer
;------------------------------------------------------------------------------   

CLEAR$ID$BUFFER:
   LXI   H, IDBuffer
   LXI   B, 512
CLEAR2:   MVI   A, ' '
   MOV   M, A
   INX   H
   DCX   B
   MOV   A, C
   ORA   B
   JNZ   CLEAR2
   
   LXI   H, IDBuffer   ;Zero for cylinder, heads, sectors
   LXI   B, 14
CLEAR3:   MVI   A, 0
   MOV   M, A
   INX   H
   DCX   B
   MOV   A, C
   ORA   B
   JNZ   CLEAR3
   RET

;------------------------------------------------------------------------------   
; Low Level 8 bit R/W to the drive controller. These are the routines that talk
; directly to the drive controller registers, via the 8255 chip. 
; Note the 16 bit I/O to the drive (which is only for SEC R/W) is done directly
; in the routines READSECTOR & WRITESECTOR for speed reasons.
;------------------------------------------------------------------------------   

;------------------------------------------------------------------------------   
;Read One Byte
;------------------------------------------------------------------------------   

IDErd8D:            ;Read 8 bits from IDE register in [E],
   MOV   A, E         ;and return info in [D]
   OUT   IDEportC      ;Drive address onto control lines

   ORI   IDErdline      ;RD pulse pin (40H)
   OUT   IDEportC      ;Assert read pin

   IN   IDEportA
   MOV   D, A         ;Return with data in [D]

   MOV   A, E
   OUT   IDEportC      ;Deassert RD pin

   XRA   A
   OUT   IDEportC      ;Zero all port C lines
   RET

;------------------------------------------------------------------------------   
;Write One Byte
;------------------------------------------------------------------------------   

IDEwr8D:            ;Write Data in [D] to IDE register [E]
   MVI   A, WRITEcfg8255      ;Set 8255 to write mode
   OUT   IDEportCtrl

   MOV   A, D         ;Get data put it in 8255 A port
   OUT   IDEportA

   MOV   A, E         ;Select IDE register
   OUT   IDEportC

   ORI   IDEwrline      ;Lower WR line
   OUT   IDEportC
   
   MOV   A, E         ;Raise WR line
   OUT   IDEportC      ;Deassert RD pin

   XRA   A         ;Deselect all lines including WR line
   OUT   IDEportC

   MVI   A, READcfg8255      ;Config 8255 chip, read mode on return
   OUT   IDEportCtrl
   RET

;------------------------------------------------------------------------------   
;This code is written to reside and run from 0H.  To re-introduce the CPMLDR,
;it must be copied from where it is stored in high memory and relocated to 100H
;in RAM, which overwrites this program.
;------------------------------------------------------------------------------   

CPM$MOVE$CODE
   LXI   H, BUFFER
   LXI   D, 100H
   LXI   B, (12*512)
   LDIR
   JMP   100H
CPM$MOVE$CODE$END:

;------------------------------------------------------------------------------   
;
;COMMAND BRANCH TABLE
;
;------------------------------------------------------------------------------   

TBL:   DW  DRIVE$0   ; "A"  Select Drive 0
   DW  DRIVE$1   ; "B"  Select Drive 1
   DW  CPMBOOT   ; "C"  LOAD CPM (if present)
   DW  DISPLAY   ; "D"  Sector contents display: ON/OFF
   DW  RAMCLEAR  ; "E"  Clear RAM buffer
   DW  FORMAT    ; "F"  Format current disk
   DW  RESTORE   ; "G"  Restore backup
   DW  BACKUP    ; "H"  Backup partition
   DW  NEXT$SECT ; "I"  Next Sector
   DW  PREV$SEC  ; "J"  Previous sector
   DW  ERROR     ; "K" 
   DW  SET$LBA   ; "L"  Set LBA value (Set track, sector) 
   DW  ERROR     ; "M" 
   DW  POWER$DOWN; "N"  Power down hard disk command
   DW  ERROR     ; "O" 
   DW  PRN$0$INFO; "P"  Print Drive 0 ID info 
   DW  PRN$1$INFO; "Q"  Print Drive 1 ID info
   DW  READ$SEC  ; "R"  Read sector to data buffer
   DW  SEQ$RD    ; "S"  Sequental sec read and display contents
   DW  ERROR     ; "T" 
   DW  POWER$UP  ; "U"  Power up hard disk command
   DW  N$RD$SEC  ; "V"  Read N sectors
   DW  WRITE$SEC ; "W"  Write data buffer to current sector
   DW  N$WR$SEC  ; "X"  Write N sectors
   DW  COPY$D0D1 ; "Y"  Copy Drive 0 to Drive 1
   DW  CMP$D0D1  ; "Z"  Verify Drive 0 = Drive 1

;------------------------------------------------------------------------------   
;
;String constants - Messages generated by this program
;
;------------------------------------------------------------------------------

SIGN$ON:   DB   CR,LF,'IDE Disk Drive Utility Program  v3.0  12-08-2022',CR,LF,LF,'$'
SEL0MSG      DB   'Selecting first IDE drive.',CR,LF,'$'
SEL1MSG      DB   'Selecting second IDE drive.',CR,LF,'$'
INITDRIVE   DB   'Initializing drive.  $'
READING$ID   DB   'Reading drive ID.  $'
GETTING$ID   DB   'Getting drive ID...',CR,LF,'$'
DISKSTATUS   DB   'Status is $'
INIT$0$ERROR:   DB   'Initialization of first drive failed. Aborting program.',BELL,CR,LF,LF,'$'
INIT$1$ERROR   DB   'Initialization of second drive failed. (Possibly not present).',BELL,CR,LF,LF,'$'
ID$ERROR:   DB   'Error obtaining drive ID.',BELL,CR,LF,'$'
INIT$DR$OK:   DB   'Drive initialized OK.',CR,LF,LF,'$'
BAD$DRIVE:   DB   CR,LF,'First Drive ID Information appears invalid.',CR,LF
      DB   'Aborting program.',BELL,CR,LF,LF,'$'
DRIVE0$INFO:   DB   '------------ Drive 0 -------------',CR,LF,'$'
DRIVE1$INFO:   DB   '------------ Drive 1 -------------',CR,LF,'$'
msgmdl:      DB   'Model: $'
msgsn:      DB   'S/N:   $'
msgrev:      DB   'Rev:   $'
msgcy:      DB   'Cyl: $'
msghd:      DB   ', Hd: $'
msgsc:      DB   ', Sec: $'
msgCPMTRK:   DB   'CPM TRK = $'
msgCPMSEC:   DB   ' CPM SEC = $'
msgLBA:      DB   '  (LBA = 00$'
MSGBracket   DB   ')$'
msgLBAsup1:   DB   'LBA is $'
msgLBAnot:   DB   'NOT $'
msgLBAsup2   DB   'supported',CR,LF,'$'
DRIVE$0$MSG   DB   CR,LF,LF,'  >>> DRIVE #0 <<<$'
DRIVE$1$MSG   DB   CR,LF,LF,'  >>> DRIVE #1 <<<$'
CMD$STRING1:    DB   '     IDE Board Diagnostic MAIN MENU',CR,LF,LF
      DB   '(A) Select Drive 0        (P) Drive 0 Information   '
      DB   '(H) Backup Disk',CR,LF
      DB   '(B) Select Drive 1        (Q) Drive 1 Information   '
      DB   '(G) Restore Backup',CR,LF
      DB   '(L) Set LBA value         (R) Read Sector to Buffer '
      DB   '(W) Write Buffer to Sector',CR,LF
      DB   '(I) Next Sector           (V) Read N Sectors        '
      DB   '(X) Write N Sectors',CR,LF
      DB   '(J) Previous Sector       (S) Sequental Sector Read '
      DB   '(Y) Copy Drive 0 to 1',CR,LF
      DB   '(U) Power Up              (N) Power Down            '
      DB   '(Z) Verify Drive 0 = 1',CR,LF
      DB   '(F) Format Disk           (D) Set Display ON        '
      DB   '(ESC) Quit',CR,LF
      DB   LF,'Current settings: $'
CMD$STRING2:    DB   '     IDE Board Diagnostic MAIN MENU',CR,LF,LF
      DB   '(A) Select Drive 0        (P) Drive 0 Information   '
      DB   '(H) Backup disk',CR,LF
      DB   '(B) Select Drive 1        (Q) Drive 1 Information   '
      DB   '(G) Restore Backup',CR,LF
      DB   '(L) Set LBA value         (R) Read Sector to Buffer '
      DB   '(W) Write Buffer to Sector',CR,LF
      DB   '(I) Next Sector           (V) Read N Sectors        '
      DB   '(X) Write N Sectors',CR,LF
      DB   '(J) Previous Sector       (S) Sequental Sector Read '
      DB   '(Y) Copy Drive 0 to 1',CR,LF
      DB   '(U) Power Up              (N) Power Down            '
      DB   '(Z) Verify Drive 0 = 1',CR,LF
      DB   '(F) Format Disk           (D) Set Display OFF       '
      DB   '(ESC) Quit',CR,LF
      DB   LF,'Current settings: $'
Prompt:      DB   CR,LF,LF,'Please enter command > $'
Response:   DB   CR,LF,'Command received:      $'
msgsure:   DB   CR,LF,'Warning: this will change data on the drive, '
      DB   'are you sure? (Y/N)...$'
msgrd:      DB   CR,LF,'Sector Read OK',CR,LF,'$'
msgwr:      DB   CR,LF,'Sector Write OK',CR,LF,'$'
GET$LBA:   DB   'Enter CPM style TRK & SEC values (in hex).',CR,LF,'$'
SEC$RW$ERROR   DB   'Drive Error, Status Register = $'
ERR$REG$DATA   DB   'Drive Error, Error Register = $'
ENTER$SECL   DB   'Starting sector number,(xxH) = $'
ENTER$TRKL   DB   'Track number (LOW byte, xxH) = $'
ENTER$TRKH   DB   'Track number (HIGH byte, xxH) = $'
ENTER$HEAD   DB   'Head number (01-0F) = $'
ENTER$COUNT   DB   'Number of sectors to R/W = $'
DRIVE$BUSY   DB   'Drive Busy (bit 7) stuck high.   Status = $'
DRIVE$NOT$READY   DB   'Drive Ready (bit 6) stuck low.  Status = $'
DRIVE$WR$FAULT   DB   'Drive write fault.    Status = $'
UNKNOWN$ERROR   DB   'Unknown error in status register.   Status = $'
BAD$BLOCK   DB   'Bad Sector ID.    Error Register = $'
UNRECOVER$ERR   DB   'Uncorrectable data error.  Error Register = $'
READ$ID$ERROR   DB   'Error setting up to read Drive ID',CR,LF,'$'
SEC$NOT$FOUND   DB   'Sector not found. Error Register = $'
INVALID$CMD   DB   'Invalid Command. Error Register = $'
TRK0$ERR   DB   'Track Zero not found. Error Register = $'
UNKNOWN$ERROR1   DB   'Unknown Error. Error Register = $'
CONTINUE$MSG   DB   CR,LF,'ESC to abort. Any other key to continue. $'
FORMAT$MSG   DB   'FORMAT DISK. Fill all sectors with E5'
      DB   60H,'s on the CURRENT drive/CF card.$'
ReadN$MSG   DB   CR,LF,'Read multiple sectors from current disk/CF card to RAM buffer.'
      DB   CR,LF,'How many 512 byte sectors (xx HEX):$'
WriteN$MSG   DB   CR,LF,'Write multiple sectors RAM buffer CURRENT disk/CF card.'
      DB   CR,LF,'How many 512 byte sectors (xx HEX):$'
ReadingN$MSG   DB   CR,LF,'Reading Sector at: $'
WritingN$MSG   DB   CR,LF,'Writing Sector at: $'
msgErr      DB   CR,LF,'Sorry, that was not a valid menu option!$'
FormatDone   DB   CR,LF,'Disk Format Complete.',CR,LF,'$'
BackupDone   DB   CR,LF,'Disk partition copy complete.',CR,LF,'$'
CopyMsg      DB   CR,LF,'Copy disk partition to a second area on disk (CF card).'
      DB   CR,LF,'>>> This assumes that tracks greater than MAXTRK '
      DB   '(for CPM, 0FFH) are unused <<<'
      DB   CR,LF,'>>> on this disk. Be sure you have nothing in this '
      DB   '"Backup partition area". <<<'
      DB   CR,LF,BELL,'Warning: This will change data in the partition area, '
      DB   'are you sure? (Y/N)...$ '
AtEnd      DB   CR,LF,'At end of disk partition!',CR,LF,'$'
RBackup$MSG   DB   'Reading track: $'
WBackup$MSG   DB   'H. Writing track: $'
H$Msg      DB   'H$'
RestoreMsg   DB   CR,LF,'Restore disk with data from backup partition on disk (CF card).'
      DB   CR,LF,BELL,'Warning: This will change data on disk, '
      DB   'are you sure? (Y/N)...$ '
RestoreDone   DB   CR,LF,'Restore of disk data from backup partition complete.',CR,LF,'$'
RANGE$MSG   DB   CR,LF,'Sector value out of range.',CR,LF,'$'
CPM$ERROR   DB   CR,LF,'Error reading CPMLDR.',CR,LF,'$'
CPM$ERROR1   DB   CR,LF,'Data error reading CPMLDR. (The first byte loaded was not 31H).',CR,LF,'$'
MOVE$REQUEST   DB   CR,LF,'The CPMLDR image is now at 3000H in RAM. '
      DB   'To boot CPM you will have to'
      DB   CR,LF,'overwrite this program at 100H. Do you wish to do so (Y/N)...$'
SET0$MSG   DB   CR,LF,'Current Drive is now #0 (Yellow LED)$'
SET1$MSG   DB   CR,LF,'Current Drive is now #1 (Green LED)$'
FILL$MSG   DB   CR,LF,'Sector buffer in RAM filled with 0',27H,'s$'      
DiskCopyMsg   DB   CR,LF,'Copy main disk partition of Drive 0 to Drive 1 (CF card).'
      DB   CR,LF,BELL,'Warning: This will delete all data on Drive 1, '
      DB   'are you sure? (Y/N)...$ '
CopyDone   DB   CR,LF,'Copy of CPM disk 0 to 1 complete.',CR,LF,'$'
CopyTrk$MSG   DB   'Copying track: $'
DiskVerifyMsg   DB   CR,LF,'Verify disk partition Drive 0 = Drive 1 (CF card).$'
VerifyTrk$MSG   DB   'Verifying track: $'
VerifyDone   DB   CR,LF,'Verify CPM disk 0 = 1 complete.',CR,LF,'$'
Verify$ERR   DB   CR,LF,BELL,'Verify error on Track $'
SEC$Msg      DB   'H  Sector $'

;------------------------------------------------------------------------------   
;RAM usage
;------------------------------------------------------------------------------   

RAMAREA      DB   '           RAM STORE AREA -------->'
mDMA      DW   buffer
mDRIVE$SEC   DB   0H
mDRIVE$TRK   DW   0H
mDisplayFlag   DB   0FFH      ;Display of sector data initially ON
mSEC      DW   0H
mTRK      DW   0H
mSEC1      DW   0H      ;For disk partition copy
mTRK1      DW   0H
mSEC2      DW   0H
mTRK2      DW   0H
mStartLineHex   DW   0H
mStartLineASCII   DW   0H
mBYTE$COUNT   DW   0H
mSECTOR$COUNT   DW   0H
mDELAYStore   DB   0H
mCURRENT$DRIVE   DB   0H
mLAST$DRIVE   DB   0H
mDriveBPresent   DB   0H      ;1 if second drive is present

      DB   '          Start of ID buffer-->'
IDbuffer:   DS   512
      DB   '<--End of ID buffer            '

      ORG   BUFFER$ORG

BUFFER:      DB   76H
      DB   '<--Start buffer area'
      DS   476
      DB   'End of buffer-->'

BUFFER2:   DB   '<--Start buffer2 area'
      DS   476
      DB   'End of buffer2-->'

      DS   100H

STACK      DW   0H

;END
Wayne Parham
 
Posts: 240
Joined: March 18th, 2022, 3:01 pm

Re: IDE interface for Altair 8800c

Postby TronDD » December 10th, 2022, 4:35 pm

I poked around with my Dual IDE card. I have the version with the GALs and had a hard time getting them programmed. Several programmers, a bunch of money, and a lot of hassle later, they seemed to program but I never trusted them. When the card didn't work, I was fed up with it and put it aside for over a year.

Taking a closer look now, I checked the output of the GALs. I'm not sure they are working correctly. GAL1, which does the address handling, seems to output regardless of the address. And GAL2 which handles the chip signals seems like it might be doing some of right thing except it never sends the chip enable signal to the 8255. So the 8255 is forever dead in the water.

I hated the GALs from the start and I no longer have access to a Windows system that the programmer software requires to check them or reprogram them. I'm just going to order the next version of the board that did away with the GALs and start over.
TronDD
 
Posts: 40
Joined: November 20th, 2018, 8:51 pm

PreviousNext

Return to Altair 8800c

Who is online

Users browsing this forum: Google [Bot] and 7 guests

cron